From a453ac31f3428614cceb99027f8efbdb9258a40b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 14 May 2024 22:03:01 +0200 Subject: Adding upstream version 2.10.7+merged+base+2.10.8+dfsg. Signed-off-by: Daniel Baumann --- .../community/network/plugins/action/__init__.py | 0 .../community/network/plugins/action/aireos.py | 89 + .../community/network/plugins/action/aruba.py | 88 + .../community/network/plugins/action/ce.py | 94 + .../network/plugins/action/ce_template.py | 104 + .../community/network/plugins/action/cnos.py | 78 + .../network/plugins/action/edgeos_config.py | 36 + .../community/network/plugins/action/enos.py | 78 + .../community/network/plugins/action/exos.py | 45 + .../community/network/plugins/action/ironware.py | 83 + .../plugins/action/network/edgeos/edgeos_config.py | 36 + .../plugins/action/network/nos/nos_config.py | 31 + .../community/network/plugins/action/nos_config.py | 31 + .../community/network/plugins/action/slxos.py | 40 + .../community/network/plugins/action/sros.py | 80 + .../community/network/plugins/action/voss.py | 36 + .../community/network/plugins/become/__init__.py | 0 .../community/network/plugins/cache/__init__.py | 0 .../community/network/plugins/callback/__init__.py | 0 .../community/network/plugins/cliconf/__init__.py | 0 .../community/network/plugins/cliconf/aireos.py | 96 + .../community/network/plugins/cliconf/apconos.py | 72 + .../community/network/plugins/cliconf/aruba.py | 96 + .../community/network/plugins/cliconf/ce.py | 122 + .../community/network/plugins/cliconf/cnos.py | 136 + .../community/network/plugins/cliconf/edgeos.py | 115 + .../network/plugins/cliconf/edgeswitch.py | 142 + .../community/network/plugins/cliconf/enos.py | 104 + .../network/plugins/cliconf/eric_eccli.py | 97 + .../community/network/plugins/cliconf/exos.py | 230 ++ .../community/network/plugins/cliconf/icx.py | 314 ++ .../community/network/plugins/cliconf/ironware.py | 96 + .../community/network/plugins/cliconf/netvisor.py | 75 + .../community/network/plugins/cliconf/nos.py | 113 + .../community/network/plugins/cliconf/routeros.py | 79 + .../community/network/plugins/cliconf/slxos.py | 105 + .../community/network/plugins/cliconf/voss.py | 236 ++ .../network/plugins/connection/__init__.py | 0 .../network/plugins/doc_fragments/__init__.py | 0 .../community/network/plugins/doc_fragments/a10.py | 48 + .../network/plugins/doc_fragments/aireos.py | 58 + .../network/plugins/doc_fragments/aruba.py | 61 + .../community/network/plugins/doc_fragments/avi.py | 100 + .../community/network/plugins/doc_fragments/ce.py | 63 + .../network/plugins/doc_fragments/cnos.py | 81 + .../network/plugins/doc_fragments/enos.py | 93 + .../network/plugins/doc_fragments/ingate.py | 64 + .../network/plugins/doc_fragments/ironware.py | 96 + .../network/plugins/doc_fragments/netscaler.py | 68 + .../community/network/plugins/doc_fragments/nso.py | 36 + .../network/plugins/doc_fragments/panos.py | 246 ++ .../network/plugins/doc_fragments/sros.py | 64 + .../community/network/plugins/filter/__init__.py | 0 .../community/network/plugins/httpapi/__init__.py | 0 .../community/network/plugins/httpapi/exos.py | 252 ++ .../network/plugins/httpapi/fortianalyzer.py | 453 +++ .../network/plugins/httpapi/fortimanager.py | 459 +++ .../community/network/plugins/httpapi/ftd.py | 395 +++ .../network/plugins/inventory/__init__.py | 0 .../community/network/plugins/lookup/__init__.py | 0 .../community/network/plugins/lookup/avi.py | 129 + .../network/plugins/module_utils/__init__.py | 0 .../plugins/module_utils/network/__init__.py | 0 .../plugins/module_utils/network/a10/__init__.py | 0 .../plugins/module_utils/network/a10/a10.py | 156 + .../module_utils/network/aireos/__init__.py | 0 .../plugins/module_utils/network/aireos/aireos.py | 139 + .../plugins/module_utils/network/aos/__init__.py | 0 .../plugins/module_utils/network/aos/aos.py | 183 ++ .../module_utils/network/apconos/__init__.py | 0 .../module_utils/network/apconos/apconos.py | 113 + .../plugins/module_utils/network/aruba/__init__.py | 0 .../plugins/module_utils/network/aruba/aruba.py | 141 + .../plugins/module_utils/network/avi/__init__.py | 0 .../module_utils/network/avi/ansible_utils.py | 574 ++++ .../plugins/module_utils/network/avi/avi.py | 39 + .../plugins/module_utils/network/avi/avi_api.py | 974 ++++++ .../module_utils/network/bigswitch/__init__.py | 0 .../module_utils/network/bigswitch/bigswitch.py | 94 + .../module_utils/network/checkpoint/__init__.py | 0 .../module_utils/network/cloudengine/__init__.py | 0 .../plugins/module_utils/network/cloudengine/ce.py | 427 +++ .../plugins/module_utils/network/cnos/__init__.py | 0 .../plugins/module_utils/network/cnos/cnos.py | 664 ++++ .../module_utils/network/cnos/cnos_devicerules.py | 1924 +++++++++++ .../module_utils/network/cnos/cnos_errorcodes.py | 259 ++ .../module_utils/network/edgeos/__init__.py | 0 .../plugins/module_utils/network/edgeos/edgeos.py | 136 + .../module_utils/network/edgeswitch/__init__.py | 0 .../module_utils/network/edgeswitch/edgeswitch.py | 172 + .../network/edgeswitch/edgeswitch_interface.py | 94 + .../plugins/module_utils/network/enos/__init__.py | 0 .../plugins/module_utils/network/enos/enos.py | 176 + .../module_utils/network/eric_eccli/__init__.py | 0 .../module_utils/network/eric_eccli/eric_eccli.py | 49 + .../plugins/module_utils/network/exos/__init__.py | 0 .../module_utils/network/exos/argspec/__init__.py | 0 .../network/exos/argspec/facts/__init__.py | 0 .../network/exos/argspec/facts/facts.py | 23 + .../network/exos/argspec/l2_interfaces/__init__.py | 0 .../exos/argspec/l2_interfaces/l2_interfaces.py | 49 + .../network/exos/argspec/lldp_global/__init__.py | 0 .../exos/argspec/lldp_global/lldp_global.py | 57 + .../exos/argspec/lldp_interfaces/__init__.py | 0 .../argspec/lldp_interfaces/lldp_interfaces.py | 49 + .../network/exos/argspec/vlans/__init__.py | 0 .../network/exos/argspec/vlans/vlans.py | 53 + .../module_utils/network/exos/config/__init__.py | 0 .../network/exos/config/l2_interfaces/__init__.py | 0 .../exos/config/l2_interfaces/l2_interfaces.py | 294 ++ .../network/exos/config/lldp_global/__init__.py | 0 .../network/exos/config/lldp_global/lldp_global.py | 199 ++ .../exos/config/lldp_interfaces/__init__.py | 0 .../exos/config/lldp_interfaces/lldp_interfaces.py | 243 ++ .../network/exos/config/vlans/__init__.py | 0 .../network/exos/config/vlans/vlans.py | 277 ++ .../plugins/module_utils/network/exos/exos.py | 222 ++ .../module_utils/network/exos/facts/__init__.py | 0 .../module_utils/network/exos/facts/facts.py | 61 + .../network/exos/facts/l2_interfaces/__init__.py | 0 .../exos/facts/l2_interfaces/l2_interfaces.py | 92 + .../network/exos/facts/legacy/__init__.py | 0 .../module_utils/network/exos/facts/legacy/base.py | 263 ++ .../network/exos/facts/lldp_global/__init__.py | 0 .../network/exos/facts/lldp_global/lldp_global.py | 97 + .../network/exos/facts/lldp_interfaces/__init__.py | 0 .../exos/facts/lldp_interfaces/lldp_interfaces.py | 88 + .../network/exos/facts/vlans/__init__.py | 0 .../module_utils/network/exos/facts/vlans/vlans.py | 89 + .../module_utils/network/exos/utils/__init__.py | 0 .../module_utils/network/exos/utils/utils.py | 9 + .../module_utils/network/fortianalyzer/__init__.py | 0 .../module_utils/network/fortianalyzer/common.py | 291 ++ .../network/fortianalyzer/fortianalyzer.py | 476 +++ .../plugins/module_utils/network/ftd/__init__.py | 0 .../plugins/module_utils/network/ftd/common.py | 242 ++ .../module_utils/network/ftd/configuration.py | 569 ++++ .../plugins/module_utils/network/ftd/device.py | 141 + .../module_utils/network/ftd/fdm_swagger_client.py | 641 ++++ .../plugins/module_utils/network/ftd/operation.py | 44 + .../plugins/module_utils/network/icx/__init__.py | 0 .../plugins/module_utils/network/icx/icx.py | 69 + .../module_utils/network/ingate/__init__.py | 0 .../plugins/module_utils/network/ingate/common.py | 69 + .../module_utils/network/ironware/__init__.py | 0 .../module_utils/network/ironware/ironware.py | 114 + .../module_utils/network/netscaler/__init__.py | 0 .../module_utils/network/netscaler/netscaler.py | 325 ++ .../module_utils/network/netvisor/__init__.py | 0 .../module_utils/network/netvisor/netvisor.py | 59 + .../module_utils/network/netvisor/pn_nvos.py | 66 + .../plugins/module_utils/network/nos/__init__.py | 0 .../plugins/module_utils/network/nos/nos.py | 164 + .../plugins/module_utils/network/nso/__init__.py | 0 .../plugins/module_utils/network/nso/nso.py | 825 +++++ .../module_utils/network/ordnance/__init__.py | 0 .../module_utils/network/ordnance/ordnance.py | 22 + .../plugins/module_utils/network/panos/__init__.py | 0 .../plugins/module_utils/network/panos/panos.py | 418 +++ .../module_utils/network/routeros/__init__.py | 0 .../module_utils/network/routeros/routeros.py | 163 + .../plugins/module_utils/network/slxos/__init__.py | 0 .../plugins/module_utils/network/slxos/slxos.py | 152 + .../plugins/module_utils/network/sros/__init__.py | 0 .../plugins/module_utils/network/sros/sros.py | 122 + .../plugins/module_utils/network/voss/__init__.py | 0 .../plugins/module_utils/network/voss/voss.py | 223 ++ .../community/network/plugins/modules/__init__.py | 0 .../network/plugins/modules/a10_server.py | 279 ++ .../network/plugins/modules/a10_server_axapi3.py | 239 ++ .../network/plugins/modules/a10_service_group.py | 332 ++ .../network/plugins/modules/a10_virtual_server.py | 278 ++ .../network/plugins/modules/aireos_command.py | 214 ++ .../network/plugins/modules/aireos_config.py | 354 ++ .../network/plugins/modules/apconos_command.py | 196 ++ .../network/plugins/modules/aruba_command.py | 213 ++ .../network/plugins/modules/aruba_config.py | 420 +++ .../plugins/modules/avi_actiongroupconfig.py | 151 + .../network/plugins/modules/avi_alertconfig.py | 225 ++ .../plugins/modules/avi_alertemailconfig.py | 120 + .../plugins/modules/avi_alertscriptconfig.py | 113 + .../plugins/modules/avi_alertsyslogconfig.py | 119 + .../plugins/modules/avi_analyticsprofile.py | 610 ++++ .../network/plugins/modules/avi_api_session.py | 256 ++ .../network/plugins/modules/avi_api_version.py | 92 + .../modules/avi_applicationpersistenceprofile.py | 164 + .../plugins/modules/avi_applicationprofile.py | 217 ++ .../network/plugins/modules/avi_authprofile.py | 164 + .../plugins/modules/avi_autoscalelaunchconfig.py | 132 + .../network/plugins/modules/avi_backup.py | 130 + .../plugins/modules/avi_backupconfiguration.py | 166 + .../modules/avi_certificatemanagementprofile.py | 117 + .../community/network/plugins/modules/avi_cloud.py | 287 ++ .../plugins/modules/avi_cloudconnectoruser.py | 143 + .../network/plugins/modules/avi_cloudproperties.py | 117 + .../network/plugins/modules/avi_cluster.py | 122 + .../plugins/modules/avi_clusterclouddetails.py | 113 + .../plugins/modules/avi_controllerproperties.py | 420 +++ .../plugins/modules/avi_customipamdnsprofile.py | 120 + .../network/plugins/modules/avi_dnspolicy.py | 125 + .../network/plugins/modules/avi_errorpagebody.py | 120 + .../plugins/modules/avi_errorpageprofile.py | 134 + .../community/network/plugins/modules/avi_gslb.py | 353 ++ .../plugins/modules/avi_gslbgeodbprofile.py | 128 + .../network/plugins/modules/avi_gslbservice.py | 229 ++ .../modules/avi_gslbservice_patch_member.py | 292 ++ .../modules/avi_hardwaresecuritymodulegroup.py | 112 + .../network/plugins/modules/avi_healthmonitor.py | 204 ++ .../network/plugins/modules/avi_httppolicyset.py | 168 + .../network/plugins/modules/avi_ipaddrgroup.py | 158 + .../plugins/modules/avi_ipamdnsproviderprofile.py | 179 + .../network/plugins/modules/avi_l4policyset.py | 130 + .../plugins/modules/avi_microservicegroup.py | 121 + .../network/plugins/modules/avi_network.py | 155 + .../network/plugins/modules/avi_networkprofile.py | 131 + .../plugins/modules/avi_networksecuritypolicy.py | 136 + .../network/plugins/modules/avi_pkiprofile.py | 149 + .../community/network/plugins/modules/avi_pool.py | 497 +++ .../network/plugins/modules/avi_poolgroup.py | 166 + .../modules/avi_poolgroupdeploymentpolicy.py | 153 + .../network/plugins/modules/avi_prioritylabels.py | 119 + .../community/network/plugins/modules/avi_role.py | 112 + .../network/plugins/modules/avi_scheduler.py | 153 + .../network/plugins/modules/avi_seproperties.py | 112 + .../plugins/modules/avi_serverautoscalepolicy.py | 179 + .../network/plugins/modules/avi_serviceengine.py | 170 + .../plugins/modules/avi_serviceenginegroup.py | 1075 ++++++ .../network/plugins/modules/avi_snmptrapprofile.py | 111 + .../plugins/modules/avi_sslkeyandcertificate.py | 196 ++ .../network/plugins/modules/avi_sslprofile.py | 208 ++ .../network/plugins/modules/avi_stringgroup.py | 134 + .../plugins/modules/avi_systemconfiguration.py | 181 ++ .../network/plugins/modules/avi_tenant.py | 127 + .../plugins/modules/avi_trafficcloneprofile.py | 126 + .../community/network/plugins/modules/avi_user.py | 189 ++ .../network/plugins/modules/avi_useraccount.py | 152 + .../plugins/modules/avi_useraccountprofile.py | 134 + .../network/plugins/modules/avi_virtualservice.py | 652 ++++ .../network/plugins/modules/avi_vrfcontext.py | 144 + .../network/plugins/modules/avi_vsdatascriptset.py | 147 + .../community/network/plugins/modules/avi_vsvip.py | 154 + .../network/plugins/modules/avi_webhook.py | 124 + .../network/plugins/modules/bcf_switch.py | 157 + .../network/plugins/modules/bigmon_chain.py | 132 + .../network/plugins/modules/bigmon_policy.py | 183 ++ .../network/plugins/modules/ce_aaa_server.py | 2176 +++++++++++++ .../network/plugins/modules/ce_aaa_server_host.py | 2636 +++++++++++++++ .../community/network/plugins/modules/ce_acl.py | 1000 ++++++ .../network/plugins/modules/ce_acl_advance.py | 1746 ++++++++++ .../network/plugins/modules/ce_acl_interface.py | 323 ++ .../network/plugins/modules/ce_bfd_global.py | 554 ++++ .../network/plugins/modules/ce_bfd_session.py | 654 ++++ .../network/plugins/modules/ce_bfd_view.py | 561 ++++ .../community/network/plugins/modules/ce_bgp.py | 2327 +++++++++++++ .../community/network/plugins/modules/ce_bgp_af.py | 3430 ++++++++++++++++++++ .../network/plugins/modules/ce_bgp_neighbor.py | 2047 ++++++++++++ .../network/plugins/modules/ce_bgp_neighbor_af.py | 2675 +++++++++++++++ .../network/plugins/modules/ce_command.py | 258 ++ .../community/network/plugins/modules/ce_config.py | 492 +++ .../community/network/plugins/modules/ce_dldp.py | 549 ++++ .../network/plugins/modules/ce_dldp_interface.py | 658 ++++ .../network/plugins/modules/ce_eth_trunk.py | 672 ++++ .../network/plugins/modules/ce_evpn_bd_vni.py | 1053 ++++++ .../network/plugins/modules/ce_evpn_bgp.py | 727 +++++ .../network/plugins/modules/ce_evpn_bgp_rr.py | 527 +++ .../network/plugins/modules/ce_evpn_global.py | 236 ++ .../community/network/plugins/modules/ce_facts.py | 414 +++ .../network/plugins/modules/ce_file_copy.py | 412 +++ .../plugins/modules/ce_info_center_debug.py | 613 ++++ .../plugins/modules/ce_info_center_global.py | 1721 ++++++++++ .../network/plugins/modules/ce_info_center_log.py | 544 ++++ .../network/plugins/modules/ce_info_center_trap.py | 693 ++++ .../network/plugins/modules/ce_interface.py | 891 +++++ .../network/plugins/modules/ce_interface_ospf.py | 793 +++++ .../network/plugins/modules/ce_ip_interface.py | 735 +++++ .../network/plugins/modules/ce_is_is_instance.py | 327 ++ .../network/plugins/modules/ce_is_is_interface.py | 785 +++++ .../network/plugins/modules/ce_is_is_view.py | 1952 +++++++++++ .../community/network/plugins/modules/ce_lacp.py | 489 +++ .../network/plugins/modules/ce_link_status.py | 564 ++++ .../community/network/plugins/modules/ce_lldp.py | 788 +++++ .../network/plugins/modules/ce_lldp_interface.py | 1381 ++++++++ .../network/plugins/modules/ce_mdn_interface.py | 399 +++ .../network/plugins/modules/ce_mlag_config.py | 912 ++++++ .../network/plugins/modules/ce_mlag_interface.py | 1038 ++++++ .../community/network/plugins/modules/ce_mtu.py | 581 ++++ .../network/plugins/modules/ce_multicast_global.py | 286 ++ .../plugins/modules/ce_multicast_igmp_enable.py | 543 ++++ .../network/plugins/modules/ce_netconf.py | 202 ++ .../network/plugins/modules/ce_netstream_aging.py | 516 +++ .../network/plugins/modules/ce_netstream_export.py | 557 ++++ .../network/plugins/modules/ce_netstream_global.py | 942 ++++++ .../plugins/modules/ce_netstream_template.py | 494 +++ .../community/network/plugins/modules/ce_ntp.py | 615 ++++ .../network/plugins/modules/ce_ntp_auth.py | 516 +++ .../community/network/plugins/modules/ce_ospf.py | 968 ++++++ .../network/plugins/modules/ce_ospf_vrf.py | 1619 +++++++++ .../community/network/plugins/modules/ce_reboot.py | 165 + .../network/plugins/modules/ce_rollback.py | 449 +++ .../community/network/plugins/modules/ce_sflow.py | 1169 +++++++ .../network/plugins/modules/ce_snmp_community.py | 975 ++++++ .../network/plugins/modules/ce_snmp_contact.py | 268 ++ .../network/plugins/modules/ce_snmp_location.py | 269 ++ .../network/plugins/modules/ce_snmp_target_host.py | 940 ++++++ .../network/plugins/modules/ce_snmp_traps.py | 559 ++++ .../network/plugins/modules/ce_snmp_user.py | 1044 ++++++ .../network/plugins/modules/ce_startup.py | 465 +++ .../network/plugins/modules/ce_static_route.py | 829 +++++ .../network/plugins/modules/ce_static_route_bfd.py | 1593 +++++++++ .../community/network/plugins/modules/ce_stp.py | 969 ++++++ .../network/plugins/modules/ce_switchport.py | 997 ++++++ .../community/network/plugins/modules/ce_vlan.py | 687 ++++ .../community/network/plugins/modules/ce_vrf.py | 352 ++ .../community/network/plugins/modules/ce_vrf_af.py | 848 +++++ .../network/plugins/modules/ce_vrf_interface.py | 517 +++ .../community/network/plugins/modules/ce_vrrp.py | 1327 ++++++++ .../network/plugins/modules/ce_vxlan_arp.py | 688 ++++ .../network/plugins/modules/ce_vxlan_gateway.py | 936 ++++++ .../network/plugins/modules/ce_vxlan_global.py | 539 +++ .../network/plugins/modules/ce_vxlan_tunnel.py | 940 ++++++ .../network/plugins/modules/ce_vxlan_vap.py | 933 ++++++ .../network/plugins/modules/cnos_backup.py | 271 ++ .../network/plugins/modules/cnos_banner.py | 258 ++ .../community/network/plugins/modules/cnos_bgp.py | 1176 +++++++ .../network/plugins/modules/cnos_command.py | 203 ++ .../plugins/modules/cnos_conditional_command.py | 163 + .../plugins/modules/cnos_conditional_template.py | 183 ++ .../network/plugins/modules/cnos_config.py | 302 ++ .../network/plugins/modules/cnos_factory.py | 111 + .../network/plugins/modules/cnos_facts.py | 535 +++ .../network/plugins/modules/cnos_image.py | 236 ++ .../network/plugins/modules/cnos_interface.py | 550 ++++ .../network/plugins/modules/cnos_l2_interface.py | 593 ++++ .../network/plugins/modules/cnos_l3_interface.py | 457 +++ .../network/plugins/modules/cnos_linkagg.py | 387 +++ .../community/network/plugins/modules/cnos_lldp.py | 135 + .../network/plugins/modules/cnos_logging.py | 421 +++ .../network/plugins/modules/cnos_reload.py | 109 + .../network/plugins/modules/cnos_rollback.py | 280 ++ .../community/network/plugins/modules/cnos_save.py | 111 + .../network/plugins/modules/cnos_showrun.py | 109 + .../network/plugins/modules/cnos_static_route.py | 284 ++ .../network/plugins/modules/cnos_system.py | 383 +++ .../network/plugins/modules/cnos_template.py | 146 + .../community/network/plugins/modules/cnos_user.py | 386 +++ .../community/network/plugins/modules/cnos_vlag.py | 441 +++ .../community/network/plugins/modules/cnos_vlan.py | 405 +++ .../community/network/plugins/modules/cnos_vrf.py | 365 +++ .../network/plugins/modules/cp_publish.py | 73 + .../network/plugins/modules/cv_server_provision.py | 638 ++++ .../network/plugins/modules/dladm_etherstub.py | 165 + .../network/plugins/modules/dladm_iptun.py | 272 ++ .../network/plugins/modules/dladm_linkprop.py | 284 ++ .../network/plugins/modules/dladm_vlan.py | 208 ++ .../network/plugins/modules/dladm_vnic.py | 265 ++ .../network/plugins/modules/edgeos_command.py | 172 + .../network/plugins/modules/edgeos_config.py | 314 ++ .../network/plugins/modules/edgeos_facts.py | 305 ++ .../network/plugins/modules/edgeswitch_facts.py | 283 ++ .../network/plugins/modules/edgeswitch_vlan.py | 493 +++ .../network/plugins/modules/enos_command.py | 223 ++ .../network/plugins/modules/enos_config.py | 305 ++ .../network/plugins/modules/enos_facts.py | 503 +++ .../network/plugins/modules/eric_eccli_command.py | 208 ++ .../network/plugins/modules/exos_command.py | 215 ++ .../network/plugins/modules/exos_config.py | 431 +++ .../network/plugins/modules/exos_facts.py | 184 ++ .../network/plugins/modules/exos_l2_interfaces.py | 1131 +++++++ .../network/plugins/modules/exos_lldp_global.py | 423 +++ .../plugins/modules/exos_lldp_interfaces.py | 674 ++++ .../network/plugins/modules/exos_vlans.py | 753 +++++ .../network/plugins/modules/faz_device.py | 432 +++ .../community/network/plugins/modules/flowadm.py | 508 +++ .../network/plugins/modules/fmgr_device.py | 296 ++ .../network/plugins/modules/fmgr_device_config.py | 231 ++ .../network/plugins/modules/fmgr_device_group.py | 323 ++ .../modules/fmgr_device_provision_template.py | 1546 +++++++++ .../network/plugins/modules/fmgr_fwobj_address.py | 661 ++++ .../network/plugins/modules/fmgr_fwobj_ippool.py | 442 +++ .../network/plugins/modules/fmgr_fwobj_ippool6.py | 223 ++ .../network/plugins/modules/fmgr_fwobj_service.py | 617 ++++ .../network/plugins/modules/fmgr_fwobj_vip.py | 2424 ++++++++++++++ .../network/plugins/modules/fmgr_fwpol_ipv4.py | 1355 ++++++++ .../network/plugins/modules/fmgr_fwpol_package.py | 479 +++ .../community/network/plugins/modules/fmgr_ha.py | 349 ++ .../network/plugins/modules/fmgr_provisioning.py | 360 ++ .../network/plugins/modules/fmgr_query.py | 424 +++ .../network/plugins/modules/fmgr_script.py | 262 ++ .../plugins/modules/fmgr_secprof_appctrl.py | 516 +++ .../network/plugins/modules/fmgr_secprof_av.py | 1386 ++++++++ .../network/plugins/modules/fmgr_secprof_dns.py | 339 ++ .../network/plugins/modules/fmgr_secprof_ips.py | 664 ++++ .../plugins/modules/fmgr_secprof_profile_group.py | 287 ++ .../network/plugins/modules/fmgr_secprof_proxy.py | 332 ++ .../network/plugins/modules/fmgr_secprof_spam.py | 607 ++++ .../plugins/modules/fmgr_secprof_ssl_ssh.py | 954 ++++++ .../network/plugins/modules/fmgr_secprof_voip.py | 1198 +++++++ .../network/plugins/modules/fmgr_secprof_waf.py | 1477 +++++++++ .../network/plugins/modules/fmgr_secprof_wanopt.py | 685 ++++ .../network/plugins/modules/fmgr_secprof_web.py | 1081 ++++++ .../network/plugins/modules/ftd_configuration.py | 135 + .../network/plugins/modules/ftd_file_download.py | 127 + .../network/plugins/modules/ftd_file_upload.py | 103 + .../network/plugins/modules/ftd_install.py | 290 ++ .../network/plugins/modules/iap_start_workflow.py | 179 + .../community/network/plugins/modules/iap_token.py | 137 + .../network/plugins/modules/icx_banner.py | 210 ++ .../network/plugins/modules/icx_command.py | 227 ++ .../network/plugins/modules/icx_config.py | 479 +++ .../community/network/plugins/modules/icx_copy.py | 367 +++ .../community/network/plugins/modules/icx_facts.py | 544 ++++ .../network/plugins/modules/icx_interface.py | 688 ++++ .../network/plugins/modules/icx_l3_interface.py | 434 +++ .../network/plugins/modules/icx_linkagg.py | 322 ++ .../community/network/plugins/modules/icx_lldp.py | 178 + .../network/plugins/modules/icx_logging.py | 576 ++++ .../community/network/plugins/modules/icx_ping.py | 264 ++ .../network/plugins/modules/icx_static_route.py | 310 ++ .../network/plugins/modules/icx_system.py | 466 +++ .../community/network/plugins/modules/icx_user.py | 387 +++ .../community/network/plugins/modules/icx_vlan.py | 779 +++++ .../community/network/plugins/modules/ig_config.py | 564 ++++ .../network/plugins/modules/ig_unit_information.py | 156 + .../network/plugins/modules/ipadm_addr.py | 398 +++ .../network/plugins/modules/ipadm_addrprop.py | 254 ++ .../community/network/plugins/modules/ipadm_if.py | 216 ++ .../network/plugins/modules/ipadm_ifprop.py | 282 ++ .../network/plugins/modules/ipadm_prop.py | 261 ++ .../network/plugins/modules/ironware_command.py | 170 + .../network/plugins/modules/ironware_config.py | 287 ++ .../network/plugins/modules/ironware_facts.py | 647 ++++ .../community/network/plugins/modules/nclu.py | 250 ++ .../network/plugins/modules/netact_cm_command.py | 360 ++ .../network/plugins/modules/netscaler_cs_action.py | 283 ++ .../network/plugins/modules/netscaler_cs_policy.py | 283 ++ .../plugins/modules/netscaler_cs_vserver.py | 1302 ++++++++ .../plugins/modules/netscaler_gslb_service.py | 691 ++++ .../network/plugins/modules/netscaler_gslb_site.py | 419 +++ .../plugins/modules/netscaler_gslb_vserver.py | 951 ++++++ .../plugins/modules/netscaler_lb_monitor.py | 1376 ++++++++ .../plugins/modules/netscaler_lb_vserver.py | 1936 +++++++++++ .../plugins/modules/netscaler_nitro_request.py | 902 +++++ .../plugins/modules/netscaler_save_config.py | 172 + .../network/plugins/modules/netscaler_server.py | 398 +++ .../network/plugins/modules/netscaler_service.py | 959 ++++++ .../plugins/modules/netscaler_servicegroup.py | 1041 ++++++ .../plugins/modules/netscaler_ssl_certkey.py | 367 +++ .../plugins/modules/network/a10/a10_server.py | 279 ++ .../modules/network/a10/a10_server_axapi3.py | 239 ++ .../modules/network/a10/a10_service_group.py | 332 ++ .../modules/network/a10/a10_virtual_server.py | 278 ++ .../modules/network/aireos/aireos_command.py | 214 ++ .../modules/network/aireos/aireos_config.py | 354 ++ .../modules/network/apconos/apconos_command.py | 196 ++ .../plugins/modules/network/aruba/aruba_command.py | 213 ++ .../plugins/modules/network/aruba/aruba_config.py | 420 +++ .../modules/network/avi/avi_actiongroupconfig.py | 151 + .../plugins/modules/network/avi/avi_alertconfig.py | 225 ++ .../modules/network/avi/avi_alertemailconfig.py | 120 + .../modules/network/avi/avi_alertscriptconfig.py | 113 + .../modules/network/avi/avi_alertsyslogconfig.py | 119 + .../modules/network/avi/avi_analyticsprofile.py | 610 ++++ .../plugins/modules/network/avi/avi_api_session.py | 256 ++ .../plugins/modules/network/avi/avi_api_version.py | 92 + .../avi/avi_applicationpersistenceprofile.py | 164 + .../modules/network/avi/avi_applicationprofile.py | 217 ++ .../plugins/modules/network/avi/avi_authprofile.py | 164 + .../network/avi/avi_autoscalelaunchconfig.py | 132 + .../plugins/modules/network/avi/avi_backup.py | 130 + .../modules/network/avi/avi_backupconfiguration.py | 166 + .../avi/avi_certificatemanagementprofile.py | 117 + .../plugins/modules/network/avi/avi_cloud.py | 287 ++ .../modules/network/avi/avi_cloudconnectoruser.py | 143 + .../modules/network/avi/avi_cloudproperties.py | 117 + .../plugins/modules/network/avi/avi_cluster.py | 122 + .../modules/network/avi/avi_clusterclouddetails.py | 113 + .../network/avi/avi_controllerproperties.py | 420 +++ .../network/avi/avi_customipamdnsprofile.py | 120 + .../plugins/modules/network/avi/avi_dnspolicy.py | 125 + .../modules/network/avi/avi_errorpagebody.py | 120 + .../modules/network/avi/avi_errorpageprofile.py | 134 + .../plugins/modules/network/avi/avi_gslb.py | 353 ++ .../modules/network/avi/avi_gslbgeodbprofile.py | 128 + .../plugins/modules/network/avi/avi_gslbservice.py | 229 ++ .../network/avi/avi_gslbservice_patch_member.py | 292 ++ .../network/avi/avi_hardwaresecuritymodulegroup.py | 112 + .../modules/network/avi/avi_healthmonitor.py | 204 ++ .../modules/network/avi/avi_httppolicyset.py | 168 + .../plugins/modules/network/avi/avi_ipaddrgroup.py | 158 + .../network/avi/avi_ipamdnsproviderprofile.py | 179 + .../plugins/modules/network/avi/avi_l4policyset.py | 130 + .../modules/network/avi/avi_microservicegroup.py | 121 + .../plugins/modules/network/avi/avi_network.py | 155 + .../modules/network/avi/avi_networkprofile.py | 131 + .../network/avi/avi_networksecuritypolicy.py | 136 + .../plugins/modules/network/avi/avi_pkiprofile.py | 149 + .../plugins/modules/network/avi/avi_pool.py | 497 +++ .../plugins/modules/network/avi/avi_poolgroup.py | 166 + .../network/avi/avi_poolgroupdeploymentpolicy.py | 153 + .../modules/network/avi/avi_prioritylabels.py | 119 + .../plugins/modules/network/avi/avi_role.py | 112 + .../plugins/modules/network/avi/avi_scheduler.py | 153 + .../modules/network/avi/avi_seproperties.py | 112 + .../network/avi/avi_serverautoscalepolicy.py | 179 + .../modules/network/avi/avi_serviceengine.py | 170 + .../modules/network/avi/avi_serviceenginegroup.py | 1075 ++++++ .../modules/network/avi/avi_snmptrapprofile.py | 111 + .../network/avi/avi_sslkeyandcertificate.py | 196 ++ .../plugins/modules/network/avi/avi_sslprofile.py | 208 ++ .../plugins/modules/network/avi/avi_stringgroup.py | 134 + .../modules/network/avi/avi_systemconfiguration.py | 181 ++ .../plugins/modules/network/avi/avi_tenant.py | 127 + .../modules/network/avi/avi_trafficcloneprofile.py | 126 + .../plugins/modules/network/avi/avi_user.py | 189 ++ .../plugins/modules/network/avi/avi_useraccount.py | 152 + .../modules/network/avi/avi_useraccountprofile.py | 134 + .../modules/network/avi/avi_virtualservice.py | 652 ++++ .../plugins/modules/network/avi/avi_vrfcontext.py | 144 + .../modules/network/avi/avi_vsdatascriptset.py | 147 + .../plugins/modules/network/avi/avi_vsvip.py | 154 + .../plugins/modules/network/avi/avi_webhook.py | 124 + .../modules/network/bigswitch/bcf_switch.py | 157 + .../modules/network/bigswitch/bigmon_chain.py | 132 + .../modules/network/bigswitch/bigmon_policy.py | 183 ++ .../modules/network/check_point/cp_publish.py | 73 + .../modules/network/cloudengine/ce_aaa_server.py | 2176 +++++++++++++ .../network/cloudengine/ce_aaa_server_host.py | 2636 +++++++++++++++ .../plugins/modules/network/cloudengine/ce_acl.py | 1000 ++++++ .../modules/network/cloudengine/ce_acl_advance.py | 1746 ++++++++++ .../network/cloudengine/ce_acl_interface.py | 323 ++ .../modules/network/cloudengine/ce_bfd_global.py | 554 ++++ .../modules/network/cloudengine/ce_bfd_session.py | 654 ++++ .../modules/network/cloudengine/ce_bfd_view.py | 561 ++++ .../plugins/modules/network/cloudengine/ce_bgp.py | 2327 +++++++++++++ .../modules/network/cloudengine/ce_bgp_af.py | 3430 ++++++++++++++++++++ .../modules/network/cloudengine/ce_bgp_neighbor.py | 2047 ++++++++++++ .../network/cloudengine/ce_bgp_neighbor_af.py | 2675 +++++++++++++++ .../modules/network/cloudengine/ce_command.py | 258 ++ .../modules/network/cloudengine/ce_config.py | 492 +++ .../plugins/modules/network/cloudengine/ce_dldp.py | 549 ++++ .../network/cloudengine/ce_dldp_interface.py | 658 ++++ .../modules/network/cloudengine/ce_eth_trunk.py | 672 ++++ .../modules/network/cloudengine/ce_evpn_bd_vni.py | 1053 ++++++ .../modules/network/cloudengine/ce_evpn_bgp.py | 727 +++++ .../modules/network/cloudengine/ce_evpn_bgp_rr.py | 527 +++ .../modules/network/cloudengine/ce_evpn_global.py | 236 ++ .../modules/network/cloudengine/ce_facts.py | 414 +++ .../modules/network/cloudengine/ce_file_copy.py | 412 +++ .../network/cloudengine/ce_info_center_debug.py | 613 ++++ .../network/cloudengine/ce_info_center_global.py | 1721 ++++++++++ .../network/cloudengine/ce_info_center_log.py | 544 ++++ .../network/cloudengine/ce_info_center_trap.py | 693 ++++ .../modules/network/cloudengine/ce_interface.py | 891 +++++ .../network/cloudengine/ce_interface_ospf.py | 793 +++++ .../modules/network/cloudengine/ce_ip_interface.py | 735 +++++ .../network/cloudengine/ce_is_is_instance.py | 327 ++ .../network/cloudengine/ce_is_is_interface.py | 785 +++++ .../modules/network/cloudengine/ce_is_is_view.py | 1952 +++++++++++ .../plugins/modules/network/cloudengine/ce_lacp.py | 489 +++ .../modules/network/cloudengine/ce_link_status.py | 564 ++++ .../plugins/modules/network/cloudengine/ce_lldp.py | 788 +++++ .../network/cloudengine/ce_lldp_interface.py | 1381 ++++++++ .../network/cloudengine/ce_mdn_interface.py | 399 +++ .../modules/network/cloudengine/ce_mlag_config.py | 912 ++++++ .../network/cloudengine/ce_mlag_interface.py | 1038 ++++++ .../plugins/modules/network/cloudengine/ce_mtu.py | 581 ++++ .../network/cloudengine/ce_multicast_global.py | 286 ++ .../cloudengine/ce_multicast_igmp_enable.py | 543 ++++ .../modules/network/cloudengine/ce_netconf.py | 202 ++ .../network/cloudengine/ce_netstream_aging.py | 516 +++ .../network/cloudengine/ce_netstream_export.py | 557 ++++ .../network/cloudengine/ce_netstream_global.py | 942 ++++++ .../network/cloudengine/ce_netstream_template.py | 494 +++ .../plugins/modules/network/cloudengine/ce_ntp.py | 615 ++++ .../modules/network/cloudengine/ce_ntp_auth.py | 516 +++ .../plugins/modules/network/cloudengine/ce_ospf.py | 968 ++++++ .../modules/network/cloudengine/ce_ospf_vrf.py | 1619 +++++++++ .../modules/network/cloudengine/ce_reboot.py | 165 + .../modules/network/cloudengine/ce_rollback.py | 449 +++ .../modules/network/cloudengine/ce_sflow.py | 1169 +++++++ .../network/cloudengine/ce_snmp_community.py | 975 ++++++ .../modules/network/cloudengine/ce_snmp_contact.py | 268 ++ .../network/cloudengine/ce_snmp_location.py | 269 ++ .../network/cloudengine/ce_snmp_target_host.py | 940 ++++++ .../modules/network/cloudengine/ce_snmp_traps.py | 559 ++++ .../modules/network/cloudengine/ce_snmp_user.py | 1044 ++++++ .../modules/network/cloudengine/ce_startup.py | 465 +++ .../modules/network/cloudengine/ce_static_route.py | 829 +++++ .../network/cloudengine/ce_static_route_bfd.py | 1593 +++++++++ .../plugins/modules/network/cloudengine/ce_stp.py | 969 ++++++ .../modules/network/cloudengine/ce_switchport.py | 997 ++++++ .../plugins/modules/network/cloudengine/ce_vlan.py | 687 ++++ .../plugins/modules/network/cloudengine/ce_vrf.py | 352 ++ .../modules/network/cloudengine/ce_vrf_af.py | 848 +++++ .../network/cloudengine/ce_vrf_interface.py | 517 +++ .../plugins/modules/network/cloudengine/ce_vrrp.py | 1327 ++++++++ .../modules/network/cloudengine/ce_vxlan_arp.py | 688 ++++ .../network/cloudengine/ce_vxlan_gateway.py | 936 ++++++ .../modules/network/cloudengine/ce_vxlan_global.py | 539 +++ .../modules/network/cloudengine/ce_vxlan_tunnel.py | 940 ++++++ .../modules/network/cloudengine/ce_vxlan_vap.py | 933 ++++++ .../network/cloudvision/cv_server_provision.py | 638 ++++ .../plugins/modules/network/cnos/cnos_backup.py | 271 ++ .../plugins/modules/network/cnos/cnos_banner.py | 258 ++ .../plugins/modules/network/cnos/cnos_bgp.py | 1176 +++++++ .../plugins/modules/network/cnos/cnos_command.py | 203 ++ .../network/cnos/cnos_conditional_command.py | 163 + .../network/cnos/cnos_conditional_template.py | 183 ++ .../plugins/modules/network/cnos/cnos_config.py | 302 ++ .../plugins/modules/network/cnos/cnos_factory.py | 111 + .../plugins/modules/network/cnos/cnos_facts.py | 535 +++ .../plugins/modules/network/cnos/cnos_image.py | 236 ++ .../plugins/modules/network/cnos/cnos_interface.py | 550 ++++ .../modules/network/cnos/cnos_l2_interface.py | 593 ++++ .../modules/network/cnos/cnos_l3_interface.py | 457 +++ .../plugins/modules/network/cnos/cnos_linkagg.py | 387 +++ .../plugins/modules/network/cnos/cnos_lldp.py | 135 + .../plugins/modules/network/cnos/cnos_logging.py | 421 +++ .../plugins/modules/network/cnos/cnos_reload.py | 109 + .../plugins/modules/network/cnos/cnos_rollback.py | 280 ++ .../plugins/modules/network/cnos/cnos_save.py | 111 + .../plugins/modules/network/cnos/cnos_showrun.py | 109 + .../modules/network/cnos/cnos_static_route.py | 284 ++ .../plugins/modules/network/cnos/cnos_system.py | 383 +++ .../plugins/modules/network/cnos/cnos_template.py | 146 + .../plugins/modules/network/cnos/cnos_user.py | 386 +++ .../plugins/modules/network/cnos/cnos_vlag.py | 441 +++ .../plugins/modules/network/cnos/cnos_vlan.py | 405 +++ .../plugins/modules/network/cnos/cnos_vrf.py | 365 +++ .../plugins/modules/network/cumulus/nclu.py | 250 ++ .../modules/network/edgeos/edgeos_command.py | 172 + .../modules/network/edgeos/edgeos_config.py | 314 ++ .../plugins/modules/network/edgeos/edgeos_facts.py | 305 ++ .../modules/network/edgeswitch/edgeswitch_facts.py | 283 ++ .../modules/network/edgeswitch/edgeswitch_vlan.py | 493 +++ .../plugins/modules/network/enos/enos_command.py | 223 ++ .../plugins/modules/network/enos/enos_config.py | 305 ++ .../plugins/modules/network/enos/enos_facts.py | 503 +++ .../network/eric_eccli/eric_eccli_command.py | 208 ++ .../plugins/modules/network/exos/exos_command.py | 215 ++ .../plugins/modules/network/exos/exos_config.py | 431 +++ .../plugins/modules/network/exos/exos_facts.py | 184 ++ .../modules/network/exos/exos_l2_interfaces.py | 1131 +++++++ .../modules/network/exos/exos_lldp_global.py | 423 +++ .../modules/network/exos/exos_lldp_interfaces.py | 674 ++++ .../plugins/modules/network/exos/exos_vlans.py | 753 +++++ .../modules/network/fortianalyzer/faz_device.py | 432 +++ .../modules/network/fortimanager/fmgr_device.py | 296 ++ .../network/fortimanager/fmgr_device_config.py | 231 ++ .../network/fortimanager/fmgr_device_group.py | 323 ++ .../fortimanager/fmgr_device_provision_template.py | 1546 +++++++++ .../network/fortimanager/fmgr_fwobj_address.py | 661 ++++ .../network/fortimanager/fmgr_fwobj_ippool.py | 442 +++ .../network/fortimanager/fmgr_fwobj_ippool6.py | 223 ++ .../network/fortimanager/fmgr_fwobj_service.py | 617 ++++ .../modules/network/fortimanager/fmgr_fwobj_vip.py | 2424 ++++++++++++++ .../network/fortimanager/fmgr_fwpol_ipv4.py | 1355 ++++++++ .../network/fortimanager/fmgr_fwpol_package.py | 479 +++ .../modules/network/fortimanager/fmgr_ha.py | 349 ++ .../network/fortimanager/fmgr_provisioning.py | 360 ++ .../modules/network/fortimanager/fmgr_query.py | 424 +++ .../modules/network/fortimanager/fmgr_script.py | 262 ++ .../network/fortimanager/fmgr_secprof_appctrl.py | 516 +++ .../network/fortimanager/fmgr_secprof_av.py | 1386 ++++++++ .../network/fortimanager/fmgr_secprof_dns.py | 339 ++ .../network/fortimanager/fmgr_secprof_ips.py | 664 ++++ .../fortimanager/fmgr_secprof_profile_group.py | 287 ++ .../network/fortimanager/fmgr_secprof_proxy.py | 332 ++ .../network/fortimanager/fmgr_secprof_spam.py | 607 ++++ .../network/fortimanager/fmgr_secprof_ssl_ssh.py | 954 ++++++ .../network/fortimanager/fmgr_secprof_voip.py | 1198 +++++++ .../network/fortimanager/fmgr_secprof_waf.py | 1477 +++++++++ .../network/fortimanager/fmgr_secprof_wanopt.py | 685 ++++ .../network/fortimanager/fmgr_secprof_web.py | 1081 ++++++ .../modules/network/ftd/ftd_configuration.py | 135 + .../modules/network/ftd/ftd_file_download.py | 127 + .../plugins/modules/network/ftd/ftd_file_upload.py | 103 + .../plugins/modules/network/ftd/ftd_install.py | 290 ++ .../plugins/modules/network/icx/icx_banner.py | 210 ++ .../plugins/modules/network/icx/icx_command.py | 227 ++ .../plugins/modules/network/icx/icx_config.py | 479 +++ .../plugins/modules/network/icx/icx_copy.py | 367 +++ .../plugins/modules/network/icx/icx_facts.py | 544 ++++ .../plugins/modules/network/icx/icx_interface.py | 688 ++++ .../modules/network/icx/icx_l3_interface.py | 434 +++ .../plugins/modules/network/icx/icx_linkagg.py | 322 ++ .../plugins/modules/network/icx/icx_lldp.py | 178 + .../plugins/modules/network/icx/icx_logging.py | 576 ++++ .../plugins/modules/network/icx/icx_ping.py | 264 ++ .../modules/network/icx/icx_static_route.py | 310 ++ .../plugins/modules/network/icx/icx_system.py | 466 +++ .../plugins/modules/network/icx/icx_user.py | 387 +++ .../plugins/modules/network/icx/icx_vlan.py | 779 +++++ .../modules/network/illumos/dladm_etherstub.py | 165 + .../plugins/modules/network/illumos/dladm_iptun.py | 272 ++ .../modules/network/illumos/dladm_linkprop.py | 284 ++ .../plugins/modules/network/illumos/dladm_vlan.py | 208 ++ .../plugins/modules/network/illumos/dladm_vnic.py | 265 ++ .../plugins/modules/network/illumos/flowadm.py | 508 +++ .../plugins/modules/network/illumos/ipadm_addr.py | 398 +++ .../modules/network/illumos/ipadm_addrprop.py | 254 ++ .../plugins/modules/network/illumos/ipadm_if.py | 216 ++ .../modules/network/illumos/ipadm_ifprop.py | 282 ++ .../plugins/modules/network/illumos/ipadm_prop.py | 261 ++ .../plugins/modules/network/ingate/ig_config.py | 564 ++++ .../modules/network/ingate/ig_unit_information.py | 156 + .../modules/network/ironware/ironware_command.py | 170 + .../modules/network/ironware/ironware_config.py | 287 ++ .../modules/network/ironware/ironware_facts.py | 647 ++++ .../modules/network/itential/iap_start_workflow.py | 179 + .../plugins/modules/network/itential/iap_token.py | 137 + .../modules/network/netact/netact_cm_command.py | 360 ++ .../network/netscaler/netscaler_cs_action.py | 283 ++ .../network/netscaler/netscaler_cs_policy.py | 283 ++ .../network/netscaler/netscaler_cs_vserver.py | 1302 ++++++++ .../network/netscaler/netscaler_gslb_service.py | 691 ++++ .../network/netscaler/netscaler_gslb_site.py | 419 +++ .../network/netscaler/netscaler_gslb_vserver.py | 951 ++++++ .../network/netscaler/netscaler_lb_monitor.py | 1376 ++++++++ .../network/netscaler/netscaler_lb_vserver.py | 1936 +++++++++++ .../network/netscaler/netscaler_nitro_request.py | 902 +++++ .../network/netscaler/netscaler_save_config.py | 172 + .../modules/network/netscaler/netscaler_server.py | 398 +++ .../modules/network/netscaler/netscaler_service.py | 959 ++++++ .../network/netscaler/netscaler_servicegroup.py | 1041 ++++++ .../network/netscaler/netscaler_ssl_certkey.py | 367 +++ .../modules/network/netvisor/pn_access_list.py | 161 + .../modules/network/netvisor/pn_access_list_ip.py | 167 + .../modules/network/netvisor/pn_admin_service.py | 202 ++ .../network/netvisor/pn_admin_session_timeout.py | 115 + .../modules/network/netvisor/pn_admin_syslog.py | 224 ++ .../plugins/modules/network/netvisor/pn_cluster.py | 320 ++ .../netvisor/pn_connection_stats_settings.py | 262 ++ .../modules/network/netvisor/pn_cpu_class.py | 207 ++ .../modules/network/netvisor/pn_cpu_mgmt_class.py | 136 + .../modules/network/netvisor/pn_dhcp_filter.py | 170 + .../modules/network/netvisor/pn_dscp_map.py | 156 + .../network/netvisor/pn_dscp_map_pri_map.py | 158 + .../modules/network/netvisor/pn_fabric_local.py | 162 + .../modules/network/netvisor/pn_igmp_snooping.py | 204 ++ .../network/netvisor/pn_ipv6security_raguard.py | 233 ++ .../netvisor/pn_ipv6security_raguard_port.py | 143 + .../netvisor/pn_ipv6security_raguard_vlan.py | 177 + .../network/netvisor/pn_log_audit_exception.py | 198 ++ .../plugins/modules/network/netvisor/pn_ospf.py | 298 ++ .../modules/network/netvisor/pn_ospfarea.py | 224 ++ .../modules/network/netvisor/pn_port_config.py | 377 +++ .../modules/network/netvisor/pn_port_cos_bw.py | 153 + .../network/netvisor/pn_port_cos_rate_setting.py | 201 ++ .../modules/network/netvisor/pn_prefix_list.py | 159 + .../network/netvisor/pn_prefix_list_network.py | 185 ++ .../plugins/modules/network/netvisor/pn_role.py | 232 ++ .../plugins/modules/network/netvisor/pn_show.py | 202 ++ .../modules/network/netvisor/pn_snmp_community.py | 173 + .../modules/network/netvisor/pn_snmp_trap_sink.py | 209 ++ .../modules/network/netvisor/pn_snmp_vacm.py | 224 ++ .../plugins/modules/network/netvisor/pn_stp.py | 199 ++ .../modules/network/netvisor/pn_stp_port.py | 190 ++ .../modules/network/netvisor/pn_switch_setup.py | 407 +++ .../plugins/modules/network/netvisor/pn_trunk.py | 462 +++ .../plugins/modules/network/netvisor/pn_user.py | 195 ++ .../network/netvisor/pn_vflow_table_profile.py | 137 + .../plugins/modules/network/netvisor/pn_vlag.py | 350 ++ .../plugins/modules/network/netvisor/pn_vlan.py | 316 ++ .../plugins/modules/network/netvisor/pn_vrouter.py | 423 +++ .../modules/network/netvisor/pn_vrouter_bgp.py | 467 +++ .../network/netvisor/pn_vrouter_bgp_network.py | 181 ++ .../network/netvisor/pn_vrouter_interface_ip.py | 247 ++ .../netvisor/pn_vrouter_loopback_interface.py | 221 ++ .../modules/network/netvisor/pn_vrouter_ospf.py | 196 ++ .../modules/network/netvisor/pn_vrouter_ospf6.py | 196 ++ .../network/netvisor/pn_vrouter_packet_relay.py | 194 ++ .../network/netvisor/pn_vrouter_pim_config.py | 169 + .../modules/network/netvisor/pn_vrouterbgp.py | 485 +++ .../modules/network/netvisor/pn_vrouterif.py | 490 +++ .../modules/network/netvisor/pn_vrouterlbif.py | 331 ++ .../plugins/modules/network/netvisor/pn_vtep.py | 198 ++ .../plugins/modules/network/nos/nos_command.py | 219 ++ .../plugins/modules/network/nos/nos_config.py | 389 +++ .../plugins/modules/network/nos/nos_facts.py | 453 +++ .../plugins/modules/network/nso/nso_action.py | 184 ++ .../plugins/modules/network/nso/nso_config.py | 282 ++ .../plugins/modules/network/nso/nso_query.py | 121 + .../plugins/modules/network/nso/nso_show.py | 126 + .../plugins/modules/network/nso/nso_verify.py | 200 ++ .../plugins/modules/network/nuage/nuage_vspk.py | 1016 ++++++ .../network/plugins/modules/network/opx/opx_cps.py | 389 +++ .../modules/network/ordnance/ordnance_config.py | 356 ++ .../modules/network/ordnance/ordnance_facts.py | 288 ++ .../plugins/modules/network/panos/panos_admin.py | 194 ++ .../plugins/modules/network/panos/panos_admpwd.py | 204 ++ .../modules/network/panos/panos_cert_gen_ssh.py | 192 ++ .../plugins/modules/network/panos/panos_check.py | 145 + .../plugins/modules/network/panos/panos_commit.py | 232 ++ .../plugins/modules/network/panos/panos_dag.py | 143 + .../modules/network/panos/panos_dag_tags.py | 231 ++ .../plugins/modules/network/panos/panos_import.py | 193 ++ .../modules/network/panos/panos_interface.py | 177 + .../plugins/modules/network/panos/panos_lic.py | 171 + .../plugins/modules/network/panos/panos_loadcfg.py | 123 + .../modules/network/panos/panos_match_rule.py | 388 +++ .../modules/network/panos/panos_mgtconfig.py | 189 ++ .../modules/network/panos/panos_nat_rule.py | 467 +++ .../plugins/modules/network/panos/panos_object.py | 489 +++ .../plugins/modules/network/panos/panos_op.py | 157 + .../plugins/modules/network/panos/panos_pg.py | 201 ++ .../modules/network/panos/panos_query_rules.py | 494 +++ .../plugins/modules/network/panos/panos_restart.py | 109 + .../plugins/modules/network/panos/panos_sag.py | 267 ++ .../modules/network/panos/panos_security_rule.py | 572 ++++ .../plugins/modules/network/panos/panos_set.py | 166 + .../modules/network/radware/vdirect_commit.py | 338 ++ .../modules/network/radware/vdirect_file.py | 239 ++ .../modules/network/radware/vdirect_runnable.py | 336 ++ .../modules/network/routeros/routeros_api.py | 482 +++ .../modules/network/routeros/routeros_command.py | 181 ++ .../modules/network/routeros/routeros_facts.py | 629 ++++ .../plugins/modules/network/slxos/slxos_command.py | 219 ++ .../plugins/modules/network/slxos/slxos_config.py | 460 +++ .../plugins/modules/network/slxos/slxos_facts.py | 451 +++ .../modules/network/slxos/slxos_interface.py | 464 +++ .../modules/network/slxos/slxos_l2_interface.py | 501 +++ .../modules/network/slxos/slxos_l3_interface.py | 308 ++ .../plugins/modules/network/slxos/slxos_linkagg.py | 322 ++ .../plugins/modules/network/slxos/slxos_lldp.py | 128 + .../plugins/modules/network/slxos/slxos_vlan.py | 305 ++ .../plugins/modules/network/sros/sros_command.py | 228 ++ .../plugins/modules/network/sros/sros_config.py | 329 ++ .../plugins/modules/network/sros/sros_rollback.py | 210 ++ .../plugins/modules/network/voss/voss_command.py | 234 ++ .../plugins/modules/network/voss/voss_config.py | 451 +++ .../plugins/modules/network/voss/voss_facts.py | 503 +++ .../network/plugins/modules/nos_command.py | 219 ++ .../network/plugins/modules/nos_config.py | 389 +++ .../community/network/plugins/modules/nos_facts.py | 453 +++ .../network/plugins/modules/nso_action.py | 184 ++ .../network/plugins/modules/nso_config.py | 282 ++ .../community/network/plugins/modules/nso_query.py | 121 + .../community/network/plugins/modules/nso_show.py | 126 + .../network/plugins/modules/nso_verify.py | 200 ++ .../network/plugins/modules/nuage_vspk.py | 1016 ++++++ .../community/network/plugins/modules/opx_cps.py | 389 +++ .../network/plugins/modules/ordnance_config.py | 356 ++ .../network/plugins/modules/ordnance_facts.py | 288 ++ .../network/plugins/modules/panos_admin.py | 194 ++ .../network/plugins/modules/panos_admpwd.py | 204 ++ .../network/plugins/modules/panos_cert_gen_ssh.py | 192 ++ .../network/plugins/modules/panos_check.py | 145 + .../network/plugins/modules/panos_commit.py | 232 ++ .../community/network/plugins/modules/panos_dag.py | 143 + .../network/plugins/modules/panos_dag_tags.py | 231 ++ .../network/plugins/modules/panos_import.py | 193 ++ .../network/plugins/modules/panos_interface.py | 177 + .../community/network/plugins/modules/panos_lic.py | 171 + .../network/plugins/modules/panos_loadcfg.py | 123 + .../network/plugins/modules/panos_match_rule.py | 388 +++ .../network/plugins/modules/panos_mgtconfig.py | 189 ++ .../network/plugins/modules/panos_nat_rule.py | 467 +++ .../network/plugins/modules/panos_object.py | 489 +++ .../community/network/plugins/modules/panos_op.py | 157 + .../community/network/plugins/modules/panos_pg.py | 201 ++ .../network/plugins/modules/panos_query_rules.py | 494 +++ .../network/plugins/modules/panos_restart.py | 109 + .../community/network/plugins/modules/panos_sag.py | 267 ++ .../network/plugins/modules/panos_security_rule.py | 572 ++++ .../community/network/plugins/modules/panos_set.py | 166 + .../network/plugins/modules/pn_access_list.py | 161 + .../network/plugins/modules/pn_access_list_ip.py | 167 + .../network/plugins/modules/pn_admin_service.py | 202 ++ .../plugins/modules/pn_admin_session_timeout.py | 115 + .../network/plugins/modules/pn_admin_syslog.py | 224 ++ .../network/plugins/modules/pn_cluster.py | 320 ++ .../modules/pn_connection_stats_settings.py | 262 ++ .../network/plugins/modules/pn_cpu_class.py | 207 ++ .../network/plugins/modules/pn_cpu_mgmt_class.py | 136 + .../network/plugins/modules/pn_dhcp_filter.py | 170 + .../network/plugins/modules/pn_dscp_map.py | 156 + .../network/plugins/modules/pn_dscp_map_pri_map.py | 158 + .../network/plugins/modules/pn_fabric_local.py | 162 + .../network/plugins/modules/pn_igmp_snooping.py | 204 ++ .../plugins/modules/pn_ipv6security_raguard.py | 233 ++ .../modules/pn_ipv6security_raguard_port.py | 143 + .../modules/pn_ipv6security_raguard_vlan.py | 177 + .../plugins/modules/pn_log_audit_exception.py | 198 ++ .../community/network/plugins/modules/pn_ospf.py | 298 ++ .../network/plugins/modules/pn_ospfarea.py | 224 ++ .../network/plugins/modules/pn_port_config.py | 377 +++ .../network/plugins/modules/pn_port_cos_bw.py | 153 + .../plugins/modules/pn_port_cos_rate_setting.py | 201 ++ .../network/plugins/modules/pn_prefix_list.py | 159 + .../plugins/modules/pn_prefix_list_network.py | 185 ++ .../community/network/plugins/modules/pn_role.py | 232 ++ .../community/network/plugins/modules/pn_show.py | 202 ++ .../network/plugins/modules/pn_snmp_community.py | 173 + .../network/plugins/modules/pn_snmp_trap_sink.py | 209 ++ .../network/plugins/modules/pn_snmp_vacm.py | 224 ++ .../community/network/plugins/modules/pn_stp.py | 199 ++ .../network/plugins/modules/pn_stp_port.py | 190 ++ .../network/plugins/modules/pn_switch_setup.py | 407 +++ .../community/network/plugins/modules/pn_trunk.py | 462 +++ .../community/network/plugins/modules/pn_user.py | 195 ++ .../plugins/modules/pn_vflow_table_profile.py | 137 + .../community/network/plugins/modules/pn_vlag.py | 350 ++ .../community/network/plugins/modules/pn_vlan.py | 316 ++ .../network/plugins/modules/pn_vrouter.py | 423 +++ .../network/plugins/modules/pn_vrouter_bgp.py | 467 +++ .../plugins/modules/pn_vrouter_bgp_network.py | 181 ++ .../plugins/modules/pn_vrouter_interface_ip.py | 247 ++ .../modules/pn_vrouter_loopback_interface.py | 221 ++ .../network/plugins/modules/pn_vrouter_ospf.py | 196 ++ .../network/plugins/modules/pn_vrouter_ospf6.py | 196 ++ .../plugins/modules/pn_vrouter_packet_relay.py | 194 ++ .../plugins/modules/pn_vrouter_pim_config.py | 169 + .../network/plugins/modules/pn_vrouterbgp.py | 485 +++ .../network/plugins/modules/pn_vrouterif.py | 490 +++ .../network/plugins/modules/pn_vrouterlbif.py | 331 ++ .../community/network/plugins/modules/pn_vtep.py | 198 ++ .../network/plugins/modules/routeros_api.py | 482 +++ .../network/plugins/modules/routeros_command.py | 181 ++ .../network/plugins/modules/routeros_facts.py | 629 ++++ .../network/plugins/modules/slxos_command.py | 219 ++ .../network/plugins/modules/slxos_config.py | 460 +++ .../network/plugins/modules/slxos_facts.py | 451 +++ .../network/plugins/modules/slxos_interface.py | 464 +++ .../network/plugins/modules/slxos_l2_interface.py | 501 +++ .../network/plugins/modules/slxos_l3_interface.py | 308 ++ .../network/plugins/modules/slxos_linkagg.py | 322 ++ .../network/plugins/modules/slxos_lldp.py | 128 + .../network/plugins/modules/slxos_vlan.py | 305 ++ .../network/plugins/modules/sros_command.py | 228 ++ .../network/plugins/modules/sros_config.py | 329 ++ .../network/plugins/modules/sros_rollback.py | 210 ++ .../network/plugins/modules/vdirect_commit.py | 338 ++ .../network/plugins/modules/vdirect_file.py | 239 ++ .../network/plugins/modules/vdirect_runnable.py | 336 ++ .../network/plugins/modules/voss_command.py | 234 ++ .../network/plugins/modules/voss_config.py | 451 +++ .../network/plugins/modules/voss_facts.py | 503 +++ .../community/network/plugins/netconf/__init__.py | 0 .../community/network/plugins/netconf/ce.py | 248 ++ .../community/network/plugins/netconf/sros.py | 120 + .../community/network/plugins/terminal/__init__.py | 0 .../community/network/plugins/terminal/aireos.py | 59 + .../community/network/plugins/terminal/apconos.py | 35 + .../community/network/plugins/terminal/aruba.py | 68 + .../community/network/plugins/terminal/ce.py | 60 + .../community/network/plugins/terminal/cnos.py | 84 + .../community/network/plugins/terminal/edgeos.py | 35 + .../network/plugins/terminal/edgeswitch.py | 87 + .../community/network/plugins/terminal/enos.py | 83 + .../network/plugins/terminal/eric_eccli.py | 59 + .../community/network/plugins/terminal/exos.py | 59 + .../community/network/plugins/terminal/icx.py | 81 + .../community/network/plugins/terminal/ironware.py | 78 + .../community/network/plugins/terminal/netvisor.py | 39 + .../community/network/plugins/terminal/nos.py | 54 + .../community/network/plugins/terminal/routeros.py | 69 + .../community/network/plugins/terminal/slxos.py | 54 + .../community/network/plugins/terminal/sros.py | 43 + .../community/network/plugins/terminal/voss.py | 89 + 960 files changed, 364563 insertions(+) create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/aireos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/aruba.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/ce_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/cnos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/edgeos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/enos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/exos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/ironware.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/network/edgeos/edgeos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/network/nos/nos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/nos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/slxos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/sros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/action/voss.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/become/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cache/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/callback/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aireos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/apconos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aruba.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/cnos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeswitch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/enos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/eric_eccli.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/exos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/icx.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ironware.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/netvisor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/nos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/routeros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/slxos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/cliconf/voss.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/connection/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/a10.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aireos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aruba.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/avi.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/cnos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/enos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ingate.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ironware.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/netscaler.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/nso.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/panos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/sros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/filter/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/httpapi/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/httpapi/exos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortianalyzer.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortimanager.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/httpapi/ftd.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/inventory/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/lookup/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/lookup/avi.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/a10.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/aireos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/aos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/apconos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/aruba.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/ansible_utils.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi_api.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/bigswitch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/checkpoint/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_devicerules.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_errorcodes.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/edgeos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/enos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/eric_eccli.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/l2_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/lldp_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/lldp_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/vlans.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/l2_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/lldp_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/lldp_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/vlans.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/exos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/l2_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/base.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/lldp_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/lldp_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/vlans.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/utils.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/common.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/fortianalyzer.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/common.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/configuration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/device.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/fdm_swagger_client.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/operation.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/icx.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/common.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/ironware.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/netscaler.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/netvisor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/pn_nvos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/nos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/nso.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/ordnance.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/panos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/routeros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/slxos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/sros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/voss.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server_axapi3.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_service_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_virtual_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/apconos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_actiongroupconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertemailconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertscriptconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertsyslogconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_analyticsprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_session.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_version.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationpersistenceprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_authprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_autoscalelaunchconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backupconfiguration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_certificatemanagementprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloud.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudconnectoruser.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cluster.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_clusterclouddetails.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_controllerproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_customipamdnsprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_dnspolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpagebody.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpageprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslb.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbgeodbprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice_patch_member.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_hardwaresecuritymodulegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_healthmonitor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_httppolicyset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipaddrgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipamdnsproviderprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_l4policyset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_microservicegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networkprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networksecuritypolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pkiprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pool.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroupdeploymentpolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_prioritylabels.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_role.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_scheduler.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_seproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serverautoscalepolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceengine.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceenginegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_snmptrapprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslkeyandcertificate.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_stringgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_systemconfiguration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_tenant.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_trafficcloneprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccount.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccountprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_virtualservice.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vrfcontext.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsdatascriptset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsvip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_webhook.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/bcf_switch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_chain.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_policy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server_host.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_advance.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_session.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_view.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_eth_trunk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bd_vni.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp_rr.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_file_copy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_debug.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_log.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_trap.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ip_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_instance.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_view.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lacp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_link_status.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mdn_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mtu.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_igmp_enable.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netconf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_aging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_export.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp_auth.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_reboot.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_sflow.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_community.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_contact.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_location.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_target_host.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_traps.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_startup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route_bfd.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_stp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_switchport.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrrp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_arp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_gateway.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_tunnel.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_vap.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_backup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_banner.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_factory.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_image.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l2_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_logging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_reload.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_save.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_showrun.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_system.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cp_publish.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/cv_server_provision.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_etherstub.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_iptun.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_linkprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vnic.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/eric_eccli_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_l2_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_vlans.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/faz_device.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/flowadm.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_provision_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_address.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool6.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_vip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_ipv4.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_package.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_ha.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_provisioning.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_query.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_script.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_appctrl.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_av.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_dns.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ips.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_profile_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_proxy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_spam.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ssl_ssh.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_voip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_waf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_wanopt.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_web.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_configuration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_download.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_upload.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_install.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_start_workflow.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_token.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_banner.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_copy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_logging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_ping.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_system.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_unit_information.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addr.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addrprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_if.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_ifprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_prop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nclu.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netact_cm_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_action.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_policy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_site.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_monitor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_nitro_request.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_save_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_servicegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_ssl_certkey.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server_axapi3.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_service_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_virtual_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/apconos/apconos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_actiongroupconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertemailconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertscriptconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertsyslogconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_analyticsprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_session.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_version.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationpersistenceprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_authprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_autoscalelaunchconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backupconfiguration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_certificatemanagementprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloud.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudconnectoruser.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cluster.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_clusterclouddetails.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_controllerproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_customipamdnsprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_dnspolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpagebody.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpageprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslb.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbgeodbprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice_patch_member.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_hardwaresecuritymodulegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_healthmonitor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_httppolicyset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipaddrgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipamdnsproviderprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_l4policyset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_microservicegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networkprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networksecuritypolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pkiprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pool.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroupdeploymentpolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_prioritylabels.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_role.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_scheduler.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_seproperties.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serverautoscalepolicy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceengine.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceenginegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_snmptrapprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslkeyandcertificate.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_stringgroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_systemconfiguration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_tenant.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_trafficcloneprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccount.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccountprofile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_virtualservice.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vrfcontext.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsdatascriptset.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsvip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_webhook.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bcf_switch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_chain.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_policy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/check_point/cp_publish.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server_host.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_advance.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_session.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_view.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_eth_trunk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bd_vni.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp_rr.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_file_copy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_debug.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_log.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_trap.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ip_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_instance.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_view.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lacp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_link_status.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mdn_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mtu.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_igmp_enable.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netconf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_aging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_export.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp_auth.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_reboot.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_sflow.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_community.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_contact.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_location.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_target_host.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_traps.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_startup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route_bfd.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_stp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_switchport.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_af.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrrp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_arp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_gateway.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_tunnel.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_vap.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudvision/cv_server_provision.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_backup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_banner.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_factory.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_image.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l2_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_logging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_reload.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_save.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_showrun.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_system.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vrf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cumulus/nclu.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/eric_eccli/eric_eccli_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_l2_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_global.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_interfaces.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_vlans.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortianalyzer/faz_device.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_provision_template.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_address.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool6.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_vip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_ipv4.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_package.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_ha.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_provisioning.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_query.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_script.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_appctrl.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_av.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_dns.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ips.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_profile_group.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_proxy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_spam.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ssl_ssh.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_voip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_waf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_wanopt.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_web.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_configuration.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_download.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_upload.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_install.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_banner.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_copy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_logging.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_ping.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_static_route.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_system.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_etherstub.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_iptun.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_linkprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vnic.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/flowadm.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addr.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addrprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_if.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_ifprop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_prop.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_unit_information.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_start_workflow.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_token.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netact/netact_cm_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_action.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_policy.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_site.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_monitor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_vserver.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_nitro_request.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_save_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_server.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_servicegroup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_ssl_certkey.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list_ip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_session_timeout.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_syslog.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cluster.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_connection_stats_settings.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_class.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_mgmt_class.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dhcp_filter.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map_pri_map.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_fabric_local.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_igmp_snooping.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_port.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_log_audit_exception.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospfarea.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_bw.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_rate_setting.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_role.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_show.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_community.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_trap_sink.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_vacm.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp_port.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_switch_setup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_trunk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vflow_table_profile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_interface_ip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_loopback_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf6.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_packet_relay.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_pim_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterbgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterif.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterlbif.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vtep.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_action.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_query.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_show.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_verify.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nuage/nuage_vspk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/opx/opx_cps.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admin.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admpwd.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_cert_gen_ssh.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_check.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_commit.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag_tags.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_import.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_lic.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_loadcfg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_match_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_mgtconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_nat_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_object.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_op.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_pg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_query_rules.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_restart.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_sag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_security_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_set.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_commit.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_file.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_runnable.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_api.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l2_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_action.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_query.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_show.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_verify.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/nuage_vspk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/opx_cps.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admin.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admpwd.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_cert_gen_ssh.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_check.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_commit.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag_tags.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_import.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_lic.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_loadcfg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_match_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_mgtconfig.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_nat_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_object.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_op.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_pg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_query_rules.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_restart.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_sag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_security_rule.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_set.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list_ip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_service.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_session_timeout.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_syslog.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cluster.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_connection_stats_settings.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_class.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_mgmt_class.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dhcp_filter.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map_pri_map.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_fabric_local.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_igmp_snooping.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_port.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_log_audit_exception.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospfarea.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_bw.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_rate_setting.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_role.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_show.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_community.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_trap_sink.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_vacm.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp_port.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_switch_setup.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_trunk.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_user.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vflow_table_profile.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlag.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp_network.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_interface_ip.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_loopback_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf6.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_packet_relay.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_pim_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterbgp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterif.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterlbif.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vtep.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_api.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l2_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l3_interface.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_linkagg.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_lldp.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_vlan.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_rollback.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_commit.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_file.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_runnable.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_command.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_config.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_facts.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/netconf/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/netconf/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/netconf/sros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/__init__.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/aireos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/apconos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/aruba.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/ce.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/cnos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeswitch.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/enos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/eric_eccli.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/exos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/icx.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/ironware.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/netvisor.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/nos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/routeros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/slxos.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/sros.py create mode 100644 collections-debian-merged/ansible_collections/community/network/plugins/terminal/voss.py (limited to 'collections-debian-merged/ansible_collections/community/network/plugins') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/aireos.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/aireos.py new file mode 100644 index 00000000..c947afb9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/aireos.py @@ -0,0 +1,89 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import aireos_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'aireos_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(aireos_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'aireos' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, + task_uuid=self._task._uuid) + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + + else: + return dict( + failed=True, + msg='invalid connection specified, expected connection is either network_cli or ansible.netcommon.network_cli' + 'got %s' % self._play_context.connection + ) + + if self._play_context.become_method and self._play_context.become_method.split('.')[-1] == 'enable': + self._play_context.become = False + self._play_context.become_method = None + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/aruba.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/aruba.py new file mode 100644 index 00000000..716cf6c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/aruba.py @@ -0,0 +1,88 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import aruba_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'aruba_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(aruba_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'aruba' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + else: + return dict( + failed=True, + msg='invalid connection specified, expected connection is either network_cli or ansible.netcommon.network_cli' + 'got %s' % self._play_context.connection + ) + + if self._play_context.become_method and self._play_context.become_method.split('.')[-1] == 'enable': + self._play_context.become = False + self._play_context.become_method = None + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/ce.py new file mode 100644 index 00000000..0b5498f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/ce.py @@ -0,0 +1,94 @@ +# +# Copyright: (c) 2016, Red Hat Inc. + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + +CLI_SUPPORTED_MODULES = ['ce_rollback', 'ce_mlag_interface', 'ce_startup', 'ce_config', + 'ce_command', 'ce_facts', 'ce_evpn_global', 'ce_evpn_bgp_rr', + 'ce_mtu', 'ce_evpn_bgp', 'ce_snmp_location', 'ce_snmp_contact', + 'ce_snmp_traps', 'ce_netstream_global', 'ce_netstream_aging', + 'ce_netstream_export', 'ce_netstream_template', 'ce_ntp_auth', + 'ce_stp', 'ce_vxlan_global', 'ce_vxlan_arp', 'ce_vxlan_gateway', + 'ce_acl_interface'] + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'ce_config' else False + socket_path = None + persistent_connection = self._play_context.connection.split('.')[-1] + + if self._play_context.connection == 'local': + provider = load_provider(ce_provider_spec, self._task.args) + transport = provider['transport'] or 'cli' + + display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr) + + if transport == 'cli': + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'ce' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + self._task.args['provider'] = provider.update( + host=pc.remote_addr, + port=pc.port, + username=pc.remote_user, + password=pc.password + ) + if module_name in ['ce_netconf'] or module_name not in CLI_SUPPORTED_MODULES: + pc.connection = 'netconf' + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + # make sure a transport value is set in args + self._task.args['transport'] = transport + self._task.args['provider'] = provider + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + + elif persistent_connection in ('netconf', 'network_cli'): + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using %s and will be ignored' % self._play_context.connection) + del self._task.args['provider'] + + if (persistent_connection == 'network_cli' and module_name not in CLI_SUPPORTED_MODULES) or \ + (persistent_connection == 'netconf' and module_name in CLI_SUPPORTED_MODULES): + return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module." + % (self._play_context.connection, self._task.action)} + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/ce_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/ce_template.py new file mode 100644 index 00000000..c874ab26 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/ce_template.py @@ -0,0 +1,104 @@ +# +# Copyright 2015 Peter Sprygada +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import time +import glob +from ansible.module_utils.six.moves.urllib.parse import urlsplit + +from ansible.module_utils._text import to_text +from ansible_collections.community.network.plugins.action.ce import ActionModule as _ActionModule + + +class ActionModule(_ActionModule): + + def run(self, tmp=None, task_vars=None): + + try: + self._handle_template() + except (ValueError, AttributeError) as exc: + return dict(failed=True, msg=exc.message) + + 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. + self._write_backup(task_vars['inventory_hostname'], result['__backup__']) + + if '__backup__' in result: + del result['__backup__'] + + 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) + + def _handle_template(self): + src = self._task.args.get('src') + if not src: + raise ValueError('missing required arguments: 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): + return + + 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)) + with self._templar.set_temporary_context(searchpath=searchpath): + self._task.args['src'] = self._templar.template(template_data) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/cnos.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/cnos.py new file mode 100644 index 00000000..49473d0c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/cnos.py @@ -0,0 +1,78 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains Action Plugin methods for CNOS Config Module +# Lenovo Networking +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'cnos_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(cnos_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'cnos' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = provider['port'] or self._play_context.port or 22 + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + pc.become = provider['authorize'] or True + pc.become_pass = provider['auth_pass'] + pc.become_method = 'enable' + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/edgeos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/edgeos_config.py new file mode 100644 index 00000000..e1145da6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/edgeos_config.py @@ -0,0 +1,36 @@ +# +# 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + self._config_module = True + + if self._play_context.connection.split('.')[-1] != 'network_cli': + return {'failed': True, 'msg': 'Connection type %s is not valid for this module. Must use fully qualified' + ' name of network_cli connection type.' % self._play_context.connection} + + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/enos.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/enos.py new file mode 100644 index 00000000..019f2872 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/enos.py @@ -0,0 +1,78 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains Action Plugin methods for ENOS Config Module +# Lenovo Networking +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import enos_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'enos_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(enos_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'enos' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = provider['port'] or self._play_context.port or 22 + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + pc.become = provider['authorize'] or True + pc.become_pass = provider['auth_pass'] + pc.become_method = 'enable' + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/exos.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/exos.py new file mode 100644 index 00000000..5d91155c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/exos.py @@ -0,0 +1,45 @@ +# +# Copyright 2015 Peter Sprygada +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + EXOS_NETWORK_CLI_MODULES = ( + 'exos_facts', + 'exos_config', + 'exos_command') + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'exos_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection not in ('network_cli', 'httpapi'): + return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} + + if persistent_connection == 'network_cli' and module_name not in self.EXOS_NETWORK_CLI_MODULES: + return {'failed': True, 'msg': "Connection type %s is not valid for this module" % self._play_context.connection} + + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/ironware.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/ironware.py new file mode 100644 index 00000000..337fd888 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/ironware.py @@ -0,0 +1,83 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_provider_spec +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'ironware_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(ironware_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'ironware' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + pc.become = provider['authorize'] or False + if pc.become: + pc.become_method = 'enable' + pc.become_pass = provider['auth_pass'] + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + + command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout') + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + else: + return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/network/edgeos/edgeos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/network/edgeos/edgeos_config.py new file mode 100644 index 00000000..e1145da6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/network/edgeos/edgeos_config.py @@ -0,0 +1,36 @@ +# +# 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + self._config_module = True + + if self._play_context.connection.split('.')[-1] != 'network_cli': + return {'failed': True, 'msg': 'Connection type %s is not valid for this module. Must use fully qualified' + ' name of network_cli connection type.' % self._play_context.connection} + + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/network/nos/nos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/network/nos/nos_config.py new file mode 100644 index 00000000..3f49e1af --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/network/nos/nos_config.py @@ -0,0 +1,31 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + self._config_module = True + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/nos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/nos_config.py new file mode 100644 index 00000000..3f49e1af --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/nos_config.py @@ -0,0 +1,31 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + self._config_module = True + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/slxos.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/slxos.py new file mode 100644 index 00000000..cb0478ce --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/slxos.py @@ -0,0 +1,40 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + +PRIVATE_KEYS_RE = re.compile('__.+__') + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'slxos_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection not in ('network_cli'): + return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/sros.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/sros.py new file mode 100644 index 00000000..05485df2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/sros.py @@ -0,0 +1,80 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import sros_provider_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + persistent_connection = self._play_context.connection.split('.')[-1] + self._config_module = True if module_name == 'sros_config' else False + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(sros_provider_spec, self._task.args) + + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'sros' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + + display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + msg = "connection local support for this module is deprecated use either" \ + " 'network_cli' or 'ansible.netcommon.network_cli' connection" + display.deprecated(msg, version='4.0.0', collection_name='community.network') + else: + return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/action/voss.py b/collections-debian-merged/ansible_collections/community/network/plugins/action/voss.py new file mode 100644 index 00000000..e7e2ca45 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/action/voss.py @@ -0,0 +1,36 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'voss_config' else False + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection not in ('network_cli'): + return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} + return super(ActionModule, self).run(task_vars=task_vars) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/become/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/become/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cache/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/cache/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/callback/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/callback/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aireos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aireos.py new file mode 100644 index 00000000..c0b53bbc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aireos.py @@ -0,0 +1,96 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: aireos +short_description: Use aireos cliconf to run command on Cisco WLC platform +description: + - This aireos plugin provides low level abstraction apis for + sending and receiving CLI commands from Cisco WLC network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'aireos' + reply = self.get('show sysinfo') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Product Version\.* (.*)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'System Name\.* (.*)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + reply = self.get('show inventory') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'DESCR: \"(.*)\"', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + return self.invalid_params("fetching configuration from %s is not supported" % source) + if source == 'running': + cmd = 'show run-config commands' + else: + cmd = 'show run-config startup-commands' + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(['config'], to_list(command), ['end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/apconos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/apconos.py new file mode 100644 index 00000000..03c7e7a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/apconos.py @@ -0,0 +1,72 @@ +# (C) 2018 Red Hat Inc. +# Copyright (C) 2019 APCON. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains CLIConf Plugin methods for apconos Modules +# APCON Networking + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: "David Li (@davidlee-ap)" +cliconf: apconos +short_description: Use apconos cliconf to run command on APCON network devices +description: + - This apconos plugin provides low level abstraction apis for + sending and receiving CLI commands from APCON network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'apconos' + reply = self.get(b'show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + if data: + device_info['network_os_version'] = self.parse_version(data) + device_info['network_os_model'] = self.parse_model(data) + + return device_info + + def parse_version(self, data): + return "" + + def parse_model(self, data): + return "" + + @enable_mode + def get_config(self, source='running', format='text'): + pass + + @enable_mode + def edit_config(self, command): + for cmd in chain([b'configure terminal'], to_list(command), [b'end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, check_all=check_all) + + def get_capabilities(self): + return json.dumps(self.get_device_info()) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aruba.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aruba.py new file mode 100644 index 00000000..b3aa7295 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/aruba.py @@ -0,0 +1,96 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: aruba +short_description: Use aruba cliconf to run command on Aruba platform +description: + - This aruba plugin provides low level abstraction apis for + sending and receiving CLI commands from Aruba network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'aruba' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Version (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'^MODEL: (\S+)\),', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + reply = self.get('show hostname') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Hostname is (.+)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + return self.invalid_params("fetching configuration from %s is not supported" % source) + if source == 'running': + cmd = 'show running-config all' + else: + cmd = 'show configuration' + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ce.py new file mode 100644 index 00000000..9b24e7f5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ce.py @@ -0,0 +1,122 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: ce +short_description: Use ce cliconf to run command on HUAWEI CloudEngine platform +description: + - This ce plugin provides low level abstraction apis for + sending and receiving CLI commands from HUAWEI CloudEngine network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'ce' + reply = self.get('display version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Huawei.+\n.+\Version\s+(\S+)', data) + if match: + device_info['network_os_version'] = match.group(1).strip(',') + + match = re.search(r'^Huawei(.+)\n.+\(\S+\s+\S+\)', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'HUAWEI\s+(\S+)\s+uptime', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running'): + return self.invalid_params("fetching configuration from %s is not supported" % source) + + if not flags: + flags = [] + + cmd = 'display current-configuration' + + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + results = [] + for cmd in chain(['configure terminal'], to_list(command), ['end']): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + newline = cmd.get('newline', True) + else: + command = cmd + prompt = None + answer = None + newline = True + + results.append(self.send_command(command, prompt, answer, False, newline)) + return results[1:-1] + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + prompt = to_text(out, errors='surrogate_then_replace').strip() + while prompt.endswith(']'): + self._connection.queue_message('vvvv', 'wrong context, sending return to device') + if prompt.startswith('[*'): + self._connection.exec_command('clear configuration candidate') + self._connection.exec_command('return') + out = self._connection.get_prompt() + prompt = to_text(out, errors='surrogate_then_replace').strip() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/cnos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/cnos.py new file mode 100644 index 00000000..6568cd4f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/cnos.py @@ -0,0 +1,136 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains CLIConf Plugin methods for CNOS Modules +# Lenovo Networking +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: cnos +short_description: Use cnos cliconf to run command on Lenovo CNOS platform +description: + - This cnos plugin provides low level abstraction apis for + sending and receiving CLI commands from Lenovo CNOS network devices. +''' + +import re +import json + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils.common._collections_compat import Mapping +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'cnos' + reply = self.get('show sys-info') + data = to_text(reply, errors='surrogate_or_strict').strip() + host = self.get('show hostname') + hostname = to_text(host, errors='surrogate_or_strict').strip() + if data: + device_info['network_os_version'] = self.parse_version(data) + device_info['network_os_model'] = self.parse_model(data) + device_info['network_os_hostname'] = hostname + + return device_info + + def parse_version(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Software Revision (.*?)', + line, re.M | re.I) + if match: + vers = line.split(':') + ver = vers[1].strip() + return ver + return "NA" + + def parse_model(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Model (.*?)', line, re.M | re.I) + if match: + mdls = line.split(':') + mdl = mdls[1].strip() + return mdl + return "NA" + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + msg = "fetching configuration from %s is not supported" + return self.invalid_params(msg % source) + if source == 'running': + cmd = 'show running-config' + else: + cmd = 'show startup-config' + return self.send_command(cmd) + + @enable_mode + def edit_config(self, candidate=None, commit=True, + replace=None, comment=None): + resp = {} + results = [] + requests = [] + if commit: + self.send_command('configure terminal') + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {'command': line} + + cmd = line['command'] + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command('end') + else: + raise ValueError('check mode is not supported') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): + self._connection.queue_message('vvvv', 'In Config mode, sending exit to device') + self._connection.send_command('exit') + else: + self._connection.send_command('enable') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeos.py new file mode 100644 index 00000000..35e59f0c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeos.py @@ -0,0 +1,115 @@ +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: edgeos +short_description: Use edgeos cliconf to run command on EdgeOS platform +description: + - This edgeos plugin provides low level abstraction apis for + sending and receiving CLI commands from Ubiquiti EdgeOS network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'edgeos' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Version:\s*v?(\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'HW model:\s*(\S+)', data) + if match: + device_info['network_os_model'] = match.group(1) + + reply = self.get('show host name') + device_info['network_os_hostname'] = to_text(reply, errors='surrogate_or_strict').strip() + + return device_info + + def get_config(self, source='running', format='text', flags=None): + return self.send_command('show configuration commands|cat') + + def edit_config(self, candidate=None, commit=True, replace=False, comment=None): + for cmd in chain(['configure'], to_list(candidate)): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def commit(self, comment=None): + if comment: + command = 'commit comment "{0}"'.format(comment) + else: + command = 'commit' + self.send_command(command) + + def discard_changes(self, *args, **kwargs): + self.send_command('exit discard') + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', e) + + responses.append(out) + + return responses + + def get_device_operations(self): + return { + 'supports_diff_replace': False, + 'supports_commit': True, + 'supports_rollback': False, + 'supports_defaults': False, + 'supports_onbox_diff': False, + 'supports_commit_comment': True, + 'supports_multiline_delimiter': False, + 'supports_diff_match': False, + 'supports_diff_ignore_lines': False, + 'supports_generate_diff': False, + 'supports_replace': False + } + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result['rpc'] += ['commit', 'discard_changes', 'run_commands'] + result['device_operations'] = self.get_device_operations() + return json.dumps(result) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeswitch.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeswitch.py new file mode 100644 index 00000000..203c9455 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/edgeswitch.py @@ -0,0 +1,142 @@ +# +# (c) 2018 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: edgeswitch +short_description: Use edgeswitch cliconf to run command on EdgeSwitch platform +description: + - This edgeswitch plugin provides low level abstraction apis for + sending and receiving CLI commands from Ubiquiti EdgeSwitch network devices. +''' + +import re +import time +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode +from ansible.module_utils.common._collections_compat import Mapping + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'edgeswitch' + reply = self.get(command='show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Software Version\.+ (.*)', data) + if match: + device_info['network_os_version'] = match.group(1).strip(',') + + match = re.search(r'^Machine Model\.+ (.*)', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'System Name\.+ (.*)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', flags=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + + if source == 'running': + cmd = 'show running-config ' + else: + cmd = 'show startup-config ' + + if flags: + cmd += ' '.join(to_list(flags)) + cmd = cmd.strip() + + return self.send_command(cmd) + + @enable_mode + def edit_config(self, commands): + resp = {} + + results = [] + requests = [] + self.send_command('configure') + for line in to_list(commands): + if not isinstance(line, Mapping): + line = {'command': line} + + cmd = line['command'] + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command('end') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, newline=True, check_all=False): + if not command: + raise ValueError('must provide value of command to execute') + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result['rpc'] += ['run_commands'] + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', e) + + responses.append(out) + + return responses diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/enos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/enos.py new file mode 100644 index 00000000..0ee6dd4a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/enos.py @@ -0,0 +1,104 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains CLIConf Plugin methods for ENOS Modules +# Lenovo Networking +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: enos +short_description: Use enos cliconf to run command on Lenovo ENOS platform +description: + - This enos plugin provides low level abstraction apis for + sending and receiving CLI commands from Lenovo ENOS network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'enos' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Software Version (.*?) ', data, re.M | re.I) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'^Lenovo RackSwitch (\S+)', data, re.M | re.I) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'^(.+) uptime', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + else: + device_info['network_os_hostname'] = "NA" + + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + msg = "fetching configuration from %s is not supported" + return self.invalid_params(msg % source) + if source == 'running': + cmd = 'show running-config' + else: + cmd = 'show startup-config' + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): + self._connection.queue_message('vvvv', 'In Config mode, sending exit to device') + self._connection.send_command('exit') + else: + self._connection.send_command('enable') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/eric_eccli.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/eric_eccli.py new file mode 100644 index 00000000..c0ad73c4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/eric_eccli.py @@ -0,0 +1,97 @@ +# +# Copyright (c) 2019 Ericsson AB. +# +# 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 . +# + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Ericsson IPOS OAM team (!UNKNOWN) +cliconf: eric_eccli +short_description: Use eccli cliconf to run command on Ericsson ECCLI platform +description: + - This eccli plugin provides low level abstraction APIs for + sending and receiving CLI commands from Ericsson ECCLI network devices. +''' + +from ansible.module_utils.common._collections_compat import Mapping +import collections +import re +import time +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_config(self, source='running', flags=None, format=None): + return + + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + return + + def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, newline=True, check_all=False): + if not command: + raise ValueError('must provide value of command to execute') + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_device_info(self): + device_info = {} + device_info['network_os'] = 'eric_eccli' + return device_info + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + ['run_commands'] + result['network_api'] = 'cliconf' + result['device_info'] = self.get_device_info() + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', e) + + responses.append(out) + + return responses diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/exos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/exos.py new file mode 100644 index 00000000..3a653d34 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/exos.py @@ -0,0 +1,230 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: exos +short_description: Use exos cliconf to run command on Extreme EXOS platform +description: + - This exos plugin provides low level abstraction apis for + sending and receiving CLI commands from Extreme EXOS network devices. +''' + +import re +import json + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + diff = {} + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations['supports_generate_diff']: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values['diff_match']: + raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match']))) + + if diff_replace not in option_values['diff_replace']: + raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace']))) + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=1) + candidate_obj.load(candidate) + + if running and diff_match != 'none' and diff_replace != 'config': + # running configuration + running_obj = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace) + + else: + configdiffobjs = candidate_obj.items + + diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else '' + return diff + + def get_device_info(self): + device_info = {} + device_info['network_os'] = 'exos' + + reply = self.run_commands({'command': 'show switch detail', 'output': 'text'}) + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'ExtremeXOS version (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'System Type: +(\S+)', data) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'SysName: +(\S+)', data) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_default_flag(self): + # The flag to modify the command to collect configuration with defaults + return 'detail' + + def get_config(self, source='running', format='text', flags=None): + options_values = self.get_option_values() + if format not in options_values['format']: + raise ValueError("'format' value %s is invalid. Valid values are %s" % (format, ','.join(options_values['format']))) + + lookup = {'running': 'show configuration', 'startup': 'debug cfgmgr show configuration file'} + if source not in lookup: + raise ValueError("fetching configuration from %s is not supported" % source) + + cmd = {'command': lookup[source], 'output': 'text'} + + if source == 'startup': + reply = self.run_commands({'command': 'show switch', 'format': 'text'}) + data = to_text(reply, errors='surrogate_or_strict').strip() + match = re.search(r'Config Selected: +(\S+)\.cfg', data, re.MULTILINE) + if match: + cmd['command'] += match.group(1) + else: + # No Startup(/Selected) Config + return {} + + cmd['command'] += ' '.join(to_list(flags)) + cmd['command'] = cmd['command'].strip() + + return self.run_commands(cmd)[0] + + def edit_config(self, candidate=None, commit=True, replace=None, diff=False, comment=None): + resp = {} + operations = self.get_device_operations() + self.check_edit_config_capability(operations, candidate, commit, replace, comment) + results = [] + requests = [] + + if commit: + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {'command': line} + results.append(self.send_command(**line)) + requests.append(line['command']) + else: + raise ValueError('check mode is not supported') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command, prompt=None, answer=None, sendonly=False, output=None, newline=True, check_all=False): + if output: + command = self._get_command_with_output(command, output) + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + cmd['command'] = self._get_command_with_output(cmd['command'], output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc is True: + raise + out = getattr(e, 'err', e) + + if out is not None: + try: + out = to_text(out, errors='surrogate_or_strict').strip() + except UnicodeError: + raise ConnectionError(message=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) + + if output and output == 'json': + try: + out = json.loads(out) + except ValueError: + raise ConnectionError('Response was not valid JSON, got {0}'.format( + to_text(out) + )) + responses.append(out) + + return responses + + def get_device_operations(self): + return { + 'supports_diff_replace': False, # identify if config should be merged or replaced is supported + 'supports_commit': False, # identify if commit is supported by device or not + 'supports_rollback': False, # identify if rollback is supported or not + 'supports_defaults': True, # identify if fetching running config with default is supported + 'supports_commit_comment': False, # identify if adding comment to commit is supported of not + 'supports_onbox_diff': False, # identify if on box diff capability is supported or not + 'supports_generate_diff': True, # identify if diff capability is supported within plugin + 'supports_multiline_delimiter': False, # identify if multiline delimiter is supported within config + 'supports_diff_match': True, # identify if match is supported + 'supports_diff_ignore_lines': True, # identify if ignore line in diff is supported + 'supports_config_replace': False, # identify if running config replace with candidate config is supported + 'supports_admin': False, # identify if admin configure mode is supported or not + 'supports_commit_label': False, # identify if commit label is supported or not + 'supports_replace': False + } + + def get_option_values(self): + return { + 'format': ['text', 'json'], + 'diff_match': ['line', 'strict', 'exact', 'none'], + 'diff_replace': ['line', 'block'], + 'output': ['text', 'json'] + } + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result['rpc'] += ['run_commmands', 'get_default_flag', 'get_diff'] + result['device_operations'] = self.get_device_operations() + result['device_info'] = self.get_device_info() + result.update(self.get_option_values()) + return json.dumps(result) + + def _get_command_with_output(self, command, output): + if output not in self.get_option_values().get('output'): + raise ValueError("'output' value is %s is invalid. Valid values are %s" % (output, ','.join(self.get_option_values().get('output')))) + + if output == 'json' and not command.startswith('run script cli2json.py'): + cmd = 'run script cli2json.py %s' % command + else: + cmd = command + return cmd diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/icx.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/icx.py new file mode 100644 index 00000000..4ce6e5d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/icx.py @@ -0,0 +1,314 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Ruckus Wireless (@Commscope) +cliconf: icx +short_description: Use icx cliconf to run command on Ruckus ICX platform +description: + - This icx plugin provides low level abstraction APIs for + sending and receiving CLI commands from Ruckus ICX network devices. +''' + + +import re +import time +import json +import os + +from itertools import chain +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode +from ansible.module_utils.common._collections_compat import Mapping + + +class Cliconf(CliconfBase): + + @enable_mode + def get_config(self, source='running', flags=None, format=None, compare=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + + if format: + raise ValueError("'format' value %s is not supported for get_config" % format) + + if not flags: + flags = [] + + if compare is False: + return '' + else: + if source == 'running': + cmd = 'show running-config ' + else: + cmd = 'show configuration ' + + cmd += ' '.join(to_list(flags)) + cmd = cmd.strip() + + return self.send_command(cmd) + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + """ + Generate diff between candidate and running configuration. If the + remote host supports onbox diff capabilities ie. supports_onbox_diff in that case + candidate and running configurations are not required to be passed as argument. + In case if onbox diff capability is not supported candidate argument is mandatory + and running argument is optional. + :param candidate: The configuration which is expected to be present on remote host. + :param running: The base configuration which is used to generate diff. + :param diff_match: Instructs how to match the candidate configuration with current device configuration + Valid values are 'line', 'strict', 'exact', 'none'. + 'line' - commands are matched line by line + 'strict' - command lines are matched with respect to position + 'exact' - command lines must be an equal match + 'none' - will not compare the candidate configuration with the running configuration + :param diff_ignore_lines: Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + :param path: The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + :param diff_replace: Instructs on the way to perform the configuration on the device. + If the replace argument is set to I(line) then the modified lines are + pushed to the device in configuration mode. If the replace argument is + set to I(block) then the entire command block is pushed to the device in + configuration mode if any line is not correct. + :return: Configuration diff in json format. + { + 'config_diff': '', + 'banner_diff': {} + } + + """ + diff = {} + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations['supports_generate_diff']: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values['diff_match']: + raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match']))) + + if diff_replace not in option_values['diff_replace']: + raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace']))) + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=1) + want_src, want_banners = self._extract_banners(candidate) + candidate_obj.load(want_src) + + if running and diff_match != 'none': + # running configuration + have_src, have_banners = self._extract_banners(running) + + running_obj = NetworkConfig(indent=1, contents=have_src, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace) + + else: + configdiffobjs = candidate_obj.items + have_banners = {} + + diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else '' + + banners = self._diff_banners(want_banners, have_banners) + diff['banner_diff'] = banners if banners else {} + return diff + + @enable_mode + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + resp = {} + operations = self.get_device_operations() + self.check_edit_config_capability(operations, candidate, commit, replace, comment) + + results = [] + requests = [] + if commit: + prompt = self._connection.get_prompt() + if (b'(config-if' in prompt) or (b'(config' in prompt) or (b'(config-lag-if' in prompt): + self.send_command('end') + + self.send_command('configure terminal') + + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {'command': line} + + cmd = line['command'] + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command('end') + else: + raise ValueError('check mode is not supported') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, check_all=False): + if not command: + raise ValueError('must provide value of command to execute') + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, check_all=check_all) + + def scp(self, command=None, scp_user=None, scp_pass=None): + if not command: + raise ValueError('must provide value of command to execute') + prompt = ["User name:", "Password:"] + if(scp_pass is None): + answer = [scp_user, self._connection._play_context.password] + else: + answer = [scp_user, scp_pass] + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=False, check_all=True) + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'icx' + reply = self.get(command='show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Version (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1).strip(',') + + match = re.search(r'^Cisco (.+) \(revision', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'^(.+) uptime', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_device_operations(self): + return { + 'supports_diff_replace': True, + 'supports_commit': False, + 'supports_rollback': False, + 'supports_defaults': True, + 'supports_onbox_diff': False, + 'supports_commit_comment': False, + 'supports_multiline_delimiter': True, + 'supports_diff_match': True, + 'supports_diff_ignore_lines': True, + 'supports_generate_diff': True, + 'supports_replace': False + } + + def get_option_values(self): + return { + 'format': ['text'], + 'diff_match': ['line', 'strict', 'exact', 'none'], + 'diff_replace': ['line', 'block'], + 'output': [] + } + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + ['edit_banner', 'get_diff', 'run_commands', 'get_defaults_flag'] + result['network_api'] = 'cliconf' + result['device_operations'] = self.get_device_operations() + result.update(self.get_option_values()) + return json.dumps(result) + + def edit_banner(self, candidate=None, multiline_delimiter="@", commit=True): + """ + Edit banner on remote device + :param banners: Banners to be loaded in json format + :param multiline_delimiter: Line delimiter for banner + :param commit: Boolean value that indicates if the device candidate + configuration should be pushed in the running configuration or discarded. + :param diff: Boolean flag to indicate if configuration that is applied on remote host should + generated and returned in response or not + :return: Returns response of executing the configuration command received + from remote host + """ + resp = {} + banners_obj = json.loads(candidate) + results = [] + requests = [] + if commit: + for key, value in iteritems(banners_obj): + key += ' %s' % multiline_delimiter + self.send_command('config terminal', sendonly=True) + for cmd in [key, value, multiline_delimiter]: + obj = {'command': cmd, 'sendonly': True} + results.append(self.send_command(**obj)) + requests.append(cmd) + + self.send_command('end', sendonly=True) + time.sleep(0.1) + results.append(self.send_command('\n')) + requests.append('\n') + + resp['request'] = requests + resp['response'] = results + + return resp + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', to_text(e)) + + responses.append(out) + + return responses + + def _extract_banners(self, config): + banners = {} + banner_cmds = re.findall(r'^banner (\w+)', config, re.M) + for cmd in banner_cmds: + regex = r'banner %s \$(.+?)(?=\$)' % cmd + match = re.search(regex, config, re.S) + if match: + key = 'banner %s' % cmd + banners[key] = match.group(1).strip() + + for cmd in banner_cmds: + regex = r'banner %s \$(.+?)(?=\$)' % cmd + match = re.search(regex, config, re.S) + if match: + config = config.replace(str(match.group(1)), '') + + config = re.sub(r'banner \w+ \$\$', '!! banner removed', config) + return config, banners + + def _diff_banners(self, want, have): + candidate = {} + for key, value in iteritems(want): + if value != have.get(key): + candidate[key] = value + return candidate diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ironware.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ironware.py new file mode 100644 index 00000000..e5b7662b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/ironware.py @@ -0,0 +1,96 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: ironware +short_description: Use ironware cliconf to run command on Extreme Ironware platform +description: + - This ironware plugin provides low level abstraction apis for + sending and receiving CLI commands from Extreme Ironware network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'ironware' + reply = self.send_command('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'IronWare : Version (\S+),', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'^(?:System Mode\:|System\:) (CES|CER|MLX|XMR)', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + + if source == 'running': + cmd = 'show running-config' + if flags is not None: + cmd += ' ' + ' '.join(flags) + + else: + cmd = 'show configuration' + if flags is not None: + raise ValueError("flags are only supported with running-config") + + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/netvisor.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/netvisor.py new file mode 100644 index 00000000..f4a3f818 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/netvisor.py @@ -0,0 +1,75 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: netvisor +short_description: Use netvisor cliconf to run command on Pluribus netvisor platform +description: + - This netvisor plugin provides low level abstraction apis for + sending and receiving CLI commands from Pluribus netvisor devices. +''' + +import json +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_config(self, source='running', format='text', flags=None): + if source not in ('running'): + return self.invalid_params("fetching configuration from %s is not supported" % source) + cmd = 'show running-config' + return self.send_command(cmd) + + def edit_config(self, command): + return + + def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, newline=True, check_all=False): + if not command: + raise ValueError('must provide value of command to execute') + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_option_values(self): + return { + 'format': ['text'], + 'diff_match': ['line', 'strict', 'exact', 'none'], + 'diff_replace': ['line', 'block'], + 'output': [] + } + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + result['network_api'] = 'cliconf' + result['device_info'] = self.get_device_info() + result.update(self.get_option_values()) + return json.dumps(result) + + def get_device_info(self): + device_info = {} + device_info['network_os'] = 'netvisor' + + return device_info diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/nos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/nos.py new file mode 100644 index 00000000..d7cff899 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/nos.py @@ -0,0 +1,113 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: nos +short_description: Use nos cliconf to run command on Extreme NOS platform +description: + - This nos plugin provides low level abstraction apis for + sending and receiving CLI commands from Extreme NOS network devices. +''' + +import re +import json + +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'nos' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Network Operating System Version: (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + reply = self.get('show chassis') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Chassis Name:(\s+)(\S+)', data, re.M) + if match: + device_info['network_os_model'] = match.group(2) + + reply = self.get('show running-config | inc "switch-attributes host-name"') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_config(self, source='running', flags=None): + if source not in 'running': + raise ValueError("fetching configuration from %s is not supported" % source) + if source == 'running': + cmd = 'show running-config' + + flags = [] if flags is None else flags + cmd += ' '.join(flags) + cmd = cmd.strip() + + return self.send_command(cmd) + + def edit_config(self, command): + resp = {} + results = [] + requests = [] + self.send_command('configure terminal') + for cmd in to_list(command): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + newline = cmd.get('newline', True) + else: + command = cmd + prompt = None + answer = None + newline = True + + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(command, prompt, answer, False, newline)) + requests.append(cmd) + + self.send_command('end') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/routeros.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/routeros.py new file mode 100644 index 00000000..11b54534 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/routeros.py @@ -0,0 +1,79 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: routeros +short_description: Use routeros cliconf to run command on MikroTik RouterOS platform +description: + - This routeros plugin provides low level abstraction apis for + sending and receiving CLI commands from MikroTik RouterOS network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + device_info['network_os'] = 'RouterOS' + + resource = self.get('/system resource print') + data = to_text(resource, errors='surrogate_or_strict').strip() + match = re.search(r'version: (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + routerboard = self.get('/system routerboard print') + data = to_text(routerboard, errors='surrogate_or_strict').strip() + match = re.search(r'model: (.+)$', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + identity = self.get('/system identity print') + data = to_text(identity, errors='surrogate_or_strict').strip() + match = re.search(r'name: (.+)$', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_config(self, source='running', format='text', flags=None): + return + + def edit_config(self, command): + return + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/slxos.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/slxos.py new file mode 100644 index 00000000..053f4a9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/slxos.py @@ -0,0 +1,105 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: slxos +short_description: Use slxos cliconf to run command on Extreme SLX-OS platform +description: + - This slxos plugin provides low level abstraction apis for + sending and receiving CLI commands from Extreme SLX-OS network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'slxos' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'SLX\-OS Operating System Version: (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + reply = self.get('show chassis') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Chassis Name:(\s+)(\S+)', data, re.M) + if match: + device_info['network_os_model'] = match.group(2) + + reply = self.get('show running-config | inc "switch-attributes host-name"') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_config(self, source='running', flags=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + if source == 'running': + cmd = 'show running-config' + else: + cmd = 'show startup-config' + + flags = [] if flags is None else flags + cmd += ' '.join(flags) + cmd = cmd.strip() + + return self.send_command(cmd) + + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + newline = cmd.get('newline', True) + else: + command = cmd + prompt = None + answer = None + newline = True + + self.send_command(command, prompt, answer, False, newline) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/voss.py b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/voss.py new file mode 100644 index 00000000..ad020ede --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/cliconf/voss.py @@ -0,0 +1,236 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +cliconf: voss +short_description: Use voss cliconf to run command on Extreme VOSS platform +description: + - This voss plugin provides low level abstraction apis for + sending and receiving CLI commands from Extreme VOSS network devices. +''' + +import re +import json + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import VossNetworkConfig +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + @enable_mode + def get_config(self, source='running', flags=None, format=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + + if format: + raise ValueError("'format' value %s is not supported for get_config" % format) + + if not flags: + flags = [] + if source == 'running': + cmd = 'show running-config ' + cmd += ' '.join(to_list(flags)) + cmd = cmd.strip() + else: + cmd = 'more /intflash/config.cfg' + + return self.send_command(cmd) + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + """ + Generate diff between candidate and running configuration. If the + remote host supports onbox diff capabilities ie. supports_onbox_diff in that case + candidate and running configurations are not required to be passed as argument. + In case if onbox diff capability is not supported candidate argument is mandatory + and running argument is optional. + :param candidate: The configuration which is expected to be present on remote host. + :param running: The base configuration which is used to generate diff. + :param diff_match: Instructs how to match the candidate configuration with current device configuration + Valid values are 'line', 'strict', 'exact', 'none'. + 'line' - commands are matched line by line + 'strict' - command lines are matched with respect to position + 'exact' - command lines must be an equal match + 'none' - will not compare the candidate configuration with the running configuration + :param diff_ignore_lines: Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + :param path: The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + :param diff_replace: Instructs on the way to perform the configuration on the device. + If the replace argument is set to I(line) then the modified lines are + pushed to the device in configuration mode. If the replace argument is + set to I(block) then the entire command block is pushed to the device in + configuration mode if any line is not correct. + :return: Configuration diff in json format. + { + 'config_diff': '', + } + + """ + diff = {} + + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations['supports_generate_diff']: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values['diff_match']: + raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match']))) + + if diff_replace not in option_values['diff_replace']: + raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace']))) + + # prepare candidate configuration + candidate_obj = VossNetworkConfig(indent=0, ignore_lines=diff_ignore_lines) + candidate_obj.load(candidate) + + if running and diff_match != 'none': + # running configuration + running_obj = VossNetworkConfig(indent=0, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace) + + else: + configdiffobjs = candidate_obj.items + + diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else '' + diff['diff_path'] = path + diff['diff_replace'] = diff_replace + return diff + + @enable_mode + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + resp = {} + operations = self.get_device_operations() + self.check_edit_config_capability(operations, candidate, commit, replace, comment) + + results = [] + requests = [] + if commit: + self.send_command('configure terminal') + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {'command': line} + + cmd = line['command'] + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command('end') + else: + raise ValueError('check mode is not supported') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'voss' + reply = self.get(command='show sys-info') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'SysDescr\s+: \S+ \((\S+)\)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'Chassis\s+: (\S+)', data) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'SysName\s+: (\S+)', data) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_device_operations(self): + return { + 'supports_diff_replace': True, + 'supports_commit': False, + 'supports_rollback': False, + 'supports_defaults': True, + 'supports_onbox_diff': False, + 'supports_commit_comment': False, + 'supports_multiline_delimiter': False, + 'supports_diff_match': True, + 'supports_diff_ignore_lines': True, + 'supports_generate_diff': True, + 'supports_replace': False + } + + def get_option_values(self): + return { + 'format': ['text'], + 'diff_match': ['line', 'strict', 'exact', 'none'], + 'diff_replace': ['line', 'block'], + 'output': [] + } + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result['rpc'] += ['get_diff', 'run_commands', 'get_defaults_flag'] + result['device_operations'] = self.get_device_operations() + result.update(self.get_option_values()) + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', e) + + responses.append(out) + + return responses + + def get_defaults_flag(self): + return 'verbose' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/connection/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/connection/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/a10.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/a10.py new file mode 100644 index 00000000..d6bf5cd7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/a10.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, John Barker +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + host: + description: + - Hostname or IP of the A10 Networks device. + type: str + required: true + username: + description: + - An account with administrator privileges. + type: str + required: true + aliases: [ admin, user ] + password: + description: + - Password for the C(username) account. + type: str + required: true + aliases: [ pass, pwd ] + write_config: + description: + - If C(yes), any changes will cause a write of the running configuration + to non-volatile memory. This will save I(all) configuration changes, + including those that may have been made manually or through other modules, + so care should be taken when specifying C(yes). + type: bool + default: no + validate_certs: + description: + - If C(no), SSL certificates will not be validated. + - This should only be used on personally controlled devices using self-signed certificates. + type: bool + default: yes +notes: + - Requires A10 Networks aXAPI 2.1. +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aireos.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aireos.py new file mode 100644 index 00000000..59e883c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aireos.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, James Mighion <@jmighion> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + provider: + description: + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote device over the specified transport. + - The value of host is used as the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + type: int + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to the remote device. + - This value is used to authenticate the SSH session. + - If the value is not specified in the task, the value of environment variable + C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to the remote device. + - This value is used to authenticate the SSH session. + - If the value is not specified in the task, the value of environment variable + C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. + - If the timeout is exceeded before the operation is completed, the module will error. + type: int + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to the remote device. + - This value is the path to the key used to authenticate the SSH session. + - If the value is not specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE) will be used instead. + type: path +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aruba.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aruba.py new file mode 100644 index 00000000..14476bcb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/aruba.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, James Mighion <@jmighion> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + provider: + description: + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote. + device. + type: int + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + type: int + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + type: path +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/avi.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/avi.py new file mode 100644 index 00000000..e4b5f898 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/avi.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Created on December 12, 2016 +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Avi Version: 16.3.4 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + # Avi common documentation fragment + DOCUMENTATION = r''' +options: + controller: + description: + - IP address or hostname of the controller. The default value is the environment variable C(AVI_CONTROLLER). + type: str + default: '' + username: + description: + - Username used for accessing Avi controller. The default value is the environment variable C(AVI_USERNAME). + type: str + default: '' + password: + description: + - Password of Avi user in Avi controller. The default value is the environment variable C(AVI_PASSWORD). + type: str + default: '' + tenant: + description: + - Name of tenant used for all Avi API calls and context of object. + type: str + default: admin + tenant_uuid: + description: + - UUID of tenant used for all Avi API calls and context of object. + type: str + default: '' + api_version: + description: + - Avi API version of to use for Avi API and objects. + type: str + default: 16.4.4 + avi_credentials: + description: + - Avi Credentials dictionary which can be used in lieu of enumerating Avi Controller login details. + suboptions: + controller: + description: + - Avi controller IP or SQDN + username: + description: + - Avi controller username + password: + description: + - Avi controller password + api_version: + description: + - Avi controller version + default: 16.4.4 + tenant: + description: + - Avi controller tenant + default: admin + tenant_uuid: + description: + - Avi controller tenant UUID + port: + description: + - Avi controller port + token: + description: + - Avi controller API token + timeout: + description: + - Avi controller request timeout + default: 300 + session_id: + description: + - Avi controller API session id to reuse existing session with csrftoken + csrftoken: + description: + - Avi controller API csrftoken to reuse existing session with session id + type: dict + api_context: + description: + - Avi API context that includes current session ID and CSRF Token. + - This allows user to perform single login and re-use the session. + type: dict + avi_disable_session_cache_as_fact: + description: + - It disables avi session information to be cached as a fact. + type: bool + default: false + +notes: + - For more information on using Ansible to manage Avi Network devices see U(https://www.ansible.com/ansible-avi-networks). +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ce.py new file mode 100644 index 00000000..3db766a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ce.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + provider: + description: + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote + device. This value applies to either I(cli) or I(netconf). The port + value will default to the appropriate transport common port if + none is provided in the task. (cli=22, netconf=22). + type: int + default: 0 (use common port) + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate the CLI login. + If the value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This is a common argument used for cli + transports. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This argument is used for the I(cli) + transport. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) will be used instead. + type: path + transport: + description: + - Configures the transport connection to use when connecting to the + remote device. The transport argument supports connectivity to the + device over cli (ssh). + type: str + required: true + choices: [ cli, netconf ] + default: cli +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/cnos.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/cnos.py new file mode 100644 index 00000000..b7776563 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/cnos.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Lenovo, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + # Standard CNOS documentation fragment + DOCUMENTATION = r''' +options: + outputfile: + description: + - This specifies the file path where the output of each command + execution is saved. Each command that is specified in the merged + template file and each response from the device are saved here. + Usually the location is the results folder, but you can + choose another location based on your write permission. + type: str + required: true + host: + description: + - This is the variable used to search the hosts file at + /etc/ansible/hosts and identify the IP address of the device on + which the template is going to be applied. Usually the Ansible + keyword {{ inventory_hostname }} is specified in the playbook as + an abstraction of the group of network elements that need to be + configured. + type: str + required: true + username: + description: + - Configures the username used to authenticate the connection to + the remote device. The value of the username parameter is used to + authenticate the SSH session. While generally the value should + come from the inventory file, you can also specify it as a + variable. This parameter is optional. If it is not specified, no + default value will be used. + type: str + required: true + password: + description: + - Configures the password used to authenticate the connection to + the remote device. The value of the password parameter is used to + authenticate the SSH session. While generally the value should + come from the inventory file, you can also specify it as a + variable. This parameter is optional. If it is not specified, no + default value will be used. + type: str + required: true + enablePassword: + description: + - Configures the password used to enter Global Configuration + command mode on the switch. If the switch does not request this + password, the parameter is ignored.While generally the value + should come from the inventory file, you can also specify it as a + variable. This parameter is optional. If it is not specified, + no default value will be used. + type: str + deviceType: + description: + - This specifies the type of device where the method is executed. + The choices NE1072T,NE1032,NE1032T,NE10032,NE2572 are added + since Ansible 2.4. The choice NE0152T is added since 2.8 + type: str + required: true + choices: + - g8272_cnos + - g8296_cnos + - g8332_cnos + - NE0152T + - NE1072T + - NE1032 + - NE1032T + - NE10032 + - NE2572 +notes: + - For more information on using Ansible to manage Lenovo Network devices see U(https://www.ansible.com/ansible-lenovo). +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/enos.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/enos.py new file mode 100644 index 00000000..4502104a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/enos.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Red Hat Inc. +# Copyright: (c) 2017, Lenovo. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: no + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. + provider: + description: + - A dict object containing connection details. + type: dict + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + type: int + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + type: int + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + type: path + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: no + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. + type: str +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ingate.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ingate.py new file mode 100644 index 00000000..8132d496 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ingate.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ingate Systems AB +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r''' +options: + client: + description: + - A dict object containing connection details. + suboptions: + version: + description: + - REST API version. + type: str + choices: [ v1 ] + default: v1 + scheme: + description: + - Which HTTP protocol to use. + type: str + required: true + choices: [ http, https ] + address: + description: + - The hostname or IP address to the unit. + type: str + required: true + username: + description: + - The username of the REST API user. + type: str + required: true + password: + description: + - The password for the REST API user. + type: str + required: true + port: + description: + - Which HTTP(S) port to connect to. + type: int + timeout: + description: + - The timeout (in seconds) for REST API requests. + type: int + validate_certs: + description: + - Verify the unit's HTTPS certificate. + type: bool + default: yes + aliases: [ verify_ssl ] +notes: + - This module requires that the Ingate Python SDK is installed on the + host. To install the SDK use the pip command from your shell + C(pip install ingatesdk). +requirements: + - ingatesdk >= 1.0.6 +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ironware.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ironware.py new file mode 100644 index 00000000..29eff80f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/ironware.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Paul Baker <@paulquack> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + authorize: + description: + - B(Deprecated) + - "Starting with Ansible 2.7 we recommend using C(connection: network_cli) and C(become: yes)." + - For more information please see the L(IronWare Platform Options guide, ../network/user_guide/platform_ironware.html). + - HORIZONTALLINE + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: no + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.7 we recommend using C(connection: network_cli) and C(become: yes)." + - For more information please see the L(IronWare Platform Options guide, ../network/user_guide/platform_ironware.html). + - HORIZONTALLINE + - A dict object containing connection details. + type: dict + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + port: + description: + - Specifies the port to use when building the connection to the remote + device. + type: int + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + type: path + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: no + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. + type: str + timeout: + description: + - Specifies idle timeout in seconds for the connection, in seconds. Useful + if the console freezes before continuing. For example when saving + configurations. + type: int + default: 10 +notes: + - For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide ` +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/netscaler.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/netscaler.py new file mode 100644 index 00000000..7f19053a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/netscaler.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r''' + +options: + nsip: + description: + - The ip address of the netscaler appliance where the nitro API calls will be made. + - "The port can be specified with the colon (:). E.g. 192.168.1.1:555." + type: str + required: True + + nitro_user: + description: + - The username with which to authenticate to the netscaler node. + type: str + required: True + + nitro_pass: + description: + - The password with which to authenticate to the netscaler node. + type: str + required: True + + nitro_protocol: + description: + - Which protocol to use when accessing the nitro API objects. + type: str + choices: [ http, https ] + default: http + + validate_certs: + description: + - 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 + + nitro_timeout: + description: + - Time in seconds until a timeout error is thrown when establishing a new session with Netscaler + type: float + default: 310 + + state: + description: + - The state of the resource being configured by the module on the netscaler node. + - When present the resource will be created if needed and configured according to the module's parameters. + - When absent the resource will be deleted from the netscaler node. + type: str + choices: [ absent, present ] + default: present + + save_config: + description: + - If C(yes) the module will save the configuration on the netscaler node if it makes any changes. + - The module will not save the configuration on the netscaler node if it made no changes. + type: bool + default: yes +notes: + - For more information on using Ansible to manage Citrix NetScaler Network devices see U(https://www.ansible.com/ansible-netscaler). +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/nso.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/nso.py new file mode 100644 index 00000000..74c16a2c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/nso.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Cisco and/or its affiliates. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + DOCUMENTATION = r''' +options: + url: + description: NSO JSON-RPC URL, http://localhost:8080/jsonrpc + type: str + required: true + username: + description: NSO username + type: str + required: true + password: + description: NSO password + type: str + required: true + timeout: + description: JSON-RPC request timeout in seconds + type: int + default: 300 + validate_certs: + description: When set to true, validates the SSL certificate of NSO when + using SSL + type: bool + required: false + default: false +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/panos.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/panos.py new file mode 100644 index 00000000..870b99b5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/panos.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, techbizdev +# Copyright: (c) 2018, Kevin Breit (@kbreit) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device. + type: str + required: true + password: + description: + - Password for authentication. + type: str + required: true + username: + description: + - Username for authentication. + type: str + default: admin +''' + + PROVIDER = r''' +options: + provider: + description: + - A dict object containing connection details. + required: true + suboptions: + ip_address: + description: + - The IP address or hostname of the PAN-OS device being configured. + type: str + required: true + username: + description: + - The username to use for authentication. This is ignored if + I(api_key) is specified. + type: str + default: 'admin' + password: + description: + - The password to use for authentication. This is ignored if + I(api_key) is specified. + type: str + api_key: + description: + - The API key to use instead of generating it using + I(username) / I(password). + type: str + port: + description: + - The port number to connect to the PAN-OS device on. + type: int + default: 443 + serial_number: + description: + - The serial number of a firewall to use for targeted commands. + If I(ip_address) is not a Panorama PAN-OS device, then + this param is ignored. + type: str +''' + + TRANSITIONAL_PROVIDER = r''' +options: + provider: + description: + - A dict object containing connection details. + suboptions: + ip_address: + description: + - The IP address or hostname of the PAN-OS device being configured. + type: str + username: + description: + - The username to use for authentication. This is ignored if + I(api_key) is specified. + type: str + default: 'admin' + password: + description: + - The password to use for authentication. This is ignored if + I(api_key) is specified. + type: str + api_key: + description: + - The API key to use instead of generating it using + I(username) / I(password). + type: str + port: + description: + - The port number to connect to the PAN-OS device on. + type: int + default: 443 + serial_number: + description: + - The serial number of a firewall to use for targeted commands. + If I(ip_address) is not a Panorama PAN-OS device, then + this param is ignored. + type: str + ip_address: + description: + - B(Deprecated) + - Use I(provider) to specify PAN-OS connectivity instead. + - HORIZONTALLINE + - The IP address or hostname of the PAN-OS device being configured. + type: str + username: + description: + - B(Deprecated) + - Use I(provider) to specify PAN-OS connectivity instead. + - HORIZONTALLINE + - The username to use for authentication. This is ignored if + I(api_key) is specified. + type: str + default: 'admin' + password: + description: + - B(Deprecated) + - Use I(provider) to specify PAN-OS connectivity instead. + - HORIZONTALLINE + - The password to use for authentication. This is ignored if + I(api_key) is specified. + type: str + api_key: + description: + - B(Deprecated) + - Use I(provider) to specify PAN-OS connectivity instead. + - HORIZONTALLINE + - The API key to use instead of generating it using + I(username) / I(password). + type: str + port: + description: + - B(Deprecated) + - Use I(provider) to specify PAN-OS connectivity instead. + - HORIZONTALLINE + - The port number to connect to the PAN-OS device on. + type: int + default: 443 +notes: + - PAN-OS connectivity should be specified using I(provider) or the + classic PAN-OS connectivity params (I(ip_address), I(username), + I(password), I(api_key), and I(port)). If both are present, then the + classic params are ignored. +''' + + STATE = r''' +options: + state: + description: + - The state. + type: str + default: present + choices: + - present + - absent +''' + + RULEBASE = r''' +options: + rulebase: + description: + - The rulebase in which the rule is to exist. If left unspecified, + this defaults to I(rulebase=pre-rulebase) for Panorama. For + NGFW, this is always set to be I(rulebase=rulebase). + type: str + choices: + - pre-rulebase + - rulebase + - post-rulebase +''' + + VSYS_DG = r''' +options: + vsys_dg: + description: + - The vsys (for NGFW) or device group (for Panorama) this + operation should target. If left unspecified, this defaults to + I(vsys_dg=vsys1) for NGFW or I(vsys_dg=shared) for Panorama. + type: str +''' + + DEVICE_GROUP = r''' +options: + device_group: + description: + - (Panorama only) The device group the operation should target. + type: str + default: shared +''' + + VSYS_IMPORT = r''' +options: + vsys: + description: + - The vsys this object should be imported into. Objects that are + imported include interfaces, virtual routers, virtual wires, and + VLANs. Interfaces are typically imported into vsys1 if no vsys + is specified. + type: str +''' + + VSYS = r''' +options: + vsys: + description: + - The vsys this object belongs to. + type: str + default: vsys1 +''' + + TEMPLATE_ONLY = r''' +options: + template: + description: + - (Panorama only) The template this operation should target. This + param is required if the PAN-OS device is Panorama. + type: str +''' + + FULL_TEMPLATE_SUPPORT = r''' +options: + template: + description: + - (Panorama only) The template this operation should target. + Mutually exclusive with I(template_stack). + type: str + template_stack: + description: + - (Panorama only) The template stack this operation should target. + Mutually exclusive with I(template). + type: str +notes: + - If the PAN-OS to be configured is Panorama, either I(template) or + I(template_stack) must be specified. +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/sros.py b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/sros.py new file mode 100644 index 00000000..377d141f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/doc_fragments/sros.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Peter Sprygada +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + provider: + description: + - A dict object containing connection details. + type: dict + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote + device. + type: int + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + type: int + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + type: path +notes: + - For more information on using Ansible to manage Nokia SR OS Network devices see U(https://www.ansible.com/ansible-nokia). +''' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/filter/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/filter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/exos.py b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/exos.py new file mode 100644 index 00000000..10d25dd4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/exos.py @@ -0,0 +1,252 @@ +# Copyright (c) 2019 Extreme Networks. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: + - "Ujwal Komarla (@ujwalkomarla)" +httpapi: exos +short_description: Use EXOS REST APIs to communicate with EXOS platform +description: + - This plugin provides low level abstraction api's to send REST API + requests to EXOS network devices and receive JSON responses. +''' + +import json +import re +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.httpapi import HttpApiBase +import ansible.module_utils.six.moves.http_cookiejar as cookiejar +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +class HttpApi(HttpApiBase): + + def __init__(self, *args, **kwargs): + super(HttpApi, self).__init__(*args, **kwargs) + self._device_info = None + self._auth_token = cookiejar.CookieJar() + + def login(self, username, password): + auth_path = '/auth/token' + credentials = {'username': username, 'password': password} + self.send_request(path=auth_path, data=json.dumps(credentials), method='POST') + + def logout(self): + pass + + def handle_httperror(self, exc): + return False + + def send_request(self, path, data=None, method='GET', **message_kwargs): + headers = {'Content-Type': 'application/json'} + response, response_data = self.connection.send(path, data, method=method, cookies=self._auth_token, headers=headers, **message_kwargs) + try: + if response.status == 204: + response_data = {} + else: + response_data = json.loads(to_text(response_data.getvalue())) + except ValueError: + raise ConnectionError('Response was not valid JSON, got {0}'.format( + to_text(response_data.getvalue()) + )) + return response_data + + def run_commands(self, commands, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + headers = {'Content-Type': 'application/json'} + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + cmd['command'] = strip_run_script_cli2json(cmd['command']) + + output = cmd.pop('output', None) + if output and output not in self.get_option_values().get('output'): + raise ValueError("'output' value is %s is invalid. Valid values are %s" % (output, ','.join(self.get_option_values().get('output')))) + + data = request_builder(cmd['command']) + + response, response_data = self.connection.send('/jsonrpc', data, cookies=self._auth_token, headers=headers, method='POST') + try: + response_data = json.loads(to_text(response_data.getvalue())) + except ValueError: + raise ConnectionError('Response was not valid JSON, got {0}'.format( + to_text(response_data.getvalue()) + )) + + if response_data.get('error', None): + raise ConnectionError("Request Error, got {0}".format(response_data['error'])) + if not response_data.get('result', None): + raise ConnectionError("Request Error, got {0}".format(response_data)) + + response_data = response_data['result'] + + if output and output == 'text': + statusOut = getKeyInResponse(response_data, 'status') + cliOut = getKeyInResponse(response_data, 'CLIoutput') + if statusOut == "ERROR": + raise ConnectionError("Command error({1}) for request {0}".format(cmd['command'], cliOut)) + if cliOut is None: + raise ValueError("Response for request {0} doesn't have the CLIoutput field, got {1}".format(cmd['command'], response_data)) + response_data = cliOut + + responses.append(response_data) + return responses + + def get_device_info(self): + device_info = {} + device_info['network_os'] = 'exos' + + reply = self.run_commands({'command': 'show switch detail', 'output': 'text'}) + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'ExtremeXOS version (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'System Type: +(\S+)', data) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'SysName: +(\S+)', data) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_device_operations(self): + return { + 'supports_diff_replace': False, # identify if config should be merged or replaced is supported + 'supports_commit': False, # identify if commit is supported by device or not + 'supports_rollback': False, # identify if rollback is supported or not + 'supports_defaults': True, # identify if fetching running config with default is supported + 'supports_commit_comment': False, # identify if adding comment to commit is supported of not + 'supports_onbox_diff': False, # identify if on box diff capability is supported or not + 'supports_generate_diff': True, # identify if diff capability is supported within plugin + 'supports_multiline_delimiter': False, # identify if multiline demiliter is supported within config + 'supports_diff_match': True, # identify if match is supported + 'supports_diff_ignore_lines': True, # identify if ignore line in diff is supported + 'supports_config_replace': False, # identify if running config replace with candidate config is supported + 'supports_admin': False, # identify if admin configure mode is supported or not + 'supports_commit_label': False # identify if commit label is supported or not + } + + def get_option_values(self): + return { + 'format': ['text', 'json'], + 'diff_match': ['line', 'strict', 'exact', 'none'], + 'diff_replace': ['line', 'block'], + 'output': ['text', 'json'] + } + + def get_capabilities(self): + result = {} + result['rpc'] = ['get_default_flag', 'run_commands', 'get_config', 'send_request', 'get_capabilities', 'get_diff'] + result['device_info'] = self.get_device_info() + result['device_operations'] = self.get_device_operations() + result.update(self.get_option_values()) + result['network_api'] = 'exosapi' + return json.dumps(result) + + def get_default_flag(self): + # The flag to modify the command to collect configuration with defaults + return 'detail' + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + diff = {} + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations['supports_generate_diff']: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values['diff_match']: + raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match']))) + + if diff_replace not in option_values['diff_replace']: + raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace']))) + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=1) + candidate_obj.load(candidate) + + if running and diff_match != 'none' and diff_replace != 'config': + # running configuration + running_obj = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace) + + else: + configdiffobjs = candidate_obj.items + + diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else '' + return diff + + def get_config(self, source='running', format='text', flags=None): + options_values = self.get_option_values() + if format not in options_values['format']: + raise ValueError("'format' value %s is invalid. Valid values are %s" % (format, ','.join(options_values['format']))) + + lookup = {'running': 'show configuration', 'startup': 'debug cfgmgr show configuration file'} + if source not in lookup: + raise ValueError("fetching configuration from %s is not supported" % source) + + cmd = {'command': lookup[source], 'output': 'text'} + + if source == 'startup': + reply = self.run_commands({'command': 'show switch', 'format': 'text'}) + data = to_text(reply, errors='surrogate_or_strict').strip() + match = re.search(r'Config Selected: +(\S+)\.cfg', data, re.MULTILINE) + if match: + cmd['command'] += match.group(1) + else: + # No Startup(/Selected) Config + return {} + + cmd['command'] += ' '.join(to_list(flags)) + cmd['command'] = cmd['command'].strip() + + return self.run_commands(cmd)[0] + + +def request_builder(command, reqid=""): + return json.dumps(dict(jsonrpc='2.0', id=reqid, method='cli', params=to_list(command))) + + +def strip_run_script_cli2json(command): + if to_text(command, errors="surrogate_then_replace").startswith('run script cli2json.py'): + command = str(command).replace('run script cli2json.py', '') + return command + + +def getKeyInResponse(response, key): + keyOut = None + for item in response: + if key in item: + keyOut = item[key] + break + return keyOut diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortianalyzer.py b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortianalyzer.py new file mode 100644 index 00000000..6eb42eef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortianalyzer.py @@ -0,0 +1,453 @@ +# Copyright (c) 2018 Fortinet and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +httpapi : fortianalyzer +short_description: HttpApi Plugin for Fortinet FortiAnalyzer Appliance or VM. +description: + - This HttpApi plugin provides methods to connect to Fortinet FortiAnalyzer Appliance or VM via JSON RPC API. + +''' + +import json +from ansible.plugins.httpapi import HttpApiBase +from ansible.module_utils.basic import to_text +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import BASE_HEADERS +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZBaseException +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZCommon +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZMethods + + +class HttpApi(HttpApiBase): + def __init__(self, connection): + super(HttpApi, self).__init__(connection) + self._req_id = 0 + self._sid = None + self._url = "/jsonrpc" + self._host = None + self._tools = FAZCommon + self._debug = False + self._connected_faz = None + self._last_response_msg = None + self._last_response_code = None + self._last_data_payload = None + self._last_url = None + self._last_response_raw = None + self._locked_adom_list = list() + self._locked_adoms_by_user = list() + self._uses_workspace = False + self._uses_adoms = False + self._adom_list = list() + self._logged_in_user = None + + def set_become(self, become_context): + """ + ELEVATION IS NOT REQUIRED ON FORTINET DEVICES - SKIPPED + :param become_context: Unused input. + :return: None + """ + return None + + def update_auth(self, response, response_data): + """ + TOKENS ARE NOT USED SO NO NEED TO UPDATE AUTH + :param response: Unused input. + :param response_data Unused_input. + :return: None + """ + return None + + def login(self, username, password): + """ + This function will log the plugin into FortiAnalyzer, and return the results. + :param username: Username of FortiAnalyzer Admin + :param password: Password of FortiAnalyzer Admin + + :return: Dictionary of status if it logged in or not. + """ + + self._logged_in_user = username + self.send_request(FAZMethods.EXEC, self._tools.format_request(FAZMethods.EXEC, "sys/login/user", + passwd=password, user=username,)) + + if "FortiAnalyzer object connected to FortiAnalyzer" in self.__str__(): + # If Login worked then inspect the FortiAnalyzer for Workspace Mode, and it's system information. + self.inspect_faz() + return + else: + raise FAZBaseException(msg="Unknown error while logging in...connection was lost during login operation..." + " Exiting") + + def inspect_faz(self): + # CHECK FOR WORKSPACE MODE TO SEE IF WE HAVE TO ENABLE ADOM LOCKS + status = self.get_system_status() + if status[0] == -11: + # THE CONNECTION GOT LOST SOMEHOW, REMOVE THE SID AND REPORT BAD LOGIN + self.logout() + raise FAZBaseException(msg="Error -11 -- the Session ID was likely malformed somehow. Contact authors." + " Exiting") + elif status[0] == 0: + try: + self.check_mode() + if self._uses_adoms: + self.get_adom_list() + if self._uses_workspace: + self.get_locked_adom_list() + self._connected_faz = status[1] + self._host = self._connected_faz["Hostname"] + except Exception: + pass + return + + def logout(self): + """ + This function will logout of the FortiAnalyzer. + """ + if self.sid is not None: + # IF WE WERE USING WORKSPACES, THEN CLEAN UP OUR LOCKS IF THEY STILL EXIST + if self.uses_workspace: + self.get_lock_info() + self.run_unlock() + ret_code, response = self.send_request(FAZMethods.EXEC, + self._tools.format_request(FAZMethods.EXEC, "sys/logout")) + self.sid = None + return ret_code, response + + def send_request(self, method, params): + """ + Responsible for actual sending of data to the connection httpapi base plugin. Does some formatting as well. + :param params: A formatted dictionary that was returned by self.common_datagram_params() + before being called here. + :param method: The preferred API Request method (GET, ADD, POST, etc....) + :type method: basestring + + :return: Dictionary of status if it logged in or not. + """ + + try: + if self.sid is None and params[0]["url"] != "sys/login/user": + try: + self.connection._connect() + except Exception as err: + raise FAZBaseException( + msg="An problem happened with the httpapi plugin self-init connection process. " + "Error: " + to_text(err)) + except IndexError: + raise FAZBaseException("An attempt was made at communicating with a FAZ with " + "no valid session and an incorrectly formatted request.") + except Exception: + raise FAZBaseException("An attempt was made at communicating with a FAZ with " + "no valid session and an unexpected error was discovered.") + + self._update_request_id() + json_request = { + "method": method, + "params": params, + "session": self.sid, + "id": self.req_id, + "verbose": 1 + } + data = json.dumps(json_request, ensure_ascii=False).replace('\\\\', '\\') + try: + # Sending URL and Data in Unicode, per Ansible Specifications for Connection Plugins + response, response_data = self.connection.send(path=to_text(self._url), data=to_text(data), + headers=BASE_HEADERS) + # Get Unicode Response - Must convert from StringIO to unicode first so we can do a replace function below + result = json.loads(to_text(response_data.getvalue())) + self._update_self_from_response(result, self._url, data) + return self._handle_response(result) + except Exception as err: + raise FAZBaseException(err) + + def _handle_response(self, response): + self._set_sid(response) + if isinstance(response["result"], list): + result = response["result"][0] + else: + result = response["result"] + if "data" in result: + return result["status"]["code"], result["data"] + else: + return result["status"]["code"], result + + def _update_self_from_response(self, response, url, data): + self._last_response_raw = response + if isinstance(response["result"], list): + result = response["result"][0] + else: + result = response["result"] + if "status" in result: + self._last_response_code = result["status"]["code"] + self._last_response_msg = result["status"]["message"] + self._last_url = url + self._last_data_payload = data + + def _set_sid(self, response): + if self.sid is None and "session" in response: + self.sid = response["session"] + + def return_connected_faz(self): + """ + Returns the data stored under self._connected_faz + + :return: dict + """ + try: + if self._connected_faz: + return self._connected_faz + except Exception: + raise FAZBaseException("Couldn't Retrieve Connected FAZ Stats") + + def get_system_status(self): + """ + Returns the system status page from the FortiAnalyzer, for logging and other uses. + return: status + """ + status = self.send_request(FAZMethods.GET, self._tools.format_request(FAZMethods.GET, "sys/status")) + return status + + @property + def debug(self): + return self._debug + + @debug.setter + def debug(self, val): + self._debug = val + + @property + def req_id(self): + return self._req_id + + @req_id.setter + def req_id(self, val): + self._req_id = val + + def _update_request_id(self, reqid=0): + self.req_id = reqid if reqid != 0 else self.req_id + 1 + + @property + def sid(self): + return self._sid + + @sid.setter + def sid(self, val): + self._sid = val + + def __str__(self): + if self.sid is not None and self.connection._url is not None: + return "FortiAnalyzer object connected to FortiAnalyzer: " + to_text(self.connection._url) + return "FortiAnalyzer object with no valid connection to a FortiAnalyzer appliance." + + ################################## + # BEGIN DATABASE LOCK CONTEXT CODE + ################################## + + @property + def uses_workspace(self): + return self._uses_workspace + + @uses_workspace.setter + def uses_workspace(self, val): + self._uses_workspace = val + + @property + def uses_adoms(self): + return self._uses_adoms + + @uses_adoms.setter + def uses_adoms(self, val): + self._uses_adoms = val + + def add_adom_to_lock_list(self, adom): + if adom not in self._locked_adom_list: + self._locked_adom_list.append(adom) + + def remove_adom_from_lock_list(self, adom): + if adom in self._locked_adom_list: + self._locked_adom_list.remove(adom) + + def check_mode(self): + """ + Checks FortiAnalyzer for the use of Workspace mode + """ + url = "/cli/global/system/global" + code, resp_obj = self.send_request(FAZMethods.GET, + self._tools.format_request(FAZMethods.GET, + url, + fields=["workspace-mode", "adom-status"])) + try: + if resp_obj["workspace-mode"] == "workflow": + self.uses_workspace = True + elif resp_obj["workspace-mode"] == "disabled": + self.uses_workspace = False + except KeyError: + self.uses_workspace = False + except Exception: + raise FAZBaseException(msg="Couldn't determine workspace-mode in the plugin") + try: + if resp_obj["adom-status"] in [1, "enable"]: + self.uses_adoms = True + else: + self.uses_adoms = False + except KeyError: + self.uses_adoms = False + except Exception: + raise FAZBaseException(msg="Couldn't determine adom-status in the plugin") + + def run_unlock(self): + """ + Checks for ADOM status, if locked, it will unlock + """ + for adom_locked in self._locked_adoms_by_user: + adom = adom_locked["adom"] + self.unlock_adom(adom) + + def lock_adom(self, adom=None, *args, **kwargs): + """ + Locks an ADOM for changes + """ + if adom: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lock/" + else: + url = "/dvmdb/adom/{adom}/workspace/lock/".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/lock" + code, respobj = self.send_request(FAZMethods.EXEC, self._tools.format_request(FAZMethods.EXEC, url)) + if code == 0 and respobj["status"]["message"].lower() == "ok": + self.add_adom_to_lock_list(adom) + return code, respobj + + def unlock_adom(self, adom=None, *args, **kwargs): + """ + Unlocks an ADOM after changes + """ + if adom: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/unlock/" + else: + url = "/dvmdb/adom/{adom}/workspace/unlock/".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/unlock" + code, respobj = self.send_request(FAZMethods.EXEC, self._tools.format_request(FAZMethods.EXEC, url)) + if code == 0 and respobj["status"]["message"].lower() == "ok": + self.remove_adom_from_lock_list(adom) + return code, respobj + + def commit_changes(self, adom=None, aux=False, *args, **kwargs): + """ + Commits changes to an ADOM + """ + if adom: + if aux: + url = "/pm/config/adom/{adom}/workspace/commit".format(adom=adom) + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/commit/" + else: + url = "/dvmdb/adom/{adom}/workspace/commit".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/commit" + return self.send_request(FAZMethods.EXEC, self._tools.format_request(FAZMethods.EXEC, url)) + + def get_lock_info(self, adom=None): + """ + Gets ADOM lock info so it can be displayed with the error messages. Or if determined to be locked by ansible + for some reason, then unlock it. + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/lockinfo" + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lockinfo/" + else: + url = "/dvmdb/adom/{adom}/workspace/lockinfo/".format(adom=adom) + datagram = {} + data = self._tools.format_request(FAZMethods.GET, url, **datagram) + resp_obj = self.send_request(FAZMethods.GET, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to get the ADOM Lock Info. Error: " + to_text(resp_obj))) + elif code == 0: + try: + if resp_obj[1]["status"]["message"] == "OK": + self._lock_info = None + except Exception: + self._lock_info = resp_obj[1] + return resp_obj + + def get_adom_list(self): + """ + Gets the list of ADOMs for the FortiAnalyzer + """ + if self.uses_adoms: + url = "/dvmdb/adom" + datagram = {} + data = self._tools.format_request(FAZMethods.GET, url, **datagram) + resp_obj = self.send_request(FAZMethods.GET, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to get the ADOM Info. Error: " + to_text(resp_obj))) + elif code == 0: + num_of_adoms = len(resp_obj[1]) + append_list = ['root', ] + for adom in resp_obj[1]: + if adom["tab_status"] != "": + append_list.append(to_text(adom["name"])) + self._adom_list = append_list + return resp_obj + + def get_locked_adom_list(self): + """ + Gets the list of locked adoms + """ + try: + locked_list = list() + locked_by_user_list = list() + for adom in self._adom_list: + adom_lock_info = self.get_lock_info(adom=adom) + try: + if adom_lock_info[1]["status"]["message"] == "OK": + continue + except Exception: + pass + try: + if adom_lock_info[1][0]["lock_user"]: + locked_list.append(to_text(adom)) + if adom_lock_info[1][0]["lock_user"] == self._logged_in_user: + locked_by_user_list.append({"adom": to_text(adom), "user": to_text(adom_lock_info[1][0]["lock_user"])}) + except Exception as err: + raise FAZBaseException(err) + self._locked_adom_list = locked_list + self._locked_adoms_by_user = locked_by_user_list + + except Exception as err: + raise FAZBaseException(msg=("An error occurred while trying to get the locked adom list. Error: " + + to_text(err))) + + ################################# + # END DATABASE LOCK CONTEXT CODE + ################################# diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortimanager.py b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortimanager.py new file mode 100644 index 00000000..6ca9a42e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/fortimanager.py @@ -0,0 +1,459 @@ +# Copyright (c) 2018 Fortinet and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +httpapi : fortimanager +short_description: HttpApi Plugin for Fortinet FortiManager Appliance or VM. +description: + - This HttpApi plugin provides methods to connect to Fortinet FortiManager Appliance or VM via JSON RPC API. +''' + +import json +from ansible.errors import AnsibleError +from ansible.plugins.httpapi import HttpApiBase +from ansible.module_utils.basic import to_text + +try: + from ansible_collections.fortinet.fortimanager.plugins.module_utils.common import BASE_HEADERS + from ansible_collections.fortinet.fortimanager.plugins.module_utils.common import FMGBaseException + from ansible_collections.fortinet.fortimanager.plugins.module_utils.common import FMGRCommon + from ansible_collections.fortinet.fortimanager.plugins.module_utils.common import FMGRMethods + HAS_FORTIMANAGER_COLLECTION = True +except ImportError: + HAS_FORTIMANAGER_COLLECTION = False + + +class HttpApi(HttpApiBase): + def __init__(self, connection): + super(HttpApi, self).__init__(connection) + self._req_id = 0 + self._sid = None + self._url = "/jsonrpc" + self._host = None + self._tools = FMGRCommon + self._debug = False + self._connected_fmgr = None + self._last_response_msg = None + self._last_response_code = None + self._last_data_payload = None + self._last_url = None + self._last_response_raw = None + self._locked_adom_list = list() + self._locked_adoms_by_user = list() + self._uses_workspace = False + self._uses_adoms = False + self._adom_list = list() + self._logged_in_user = None + if not HAS_FORTIMANAGER_COLLECTION: + raise AnsibleError("The community.network.fortimanager httpapi plugin requires the fortios.fortimanager collection.") + + def set_become(self, become_context): + """ + ELEVATION IS NOT REQUIRED ON FORTINET DEVICES - SKIPPED. + :param become_context: Unused input. + :return: None + """ + return None + + def update_auth(self, response, response_data): + """ + TOKENS ARE NOT USED SO NO NEED TO UPDATE AUTH. + :param response: Unused input. + :param response_data Unused_input. + :return: None + """ + return None + + def login(self, username, password): + + """ + This function will log the plugin into FortiManager, and return the results. + :param username: Username of FortiManager Admin + :param password: Password of FortiManager Admin + + :return: Dictionary of status if it logged in or not. + """ + self._logged_in_user = username + self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, "sys/login/user", + passwd=password, user=username, )) + + if "FortiManager object connected to FortiManager" in self.__str__(): + # If Login worked, then inspect the FortiManager for Workspace Mode, and it's system information. + self.inspect_fmgr() + return + else: + raise FMGBaseException(msg="Unknown error while logging in...connection was lost during login operation...." + " Exiting") + + def inspect_fmgr(self): + # CHECK FOR WORKSPACE MODE TO SEE IF WE HAVE TO ENABLE ADOM LOCKS + status = self.get_system_status() + if status[0] == -11: + # THE CONNECTION GOT LOST SOMEHOW, REMOVE THE SID AND REPORT BAD LOGIN + self.logout() + raise FMGBaseException(msg="Error -11 -- the Session ID was likely malformed somehow. Contact authors." + " Exiting") + elif status[0] == 0: + try: + self.check_mode() + if self._uses_adoms: + self.get_adom_list() + if self._uses_workspace: + self.get_locked_adom_list() + self._connected_fmgr = status[1] + self._host = self._connected_fmgr["Hostname"] + except BaseException: + pass + return + + def logout(self): + """ + This function will logout of the FortiManager. + """ + if self.sid is not None: + # IF WE WERE USING WORKSPACES, THEN CLEAN UP OUR LOCKS IF THEY STILL EXIST + if self.uses_workspace: + self.get_lock_info() + self.run_unlock() + ret_code, response = self.send_request(FMGRMethods.EXEC, + self._tools.format_request(FMGRMethods.EXEC, "sys/logout")) + self.sid = None + return ret_code, response + + def send_request(self, method, params): + """ + Responsible for actual sending of data to the connection httpapi base plugin. Does some formatting too. + :param params: A formatted dictionary that was returned by self.common_datagram_params() + before being called here. + :param method: The preferred API Request method (GET, ADD, POST, etc....) + :type method: basestring + + :return: Dictionary of status, if it logged in or not. + """ + try: + if self.sid is None and params[0]["url"] != "sys/login/user": + try: + self.connection._connect() + except Exception as err: + raise FMGBaseException( + msg="An problem happened with the httpapi plugin self-init connection process. " + "Error: " + to_text(err)) + except IndexError: + raise FMGBaseException("An attempt was made at communicating with a FMG with " + "no valid session and an incorrectly formatted request.") + except Exception as err: + raise FMGBaseException("An attempt was made at communicating with a FMG with " + "no valid session and an unexpected error was discovered. \n Error: " + to_text(err)) + + self._update_request_id() + json_request = { + "method": method, + "params": params, + "session": self.sid, + "id": self.req_id, + "verbose": 1 + } + data = json.dumps(json_request, ensure_ascii=False).replace('\\\\', '\\') + try: + # Sending URL and Data in Unicode, per Ansible Specifications for Connection Plugins + response, response_data = self.connection.send(path=to_text(self._url), data=to_text(data), + headers=BASE_HEADERS) + # Get Unicode Response - Must convert from StringIO to unicode first so we can do a replace function below + result = json.loads(to_text(response_data.getvalue())) + self._update_self_from_response(result, self._url, data) + return self._handle_response(result) + except Exception as err: + raise FMGBaseException(err) + + def _handle_response(self, response): + self._set_sid(response) + if isinstance(response["result"], list): + result = response["result"][0] + else: + result = response["result"] + if "data" in result: + return result["status"]["code"], result["data"] + else: + return result["status"]["code"], result + + def _update_self_from_response(self, response, url, data): + self._last_response_raw = response + if isinstance(response["result"], list): + result = response["result"][0] + else: + result = response["result"] + if "status" in result: + self._last_response_code = result["status"]["code"] + self._last_response_msg = result["status"]["message"] + self._last_url = url + self._last_data_payload = data + + def _set_sid(self, response): + if self.sid is None and "session" in response: + self.sid = response["session"] + + def return_connected_fmgr(self): + """ + Returns the data stored under self._connected_fmgr + + :return: dict + """ + try: + if self._connected_fmgr: + return self._connected_fmgr + except Exception: + raise FMGBaseException("Couldn't Retrieve Connected FMGR Stats") + + def get_system_status(self): + """ + Returns the system status page from the FortiManager, for logging and other uses. + return: status + """ + status = self.send_request(FMGRMethods.GET, self._tools.format_request(FMGRMethods.GET, "sys/status")) + return status + + @property + def debug(self): + return self._debug + + @debug.setter + def debug(self, val): + self._debug = val + + @property + def req_id(self): + return self._req_id + + @req_id.setter + def req_id(self, val): + self._req_id = val + + def _update_request_id(self, reqid=0): + self.req_id = reqid if reqid != 0 else self.req_id + 1 + + @property + def sid(self): + return self._sid + + @sid.setter + def sid(self, val): + self._sid = val + + def __str__(self): + if self.sid is not None and self.connection._url is not None: + return "FortiManager object connected to FortiManager: " + to_text(self.connection._url) + return "FortiManager object with no valid connection to a FortiManager appliance." + + ################################## + # BEGIN DATABASE LOCK CONTEXT CODE + ################################## + + @property + def uses_workspace(self): + return self._uses_workspace + + @uses_workspace.setter + def uses_workspace(self, val): + self._uses_workspace = val + + @property + def uses_adoms(self): + return self._uses_adoms + + @uses_adoms.setter + def uses_adoms(self, val): + self._uses_adoms = val + + def add_adom_to_lock_list(self, adom): + if adom not in self._locked_adom_list: + self._locked_adom_list.append(adom) + + def remove_adom_from_lock_list(self, adom): + if adom in self._locked_adom_list: + self._locked_adom_list.remove(adom) + + def check_mode(self): + """ + Checks FortiManager for the use of Workspace mode + """ + url = "/cli/global/system/global" + code, resp_obj = self.send_request(FMGRMethods.GET, + self._tools.format_request(FMGRMethods.GET, + url, + fields=["workspace-mode", "adom-status"])) + try: + if resp_obj["workspace-mode"] == "workflow": + self.uses_workspace = True + elif resp_obj["workspace-mode"] == "disabled": + self.uses_workspace = False + except KeyError: + raise FMGBaseException(msg="Couldn't determine workspace-mode in the plugin") + try: + if resp_obj["adom-status"] in [1, "enable"]: + self.uses_adoms = True + else: + self.uses_adoms = False + except KeyError: + raise FMGBaseException(msg="Couldn't determine adom-status in the plugin") + + def run_unlock(self): + """ + Checks for ADOM status, if locked, it will unlock + """ + for adom_locked in self._locked_adoms_by_user: + adom = adom_locked["adom"] + self.unlock_adom(adom) + + def lock_adom(self, adom=None, *args, **kwargs): + """ + Locks an ADOM for changes + """ + if adom: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lock/" + else: + url = "/dvmdb/adom/{adom}/workspace/lock/".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/lock" + code, respobj = self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, url)) + if code == 0 and respobj["status"]["message"].lower() == "ok": + self.add_adom_to_lock_list(adom) + return code, respobj + + def unlock_adom(self, adom=None, *args, **kwargs): + """ + Unlocks an ADOM after changes + """ + if adom: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/unlock/" + else: + url = "/dvmdb/adom/{adom}/workspace/unlock/".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/unlock" + code, respobj = self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, url)) + if code == 0 and respobj["status"]["message"].lower() == "ok": + self.remove_adom_from_lock_list(adom) + return code, respobj + + def commit_changes(self, adom=None, aux=False, *args, **kwargs): + """ + Commits changes to an ADOM + """ + if adom: + if aux: + url = "/pm/config/adom/{adom}/workspace/commit".format(adom=adom) + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/commit/" + else: + url = "/dvmdb/adom/{adom}/workspace/commit".format(adom=adom) + else: + url = "/dvmdb/adom/root/workspace/commit" + return self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, url)) + + def get_lock_info(self, adom=None): + """ + Gets ADOM lock info so it can be displayed with the error messages. Or if determined to be locked by ansible + for some reason, then unlock it. + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/lockinfo" + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lockinfo/" + else: + url = "/dvmdb/adom/{adom}/workspace/lockinfo/".format(adom=adom) + datagram = {} + data = self._tools.format_request(FMGRMethods.GET, url, **datagram) + resp_obj = self.send_request(FMGRMethods.GET, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to get the ADOM Lock Info. " + "Error: " + to_text(resp_obj))) + elif code == 0: + try: + if resp_obj[1]["status"]["message"] == "OK": + self._lock_info = None + except Exception: + self._lock_info = resp_obj[1] + return resp_obj + + def get_adom_list(self): + """ + Gets the list of ADOMs for the FortiManager + """ + if self.uses_adoms: + url = "/dvmdb/adom" + datagram = {} + data = self._tools.format_request(FMGRMethods.GET, url, **datagram) + resp_obj = self.send_request(FMGRMethods.GET, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to get the ADOM Info. " + "Error: " + to_text(resp_obj))) + elif code == 0: + num_of_adoms = len(resp_obj[1]) + append_list = ['root', ] + for adom in resp_obj[1]: + if adom["tab_status"] != "": + append_list.append(to_text(adom["name"])) + self._adom_list = append_list + return resp_obj + + def get_locked_adom_list(self): + """ + Gets the list of locked adoms + """ + try: + locked_list = list() + locked_by_user_list = list() + for adom in self._adom_list: + adom_lock_info = self.get_lock_info(adom=adom) + try: + if adom_lock_info[1]["status"]["message"] == "OK": + continue + except IndexError as err: + pass + try: + if adom_lock_info[1][0]["lock_user"]: + locked_list.append(to_text(adom)) + if adom_lock_info[1][0]["lock_user"] == self._logged_in_user: + locked_by_user_list.append({"adom": to_text(adom), + "user": to_text(adom_lock_info[1][0]["lock_user"])}) + except Exception as err: + raise FMGBaseException(err) + self._locked_adom_list = locked_list + self._locked_adoms_by_user = locked_by_user_list + + except Exception as err: + raise FMGBaseException(msg=("An error occurred while trying to get the locked adom list. Error: " + + to_text(err))) + + ################################ + # END DATABASE LOCK CONTEXT CODE + ################################ diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/ftd.py b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/ftd.py new file mode 100644 index 00000000..5400438e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/httpapi/ftd.py @@ -0,0 +1,395 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Ansible Networking Team (!UNKNOWN) +httpapi : ftd +short_description: HttpApi Plugin for Cisco ASA Firepower device +description: + - This HttpApi plugin provides methods to connect to Cisco ASA firepower + devices over a HTTP(S)-based api. +options: + token_path: + type: str + description: + - Specifies the api token path of the FTD device + vars: + - name: ansible_httpapi_ftd_token_path + spec_path: + type: str + description: + - Specifies the api spec path of the FTD device + default: '/apispec/ngfw.json' + vars: + - name: ansible_httpapi_ftd_spec_path +''' + +import json +import os +import re + +from ansible import __version__ as ansible_version + +from ansible.module_utils.basic import to_text +from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import FdmSwaggerParser, SpecProp, FdmSwaggerValidator +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import HTTPMethod, ResponseParams +from ansible.module_utils.six.moves.urllib.error import HTTPError +from ansible.module_utils.six.moves.urllib.parse import urlencode +from ansible.plugins.httpapi import HttpApiBase +from ansible.module_utils.connection import ConnectionError + +try: + from urllib3 import encode_multipart_formdata + from urllib3.fields import RequestField + HAS_URLLIB3 = True +except ImportError: + HAS_URLLIB3 = False + + +BASE_HEADERS = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'FTD Ansible/%s' % ansible_version +} + +TOKEN_EXPIRATION_STATUS_CODE = 408 +UNAUTHORIZED_STATUS_CODE = 401 +API_TOKEN_PATH_OPTION_NAME = 'token_path' +TOKEN_PATH_TEMPLATE = '/api/fdm/{0}/fdm/token' +GET_API_VERSIONS_PATH = '/api/versions' +DEFAULT_API_VERSIONS = ['v2', 'v1'] + +INVALID_API_TOKEN_PATH_MSG = ('The API token path is incorrect. Please, check correctness of ' + 'the `ansible_httpapi_ftd_token_path` variable in the inventory file.') +MISSING_API_TOKEN_PATH_MSG = ('Ansible could not determine the API token path automatically. Please, ' + 'specify the `ansible_httpapi_ftd_token_path` variable in the inventory file.') + + +class HttpApi(HttpApiBase): + def __init__(self, connection): + super(HttpApi, self).__init__(connection) + self.connection = connection + self.access_token = None + self.refresh_token = None + self._api_spec = None + self._api_validator = None + self._ignore_http_errors = False + if not HAS_URLLIB3: + raise AnsibleError( + 'The community.network.ftd httpapi plugin requires urllib3. Use `pip install urllib3` to install it') + + def login(self, username, password): + def request_token_payload(username, password): + return { + 'grant_type': 'password', + 'username': username, + 'password': password + } + + def refresh_token_payload(refresh_token): + return { + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token + } + + if self.refresh_token: + payload = refresh_token_payload(self.refresh_token) + elif username and password: + payload = request_token_payload(username, password) + else: + raise AnsibleConnectionFailure('Username and password are required for login in absence of refresh token') + + response = self._lookup_login_url(payload) + + try: + self.refresh_token = response['refresh_token'] + self.access_token = response['access_token'] + self.connection._auth = {'Authorization': 'Bearer %s' % self.access_token} + except KeyError: + raise ConnectionError( + 'Server returned response without token info during connection authentication: %s' % response) + + def _lookup_login_url(self, payload): + """ Try to find correct login URL and get api token using this URL. + + :param payload: Token request payload + :type payload: dict + :return: token generation response + """ + preconfigured_token_path = self._get_api_token_path() + if preconfigured_token_path: + token_paths = [preconfigured_token_path] + else: + token_paths = self._get_known_token_paths() + + for url in token_paths: + try: + response = self._send_login_request(payload, url) + + except ConnectionError as e: + self.connection.queue_message('vvvv', 'REST:request to %s failed because of connection error: %s ' % ( + url, e)) + # In the case of ConnectionError caused by HTTPError we should check response code. + # Response code 400 returned in case of invalid credentials so we should stop attempts to log in and + # inform the user. + if hasattr(e, 'http_code') and e.http_code == 400: + raise + else: + if not preconfigured_token_path: + self._set_api_token_path(url) + return response + + raise ConnectionError(INVALID_API_TOKEN_PATH_MSG if preconfigured_token_path else MISSING_API_TOKEN_PATH_MSG) + + def _send_login_request(self, payload, url): + self._display(HTTPMethod.POST, 'login', url) + response, response_data = self._send_auth_request( + url, json.dumps(payload), method=HTTPMethod.POST, headers=BASE_HEADERS + ) + self._display(HTTPMethod.POST, 'login:status_code', response.getcode()) + + response = self._response_to_json(self._get_response_value(response_data)) + return response + + def logout(self): + auth_payload = { + 'grant_type': 'revoke_token', + 'access_token': self.access_token, + 'token_to_revoke': self.refresh_token + } + + url = self._get_api_token_path() + + self._display(HTTPMethod.POST, 'logout', url) + response, dummy = self._send_auth_request(url, json.dumps(auth_payload), method=HTTPMethod.POST, + headers=BASE_HEADERS) + self._display(HTTPMethod.POST, 'logout:status_code', response.getcode()) + + self.refresh_token = None + self.access_token = None + + def _send_auth_request(self, path, data, **kwargs): + error_msg_prefix = 'Server returned an error during authentication request' + return self._send_service_request(path, error_msg_prefix, data=data, **kwargs) + + def _send_service_request(self, path, error_msg_prefix, data=None, **kwargs): + try: + self._ignore_http_errors = True + return self.connection.send(path, data, **kwargs) + except HTTPError as e: + # HttpApi connection does not read the error response from HTTPError, so we do it here and wrap it up in + # ConnectionError, so the actual error message is displayed to the user. + error_msg = self._response_to_json(to_text(e.read())) + raise ConnectionError('%s: %s' % (error_msg_prefix, error_msg), http_code=e.code) + finally: + self._ignore_http_errors = False + + def update_auth(self, response, response_data): + # With tokens, authentication should not be checked and updated on each request + return None + + def send_request(self, url_path, http_method, body_params=None, path_params=None, query_params=None): + url = construct_url_path(url_path, path_params, query_params) + data = json.dumps(body_params) if body_params else None + try: + self._display(http_method, 'url', url) + if data: + self._display(http_method, 'data', data) + + response, response_data = self.connection.send(url, data, method=http_method, headers=BASE_HEADERS) + + value = self._get_response_value(response_data) + self._display(http_method, 'response', value) + + return { + ResponseParams.SUCCESS: True, + ResponseParams.STATUS_CODE: response.getcode(), + ResponseParams.RESPONSE: self._response_to_json(value) + } + # Being invoked via JSON-RPC, this method does not serialize and pass HTTPError correctly to the method caller. + # Thus, in order to handle non-200 responses, we need to wrap them into a simple structure and pass explicitly. + except HTTPError as e: + error_msg = to_text(e.read()) + self._display(http_method, 'error', error_msg) + return { + ResponseParams.SUCCESS: False, + ResponseParams.STATUS_CODE: e.code, + ResponseParams.RESPONSE: self._response_to_json(error_msg) + } + + def upload_file(self, from_path, to_url): + url = construct_url_path(to_url) + self._display(HTTPMethod.POST, 'upload', url) + with open(from_path, 'rb') as src_file: + rf = RequestField('fileToUpload', src_file.read(), os.path.basename(src_file.name)) + rf.make_multipart() + body, content_type = encode_multipart_formdata([rf]) + + headers = dict(BASE_HEADERS) + headers['Content-Type'] = content_type + headers['Content-Length'] = len(body) + + dummy, response_data = self.connection.send(url, data=body, method=HTTPMethod.POST, headers=headers) + value = self._get_response_value(response_data) + self._display(HTTPMethod.POST, 'upload:response', value) + return self._response_to_json(value) + + def download_file(self, from_url, to_path, path_params=None): + url = construct_url_path(from_url, path_params=path_params) + self._display(HTTPMethod.GET, 'download', url) + response, response_data = self.connection.send(url, data=None, method=HTTPMethod.GET, headers=BASE_HEADERS) + + if os.path.isdir(to_path): + filename = extract_filename_from_headers(response.info()) + to_path = os.path.join(to_path, filename) + + with open(to_path, "wb") as output_file: + output_file.write(response_data.getvalue()) + self._display(HTTPMethod.GET, 'downloaded', to_path) + + def handle_httperror(self, exc): + is_auth_related_code = exc.code == TOKEN_EXPIRATION_STATUS_CODE or exc.code == UNAUTHORIZED_STATUS_CODE + if not self._ignore_http_errors and is_auth_related_code: + self.connection._auth = None + self.login(self.connection.get_option('remote_user'), self.connection.get_option('password')) + return True + # False means that the exception will be passed further to the caller + return False + + def _display(self, http_method, title, msg=''): + self.connection.queue_message('vvvv', 'REST:%s:%s:%s\n%s' % (http_method, self.connection._url, title, msg)) + + @staticmethod + def _get_response_value(response_data): + return to_text(response_data.getvalue()) + + def _get_api_spec_path(self): + return self.get_option('spec_path') + + def _get_known_token_paths(self): + """Generate list of token generation urls based on list of versions supported by device(if exposed via API) or + default list of API versions. + + :returns: list of token generation urls + :rtype: generator + """ + try: + api_versions = self._get_supported_api_versions() + except ConnectionError: + # API versions API is not supported we need to check all known version + api_versions = DEFAULT_API_VERSIONS + + return [TOKEN_PATH_TEMPLATE.format(version) for version in api_versions] + + def _get_supported_api_versions(self): + """ + Fetch list of API versions supported by device. + + :return: list of API versions suitable for device + :rtype: list + """ + # Try to fetch supported API version + http_method = HTTPMethod.GET + response, response_data = self._send_service_request( + path=GET_API_VERSIONS_PATH, + error_msg_prefix="Can't fetch list of supported api versions", + method=http_method, + headers=BASE_HEADERS + ) + + value = self._get_response_value(response_data) + self._display(http_method, 'response', value) + api_versions_info = self._response_to_json(value) + return api_versions_info["supportedVersions"] + + def _get_api_token_path(self): + return self.get_option(API_TOKEN_PATH_OPTION_NAME) + + def _set_api_token_path(self, url): + return self.set_option(API_TOKEN_PATH_OPTION_NAME, url) + + @staticmethod + def _response_to_json(response_text): + try: + return json.loads(response_text) if response_text else {} + # JSONDecodeError only available on Python 3.5+ + except getattr(json.decoder, 'JSONDecodeError', ValueError): + raise ConnectionError('Invalid JSON response: %s' % response_text) + + def get_operation_spec(self, operation_name): + return self.api_spec[SpecProp.OPERATIONS].get(operation_name, None) + + def get_operation_specs_by_model_name(self, model_name): + if model_name: + return self.api_spec[SpecProp.MODEL_OPERATIONS].get(model_name, None) + else: + return None + + def get_model_spec(self, model_name): + return self.api_spec[SpecProp.MODELS].get(model_name, None) + + def validate_data(self, operation_name, data): + return self.api_validator.validate_data(operation_name, data) + + def validate_query_params(self, operation_name, params): + return self.api_validator.validate_query_params(operation_name, params) + + def validate_path_params(self, operation_name, params): + return self.api_validator.validate_path_params(operation_name, params) + + @property + def api_spec(self): + if self._api_spec is None: + spec_path_url = self._get_api_spec_path() + response = self.send_request(url_path=spec_path_url, http_method=HTTPMethod.GET) + if response[ResponseParams.SUCCESS]: + self._api_spec = FdmSwaggerParser().parse_spec(response[ResponseParams.RESPONSE]) + else: + raise ConnectionError('Failed to download API specification. Status code: %s. Response: %s' % ( + response[ResponseParams.STATUS_CODE], response[ResponseParams.RESPONSE])) + return self._api_spec + + @property + def api_validator(self): + if self._api_validator is None: + self._api_validator = FdmSwaggerValidator(self.api_spec) + return self._api_validator + + +def construct_url_path(path, path_params=None, query_params=None): + url = path + if path_params: + url = url.format(**path_params) + if query_params: + url += "?" + urlencode(query_params) + return url + + +def extract_filename_from_headers(response_info): + content_header_regex = r'attachment; ?filename="?([^"]+)' + match = re.match(content_header_regex, response_info.get('Content-Disposition')) + if match: + return match.group(1) + else: + raise ValueError("No appropriate Content-Disposition header is specified.") diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/inventory/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/inventory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/lookup/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/lookup/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/lookup/avi.py b/collections-debian-merged/ansible_collections/community/network/plugins/lookup/avi.py new file mode 100644 index 00000000..465eb07a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/lookup/avi.py @@ -0,0 +1,129 @@ +# Copyright (c) Sandeep Bandi +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +lookup: avi +author: Sandeep Bandi (@sabandi) +short_description: Look up ``Avi`` objects. +description: + - Given an object_type, fetch all the objects of that type or fetch + the specific object that matches the name/uuid given via options. + - For single object lookup. If you want the output to be a list, you may + want to pass option wantlist=True to the plugin. + +options: + obj_type: + description: + - type of object to query + required: True + obj_name: + description: + - name of the object to query + obj_uuid: + description: + - UUID of the object to query +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +# Lookup query for all the objects of a specific type. +- ansible.builtin.debug: msg="{{ lookup('community.network.avi', avi_credentials=avi_credentials, obj_type='virtualservice') }}" +# Lookup query for an object with the given name and type. +- ansible.builtin.debug: msg="{{ lookup('community.network.avi', avi_credentials=avi_credentials, obj_name='vs1', obj_type='virtualservice', wantlist=True) }}" +# Lookup query for an object with the given UUID and type. +- ansible.builtin.debug: msg="{{ lookup('community.network.avi', obj_uuid='virtualservice-5c0e183a-690a-45d8-8d6f-88c30a52550d', obj_type='virtualservice') }}" +# We can replace lookup with query function to always the get the output as list. +# This is helpful for looping. +- ansible.builtin.debug: msg="{{ query('community.network.avi', obj_uuid='virtualservice-5c0e183a-690a-45d8-8d6f-88c30a52550d', obj_type='virtualservice') }}" +""" + +RETURN = """ + _raw: + description: + - One ore more objects returned from ``Avi`` API. + type: list + elements: dictionary +""" + +from ansible.module_utils._text import to_native +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.plugins.lookup import LookupBase +from ansible.utils.display import Display +from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import (ApiSession, + AviCredentials, + AviServerError, + ObjectNotFound, + APIError) + +display = Display() + + +def _api(avi_session, path, **kwargs): + ''' + Generic function to handle both // and / + API resource endpoints. + ''' + rsp = [] + try: + rsp_data = avi_session.get(path, **kwargs).json() + if 'results' in rsp_data: + rsp = rsp_data['results'] + else: + rsp.append(rsp_data) + except ObjectNotFound as e: + display.warning('Resource not found. Please check obj_name/' + 'obj_uuid/obj_type are spelled correctly.') + display.v(to_native(e)) + except (AviServerError, APIError) as e: + raise AnsibleError(to_native(e)) + except Exception as e: + # Generic excption handling for connection failures + raise AnsibleError('Unable to communicate with controller' + 'due to error: %s' % to_native(e)) + + return rsp + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, avi_credentials=None, **kwargs): + + api_creds = AviCredentials(**avi_credentials) + # Create the session using avi_credentials + try: + avi = ApiSession(avi_credentials=api_creds) + except Exception as e: + raise AnsibleError(to_native(e)) + + # Return an empty list if the object is not found + rsp = [] + try: + path = kwargs.pop('obj_type') + except KeyError: + raise AnsibleError("Please pass the obj_type for lookup") + + if kwargs.get('obj_name', None): + name = kwargs.pop('obj_name') + try: + display.v("Fetching obj: %s of type: %s" % (name, path)) + rsp_data = avi.get_object_by_name(path, name, **kwargs) + if rsp_data: + # Append the return data only if it is not None. i.e object + # with specified name is present + rsp.append(rsp_data) + except AviServerError as e: + raise AnsibleError(to_native(e)) + elif kwargs.get('obj_uuid', None): + obj_uuid = kwargs.pop('obj_uuid') + obj_path = "%s/%s" % (path, obj_uuid) + display.v("Fetching obj: %s of type: %s" % (obj_uuid, path)) + rsp = _api(avi, obj_path, **kwargs) + else: + display.v("Fetching all objects of type: %s" % path) + rsp = _api(avi, path, **kwargs) + + return rsp diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/a10.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/a10.py new file mode 100644 index 00000000..329c6493 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/a10/a10.py @@ -0,0 +1,156 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Michael DeHaan , 2012-2013 +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible.module_utils.urls import fetch_url + + +AXAPI_PORT_PROTOCOLS = { + 'tcp': 2, + 'udp': 3, +} + +AXAPI_VPORT_PROTOCOLS = { + 'tcp': 2, + 'udp': 3, + 'fast-http': 9, + 'http': 11, + 'https': 12, +} + + +def a10_argument_spec(): + return dict( + host=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), + write_config=dict(type='bool', default=False) + ) + + +def axapi_failure(result): + if 'response' in result and result['response'].get('status') == 'fail': + return True + return False + + +def axapi_call(module, url, post=None): + ''' + Returns a datastructure based on the result of the API call + ''' + rsp, info = fetch_url(module, url, data=post) + if not rsp or info['status'] >= 400: + module.fail_json(msg="failed to connect (status code %s), error was %s" % (info['status'], info.get('msg', 'no error given'))) + try: + raw_data = rsp.read() + data = json.loads(raw_data) + except ValueError: + # at least one API call (system.action.write_config) returns + # XML even when JSON is requested, so do some minimal handling + # here to prevent failing even when the call succeeded + if 'status="ok"' in raw_data.lower(): + data = {"response": {"status": "OK"}} + else: + data = {"response": {"status": "fail", "err": {"msg": raw_data}}} + except Exception: + module.fail_json(msg="could not read the result from the host") + finally: + rsp.close() + return data + + +def axapi_authenticate(module, base_url, username, password): + url = '%s&method=authenticate&username=%s&password=%s' % (base_url, username, password) + result = axapi_call(module, url) + if axapi_failure(result): + return module.fail_json(msg=result['response']['err']['msg']) + sessid = result['session_id'] + return base_url + '&session_id=' + sessid + + +def axapi_authenticate_v3(module, base_url, username, password): + url = base_url + auth_payload = {"credentials": {"username": username, "password": password}} + result = axapi_call_v3(module, url, method='POST', body=json.dumps(auth_payload)) + if axapi_failure(result): + return module.fail_json(msg=result['response']['err']['msg']) + signature = result['authresponse']['signature'] + return signature + + +def axapi_call_v3(module, url, method=None, body=None, signature=None): + ''' + Returns a datastructure based on the result of the API call + ''' + if signature: + headers = {'content-type': 'application/json', 'Authorization': 'A10 %s' % signature} + else: + headers = {'content-type': 'application/json'} + rsp, info = fetch_url(module, url, method=method, data=body, headers=headers) + if not rsp or info['status'] >= 400: + module.fail_json(msg="failed to connect (status code %s), error was %s" % (info['status'], info.get('msg', 'no error given'))) + try: + raw_data = rsp.read() + data = json.loads(raw_data) + except ValueError: + # at least one API call (system.action.write_config) returns + # XML even when JSON is requested, so do some minimal handling + # here to prevent failing even when the call succeeded + if 'status="ok"' in raw_data.lower(): + data = {"response": {"status": "OK"}} + else: + data = {"response": {"status": "fail", "err": {"msg": raw_data}}} + except Exception: + module.fail_json(msg="could not read the result from the host") + finally: + rsp.close() + return data + + +def axapi_enabled_disabled(flag): + ''' + The axapi uses 0/1 integer values for flags, rather than strings + or booleans, so convert the given flag to a 0 or 1. For now, params + are specified as strings only so thats what we check. + ''' + if flag == 'enabled': + return 1 + else: + return 0 + + +def axapi_get_port_protocol(protocol): + return AXAPI_PORT_PROTOCOLS.get(protocol.lower(), None) + + +def axapi_get_vport_protocol(protocol): + return AXAPI_VPORT_PROTOCOLS.get(protocol.lower(), None) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/aireos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/aireos.py new file mode 100644 index 00000000..f60a2e81 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aireos/aireos.py @@ -0,0 +1,139 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import exec_command + +_DEVICE_CONFIGS = {} + +aireos_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'timeout': dict(type='int'), +} +aireos_argument_spec = { + 'provider': dict(type='dict', options=aireos_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network') +} + +aireos_top_spec = { + 'host': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'port': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 + 'username': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'password': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', no_log=True), # was Ansible 2.9 + 'ssh_keyfile': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='path'), # was Ansible 2.9 + 'timeout': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 +} +aireos_argument_spec.update(aireos_top_spec) + + +def sanitize(resp): + # Takes response from device and strips whitespace from all lines + # Aireos adds in extra preceding whitespace which netcfg parses as children/parents, which Aireos does not do + # Aireos also adds in trailing whitespace that is unused + cleaned = [] + for line in resp.splitlines(): + cleaned.append(line.strip()) + return '\n'.join(cleaned).strip() + + +def get_provider_argspec(): + return aireos_provider_spec + + +def check_args(module, warnings): + pass + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = 'show run-config commands ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_then_replace')) + cfg = sanitize(to_text(out, errors='surrogate_then_replace').strip()) + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + responses = list() + commands = to_commands(module, to_list(commands)) + for cmd in commands: + cmd = module.jsonify(cmd) + rc, out, err = exec_command(module, cmd) + if check_rc and rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), rc=rc) + responses.append(sanitize(to_text(out, errors='surrogate_then_replace'))) + return responses + + +def load_config(module, commands): + + rc, out, err = exec_command(module, 'config') + if rc != 0: + module.fail_json(msg='unable to enter configuration mode', err=to_text(out, errors='surrogate_then_replace')) + + for command in to_list(commands): + if command == 'end': + continue + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + exec_command(module, 'end') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/aos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/aos.py new file mode 100644 index 00000000..f7fdb208 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aos/aos.py @@ -0,0 +1,183 @@ +# +# Copyright (c) 2017 Apstra Inc, +# +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +""" +This module adds shared support for Apstra AOS modules + +In order to use this module, include it as part of your module + +from ansible.module_utils.network.aos.aos import (check_aos_version, get_aos_session, find_collection_item, + content_to_dict, do_load_resource) + +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from distutils.version import LooseVersion + +try: + import yaml + HAS_YAML = True +except ImportError: + HAS_YAML = False + +try: + from apstra.aosom.session import Session + + HAS_AOS_PYEZ = True +except ImportError: + HAS_AOS_PYEZ = False + +from ansible.module_utils._text import to_native + + +def check_aos_version(module, min=False): + """ + Check if the library aos-pyez is present. + If provided, also check if the minimum version requirement is met + """ + if not HAS_AOS_PYEZ: + module.fail_json(msg='aos-pyez is not installed. Please see details ' + 'here: https://github.com/Apstra/aos-pyez') + + elif min: + import apstra.aosom + AOS_PYEZ_VERSION = apstra.aosom.__version__ + + if LooseVersion(AOS_PYEZ_VERSION) < LooseVersion(min): + module.fail_json(msg='aos-pyez >= %s is required for this module' % min) + + return True + + +def get_aos_session(module, auth): + """ + Resume an existing session and return an AOS object. + + Args: + auth (dict): An AOS session as obtained by aos_login module blocks:: + + dict( token=, + server=, + port= + ) + + Return: + Aos object + """ + + check_aos_version(module) + + aos = Session() + aos.session = auth + + return aos + + +def find_collection_item(collection, item_name=False, item_id=False): + """ + Find collection_item based on name or id from a collection object + Both Collection_item and Collection Objects are provided by aos-pyez library + + Return + collection_item: object corresponding to the collection type + """ + my_dict = None + + if item_name: + my_dict = collection.find(label=item_name) + elif item_id: + my_dict = collection.find(uid=item_id) + + if my_dict is None: + return collection[''] + else: + return my_dict + + +def content_to_dict(module, content): + """ + Convert 'content' into a Python Dict based on 'content_format' + """ + + # if not HAS_YAML: + # module.fail_json(msg="Python Library Yaml is not present, mandatory to use 'content'") + + content_dict = None + + # try: + # content_dict = json.loads(content.replace("\'", '"')) + # except: + # module.fail_json(msg="Unable to convert 'content' from JSON, please check if valid") + # + # elif format in ['yaml', 'var']: + + try: + content_dict = yaml.safe_load(content) + + if not isinstance(content_dict, dict): + raise Exception() + + # Check if dict is empty and return an error if it's + if not content_dict: + raise Exception() + + except Exception: + module.fail_json(msg="Unable to convert 'content' to a dict, please check if valid") + + # replace the string with the dict + module.params['content'] = content_dict + + return content_dict + + +def do_load_resource(module, collection, name): + """ + Create a new object (collection.item) by loading a datastructure directly + """ + + try: + item = find_collection_item(collection, name, '') + except Exception: + module.fail_json(msg="An error occurred while running 'find_collection_item'") + + if item.exists: + module.exit_json(changed=False, name=item.name, id=item.id, value=item.value) + + # If not in check mode, apply the changes + if not module.check_mode: + try: + item.datum = module.params['content'] + item.write() + except Exception as e: + module.fail_json(msg="Unable to write item content : %r" % to_native(e)) + + module.exit_json(changed=True, name=item.name, id=item.id, value=item.value) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/apconos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/apconos.py new file mode 100644 index 00000000..1b9eebcd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/apconos/apconos.py @@ -0,0 +1,113 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by +# Ansible still belong to the author of the module, and may assign their own +# license to the complete work. +# +# Copyright (C) 2019 APCON, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Contains utility methods +# APCON Networking + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import EntityCollection +from ansible.module_utils.connection import Connection, exec_command +from ansible.module_utils.connection import ConnectionError + +_DEVICE_CONFIGS = {} +_CONNECTION = None + + +command_spec = { + 'command': dict(key=True), +} + + +def check_args(module, warnings): + pass + + +def get_connection(module): + global _CONNECTION + if _CONNECTION: + return _CONNECTION + _CONNECTION = Connection(module._socket_path) + + return _CONNECTION + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = ' '.join(flags).strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + conn = get_connection(module) + out = conn.get(cmd) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + responses = list() + + for cmd in commands: + out = connection.get(**cmd) + responses.append(to_text(out, errors='surrogate_then_replace')) + + return responses + + +def load_config(module, config): + try: + conn = get_connection(module) + conn.edit_config(config) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_defaults_flag(module): + rc, out, err = exec_command(module, 'display running-config ?') + out = to_text(out, errors='surrogate_then_replace') + + commands = set() + for line in out.splitlines(): + if line: + commands.add(line.strip().split()[0]) + + if 'all' in commands: + return 'all' + else: + return 'full' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/aruba.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/aruba.py new file mode 100644 index 00000000..4ca701b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/aruba/aruba.py @@ -0,0 +1,141 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import exec_command + +_DEVICE_CONFIGS = {} + +aruba_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'timeout': dict(type='int'), +} +aruba_argument_spec = { + 'provider': dict(type='dict', options=aruba_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network') +} + +aruba_top_spec = { + 'host': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'port': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 + 'username': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'password': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', no_log=True), # was Ansible 2.9 + 'ssh_keyfile': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='path'), # was Ansible 2.9 + 'timeout': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 +} + +aruba_argument_spec.update(aruba_top_spec) + + +def get_provider_argspec(): + return aruba_provider_spec + + +def check_args(module, warnings): + pass + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = 'show running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_then_replace')) + cfg = sanitize(to_text(out, errors='surrogate_then_replace').strip()) + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def sanitize(resp): + # Takes response from device and adjusts leading whitespace to just 1 space + cleaned = [] + for line in resp.splitlines(): + cleaned.append(re.sub(r"^\s+", " ", line)) + return '\n'.join(cleaned).strip() + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + responses = list() + commands = to_commands(module, to_list(commands)) + for cmd in commands: + cmd = module.jsonify(cmd) + rc, out, err = exec_command(module, cmd) + if check_rc and rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), rc=rc) + responses.append(to_text(out, errors='surrogate_then_replace')) + return responses + + +def load_config(module, commands): + + rc, out, err = exec_command(module, 'configure terminal') + if rc != 0: + module.fail_json(msg='unable to enter configuration mode', err=to_text(out, errors='surrogate_then_replace')) + + for command in to_list(commands): + if command == 'end': + continue + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + exec_command(module, 'end') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/ansible_utils.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/ansible_utils.py new file mode 100644 index 00000000..93de7943 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/ansible_utils.py @@ -0,0 +1,574 @@ +""" +Created on Aug 16, 2016 + +@author: Gaurav Rastogi (grastogi@avinetworks.com) +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import os +import re +import logging +import sys +from copy import deepcopy +from ansible.module_utils.basic import env_fallback + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, ObjectNotFound, avi_sdk_syslog_logger, AviCredentials, HAS_AVI) +except ImportError: + HAS_AVI = False + + +if os.environ.get('AVI_LOG_HANDLER', '') != 'syslog': + log = logging.getLogger(__name__) +else: + # Ansible does not allow logging from the modules. + log = avi_sdk_syslog_logger() + + +def _check_type_string(x): + """ + :param x: + :return: True if it is of type string + """ + if isinstance(x, str): + return True + if sys.version_info[0] < 3: + try: + return isinstance(x, unicode) + except NameError: + return False + + +class AviCheckModeResponse(object): + """ + Class to support ansible check mode. + """ + + def __init__(self, obj, status_code=200): + self.obj = obj + self.status_code = status_code + + def json(self): + return self.obj + + +def ansible_return(module, rsp, changed, req=None, existing_obj=None, + api_context=None): + """ + :param module: AnsibleModule + :param rsp: ApiResponse from avi_api + :param changed: boolean + :param req: ApiRequest to avi_api + :param existing_obj: object to be passed debug output + :param api_context: api login context + + helper function to return the right ansible based on the error code and + changed + Returns: specific ansible module exit function + """ + + if rsp is not None and rsp.status_code > 299: + return module.fail_json( + msg='Error %d Msg %s req: %s api_context:%s ' % ( + rsp.status_code, rsp.text, req, api_context)) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + key = '%s:%s:%s' % (api_creds.controller, api_creds.username, + api_creds.port) + disable_fact = module.params.get('avi_disable_session_cache_as_fact') + + fact_context = None + if not disable_fact: + fact_context = module.params.get('api_context', {}) + if fact_context: + fact_context.update({key: api_context}) + else: + fact_context = {key: api_context} + + obj_val = rsp.json() if rsp else existing_obj + + if (obj_val and module.params.get("obj_username", None) and + "username" in obj_val): + obj_val["obj_username"] = obj_val["username"] + if (obj_val and module.params.get("obj_password", None) and + "password" in obj_val): + obj_val["obj_password"] = obj_val["password"] + old_obj_val = existing_obj if changed and existing_obj else None + api_context_val = api_context if disable_fact else None + ansible_facts_val = dict( + avi_api_context=fact_context) if not disable_fact else {} + + return module.exit_json( + changed=changed, obj=obj_val, old_obj=old_obj_val, + ansible_facts=ansible_facts_val, api_context=api_context_val) + + +def purge_optional_fields(obj, module): + """ + It purges the optional arguments to be sent to the controller. + :param obj: dictionary of the ansible object passed as argument. + :param module: AnsibleModule + return modified obj + """ + purge_fields = [] + for param, spec in module.argument_spec.items(): + if not spec.get('required', False): + if param not in obj: + # these are ansible common items + continue + if obj[param] is None: + purge_fields.append(param) + log.debug('purging fields %s', purge_fields) + for param in purge_fields: + obj.pop(param, None) + return obj + + +def cleanup_absent_fields(obj): + """ + cleans up any field that is marked as state: absent. It needs to be removed + from the object if it is present. + :param obj: + :return: Purged object + """ + if type(obj) != dict: + return obj + cleanup_keys = [] + for k, v in obj.items(): + if type(v) == dict: + if (('state' in v and v['state'] == 'absent') or + (v == "{'state': 'absent'}")): + cleanup_keys.append(k) + else: + cleanup_absent_fields(v) + if not v: + cleanup_keys.append(k) + elif type(v) == list: + new_list = [] + for elem in v: + elem = cleanup_absent_fields(elem) + if elem: + # remove the item from list + new_list.append(elem) + if new_list: + obj[k] = new_list + else: + cleanup_keys.append(k) + elif isinstance(v, str) or isinstance(v, str): + if v == "{'state': 'absent'}": + cleanup_keys.append(k) + for k in cleanup_keys: + del obj[k] + return obj + + +RE_REF_MATCH = re.compile(r'^/api/[\w/]+\?name\=[\w]+[^#<>]*$') +# if HTTP ref match then strip out the #name +HTTP_REF_MATCH = re.compile(r'https://[\w.0-9:-]+/api/.+') +HTTP_REF_W_NAME_MATCH = re.compile(r'https://[\w.0-9:-]+/api/.*#.+') + + +def ref_n_str_cmp(x, y): + """ + compares two references + 1. check for exact reference + 2. check for obj_type/uuid + 3. check for name + + if x is ref=name then extract uuid and name from y and use it. + if x is http_ref then + strip x and y + compare them. + + if x and y are urls then match with split on # + if x is a RE_REF_MATCH then extract name + if y is a REF_MATCH then extract name + :param x: first string + :param y: second string from controller's object + + Returns + True if they are equivalent else False + """ + if type(y) in (int, float, bool, int, complex): + y = str(y) + x = str(x) + if not (_check_type_string(x) and _check_type_string(y)): + return False + y_uuid = y_name = str(y) + x = str(x) + if RE_REF_MATCH.match(x): + x = x.split('name=')[1] + elif HTTP_REF_MATCH.match(x): + x = x.rsplit('#', 1)[0] + y = y.rsplit('#', 1)[0] + elif RE_REF_MATCH.match(y): + y = y.split('name=')[1] + + if HTTP_REF_W_NAME_MATCH.match(y): + path = y.split('api/', 1)[1] + # Fetching name or uuid from path /xxxx_xx/xx/xx_x/uuid_or_name + uuid_or_name = path.split('/')[-1] + parts = uuid_or_name.rsplit('#', 1) + y_uuid = parts[0] + y_name = parts[1] if len(parts) > 1 else '' + # is just string but y is a url so match either uuid or name + result = (x in (y, y_name, y_uuid)) + if not result: + log.debug('x: %s y: %s y_name %s y_uuid %s', + x, y, y_name, y_uuid) + return result + + +def avi_obj_cmp(x, y, sensitive_fields=None): + """ + compares whether x is fully contained in y. The comparision is different + from a simple dictionary compare for following reasons + 1. Some fields could be references. The object in controller returns the + full URL for those references. However, the ansible script would have + it specified as /api/pool?name=blah. So, the reference fields need + to match uuid, relative reference based on name and actual reference. + + 2. Optional fields with defaults: In case there are optional fields with + defaults then controller automatically fills it up. This would + cause the comparison with Ansible object specification to always return + changed. + + 3. Optional fields without defaults: This is most tricky. The issue is + how to specify deletion of such objects from ansible script. If the + ansible playbook has object specified as Null then Avi controller will + reject for non Message(dict) type fields. In addition, to deal with the + defaults=null issue all the fields that are set with None are purged + out before comparing with Avi controller's version + + So, the solution is to pass state: absent if any optional field needs + to be deleted from the configuration. The script would return changed + =true if it finds a key in the controller version and it is marked with + state: absent in ansible playbook. Alternatively, it would return + false if key is not present in the controller object. Before, doing + put or post it would purge the fields that are marked state: absent. + + :param x: first string + :param y: second string from controller's object + :param sensitive_fields: sensitive fields to ignore for diff + + Returns: + True if x is subset of y else False + """ + if not sensitive_fields: + sensitive_fields = set() + if isinstance(x, str) or isinstance(x, str): + # Special handling for strings as they can be references. + return ref_n_str_cmp(x, y) + if type(x) not in [list, dict]: + # if it is not list or dict or string then simply compare the values + return x == y + if type(x) == list: + # should compare each item in the list and that should match + if len(x) != len(y): + log.debug('x has %d items y has %d', len(x), len(y)) + return False + for i in zip(x, y): + if not avi_obj_cmp(i[0], i[1], sensitive_fields=sensitive_fields): + # no need to continue + return False + + if type(x) == dict: + x.pop('_last_modified', None) + x.pop('tenant', None) + y.pop('_last_modified', None) + x.pop('api_version', None) + y.pop('api_verison', None) + d_xks = [k for k in x.keys() if k in sensitive_fields] + + if d_xks: + # if there is sensitive field then always return changed + return False + # pop the keys that are marked deleted but not present in y + # return false if item is marked absent and is present in y + d_x_absent_ks = [] + for k, v in x.items(): + if v is None: + d_x_absent_ks.append(k) + continue + if isinstance(v, dict): + if ('state' in v) and (v['state'] == 'absent'): + if type(y) == dict and k not in y: + d_x_absent_ks.append(k) + else: + return False + elif not v: + d_x_absent_ks.append(k) + elif isinstance(v, list) and not v: + d_x_absent_ks.append(k) + # Added condition to check key in dict. + elif isinstance(v, str) or (k in y and isinstance(y[k], str)): + # this is the case when ansible converts the dictionary into a + # string. + if v == "{'state': 'absent'}" and k not in y: + d_x_absent_ks.append(k) + elif not v and k not in y: + # this is the case when x has set the value that qualifies + # as not but y does not have that value + d_x_absent_ks.append(k) + for k in d_x_absent_ks: + x.pop(k) + x_keys = set(x.keys()) + y_keys = set(y.keys()) + if not x_keys.issubset(y_keys): + # log.debug('x has %s and y has %s keys', len(x_keys), len(y_keys)) + return False + for k, v in x.items(): + if k not in y: + # log.debug('k %s is not in y %s', k, y) + return False + if not avi_obj_cmp(v, y[k], sensitive_fields=sensitive_fields): + # log.debug('k %s v %s did not match in y %s', k, v, y[k]) + return False + return True + + +POP_FIELDS = ['state', 'controller', 'username', 'password', 'api_version', + 'avi_credentials', 'avi_api_update_method', 'avi_api_patch_op', + 'api_context', 'tenant', 'tenant_uuid', 'avi_disable_session_cache_as_fact'] + + +def get_api_context(module, api_creds): + api_context = module.params.get('api_context') + if api_context and module.params.get('avi_disable_session_cache_as_fact'): + return api_context + elif api_context and not module.params.get( + 'avi_disable_session_cache_as_fact'): + key = '%s:%s:%s' % (api_creds.controller, api_creds.username, + api_creds.port) + return api_context.get(key) + else: + return None + + +def avi_ansible_api(module, obj_type, sensitive_fields): + """ + This converts the Ansible module into AVI object and invokes APIs + :param module: Ansible module + :param obj_type: string representing Avi object type + :param sensitive_fields: sensitive fields to be excluded for comparison + purposes. + Returns: + success: module.exit_json with obj=avi object + faliure: module.fail_json + """ + + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api_context = get_api_context(module, api_creds) + if api_context: + api = ApiSession.get_session( + api_creds.controller, + api_creds.username, + password=api_creds.password, + timeout=api_creds.timeout, + tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, + token=api_context['csrftoken'], + port=api_creds.port, + session_id=api_context['session_id'], + csrftoken=api_context['csrftoken']) + else: + api = ApiSession.get_session( + api_creds.controller, + api_creds.username, + password=api_creds.password, + timeout=api_creds.timeout, + tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, + token=api_creds.token, + port=api_creds.port) + state = module.params['state'] + # Get the api version. + avi_update_method = module.params.get('avi_api_update_method', 'put') + avi_patch_op = module.params.get('avi_api_patch_op', 'add') + + api_version = api_creds.api_version + name = module.params.get('name', None) + # Added Support to get uuid + uuid = module.params.get('uuid', None) + check_mode = module.check_mode + if uuid and obj_type != 'cluster': + obj_path = '%s/%s' % (obj_type, uuid) + else: + obj_path = '%s/' % obj_type + obj = deepcopy(module.params) + tenant = obj.pop('tenant', '') + tenant_uuid = obj.pop('tenant_uuid', '') + # obj.pop('cloud_ref', None) + for k in POP_FIELDS: + obj.pop(k, None) + purge_optional_fields(obj, module) + + # Special code to handle situation where object has a field + # named username. This is used in case of api/user + # The following code copies the username and password + # from the obj_username and obj_password fields. + if 'obj_username' in obj: + obj['username'] = obj['obj_username'] + obj.pop('obj_username') + if 'obj_password' in obj: + obj['password'] = obj['obj_password'] + obj.pop('obj_password') + if 'full_name' not in obj and 'name' in obj and obj_type == "user": + obj['full_name'] = obj['name'] + # Special case as name represent full_name in user module + # As per API response, name is always same as username regardless of full_name + obj['name'] = obj['username'] + + log.info('passed object %s ', obj) + + if uuid: + # Get the object based on uuid. + try: + existing_obj = api.get( + obj_path, tenant=tenant, tenant_uuid=tenant_uuid, + params={'include_refs': '', 'include_name': ''}, + api_version=api_version) + existing_obj = existing_obj.json() + except ObjectNotFound: + existing_obj = None + elif name: + params = {'include_refs': '', 'include_name': ''} + if obj.get('cloud_ref', None): + # this is the case when gets have to be scoped with cloud + cloud = obj['cloud_ref'].split('name=')[1] + params['cloud_ref.name'] = cloud + existing_obj = api.get_object_by_name( + obj_type, name, tenant=tenant, tenant_uuid=tenant_uuid, + params=params, api_version=api_version) + + # Need to check if tenant_ref was provided and the object returned + # is actually in admin tenant. + if existing_obj and 'tenant_ref' in obj and 'tenant_ref' in existing_obj: + # https://10.10.25.42/api/tenant/admin#admin + existing_obj_tenant = existing_obj['tenant_ref'].split('#')[1] + obj_tenant = obj['tenant_ref'].split('name=')[1] + if obj_tenant != existing_obj_tenant: + existing_obj = None + else: + # added api version to avi api call. + existing_obj = api.get(obj_path, tenant=tenant, tenant_uuid=tenant_uuid, + params={'include_refs': '', 'include_name': ''}, + api_version=api_version).json() + + if state == 'absent': + rsp = None + changed = False + err = False + if not check_mode and existing_obj: + try: + if name is not None: + # added api version to avi api call. + rsp = api.delete_by_name( + obj_type, name, tenant=tenant, tenant_uuid=tenant_uuid, + api_version=api_version) + else: + # added api version to avi api call. + rsp = api.delete( + obj_path, tenant=tenant, tenant_uuid=tenant_uuid, + api_version=api_version) + except ObjectNotFound: + pass + if check_mode and existing_obj: + changed = True + + if rsp: + if rsp.status_code == 204: + changed = True + else: + err = True + if not err: + return ansible_return( + module, rsp, changed, existing_obj=existing_obj, + api_context=api.get_context()) + elif rsp: + return module.fail_json(msg=rsp.text) + + rsp = None + req = None + if existing_obj: + # this is case of modify as object exists. should find out + # if changed is true or not + if name is not None and obj_type != 'cluster': + obj_uuid = existing_obj['uuid'] + obj_path = '%s/%s' % (obj_type, obj_uuid) + if avi_update_method == 'put': + changed = not avi_obj_cmp(obj, existing_obj, sensitive_fields) + obj = cleanup_absent_fields(obj) + if changed: + req = obj + if check_mode: + # No need to process any further. + rsp = AviCheckModeResponse(obj=existing_obj) + else: + rsp = api.put( + obj_path, data=req, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + elif check_mode: + rsp = AviCheckModeResponse(obj=existing_obj) + else: + if check_mode: + # No need to process any further. + rsp = AviCheckModeResponse(obj=existing_obj) + changed = True + else: + obj.pop('name', None) + patch_data = {avi_patch_op: obj} + rsp = api.patch( + obj_path, data=patch_data, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + obj = rsp.json() + changed = not avi_obj_cmp(obj, existing_obj) + if changed: + log.debug('EXISTING OBJ %s', existing_obj) + log.debug('NEW OBJ %s', obj) + else: + changed = True + req = obj + if check_mode: + rsp = AviCheckModeResponse(obj=None) + else: + rsp = api.post(obj_type, data=obj, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + return ansible_return(module, rsp, changed, req, existing_obj=existing_obj, + api_context=api.get_context()) + + +def avi_common_argument_spec(): + """ + Returns common arguments for all Avi modules + :return: dict + """ + credentials_spec = dict( + controller=dict(fallback=(env_fallback, ['AVI_CONTROLLER'])), + username=dict(fallback=(env_fallback, ['AVI_USERNAME'])), + password=dict(fallback=(env_fallback, ['AVI_PASSWORD']), no_log=True), + api_version=dict(default='16.4.4', type='str'), + tenant=dict(default='admin'), + tenant_uuid=dict(default='', type='str'), + port=dict(type='int'), + timeout=dict(default=300, type='int'), + token=dict(default='', type='str', no_log=True), + session_id=dict(default='', type='str', no_log=True), + csrftoken=dict(default='', type='str', no_log=True) + ) + + return dict( + controller=dict(fallback=(env_fallback, ['AVI_CONTROLLER'])), + username=dict(fallback=(env_fallback, ['AVI_USERNAME'])), + password=dict(fallback=(env_fallback, ['AVI_PASSWORD']), no_log=True), + tenant=dict(default='admin'), + tenant_uuid=dict(default=''), + api_version=dict(default='16.4.4', type='str'), + avi_credentials=dict(default=None, type='dict', + options=credentials_spec), + api_context=dict(type='dict'), + avi_disable_session_cache_as_fact=dict(default=False, type='bool')) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi.py new file mode 100644 index 00000000..649a16bf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi.py @@ -0,0 +1,39 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Gaurav Rastogi , 2017 +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This module initially matched the namespace of network module avi. However, +# that causes namespace import error when other modules from avi namespaces +# are imported. Added import of absolute_import to avoid import collisions for +# avi.sdk. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.network.plugins.module_utils.network.avi.ansible_utils import ( + avi_ansible_api, avi_common_argument_spec, ansible_return, + avi_obj_cmp, cleanup_absent_fields, AviCheckModeResponse, HAS_AVI) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi_api.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi_api.py new file mode 100644 index 00000000..684b2dd1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/avi/avi_api.py @@ -0,0 +1,974 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import os +import sys +import copy +import json +import logging +import time +from datetime import datetime, timedelta +from ssl import SSLError + + +class MockResponse(object): + def __init__(self, *args, **kwargs): + raise Exception("Requests library Response object not found. Using fake one.") + + +class MockRequestsConnectionError(Exception): + pass + + +class MockSession(object): + def __init__(self, *args, **kwargs): + raise Exception("Requests library Session object not found. Using fake one.") + + +HAS_AVI = True +try: + from requests import ConnectionError as RequestsConnectionError + from requests import Response + from requests.sessions import Session +except ImportError: + HAS_AVI = False + Response = MockResponse + RequestsConnectionError = MockRequestsConnectionError + Session = MockSession + + +logger = logging.getLogger(__name__) + +sessionDict = {} + + +def avi_timedelta(td): + ''' + This is a wrapper class to workaround python 2.6 builtin datetime.timedelta + does not have total_seconds method + :param timedelta object + ''' + if type(td) != timedelta: + raise TypeError() + if sys.version_info >= (2, 7): + ts = td.total_seconds() + else: + ts = td.seconds + (24 * 3600 * td.days) + return ts + + +def avi_sdk_syslog_logger(logger_name='avi.sdk'): + # The following sets up syslog module to log underlying avi SDK messages + # based on the environment variables: + # AVI_LOG_HANDLER: names the logging handler to use. Only syslog is + # supported. + # AVI_LOG_LEVEL: Logging level used for the avi SDK. Default is DEBUG + # AVI_SYSLOG_ADDRESS: Destination address for the syslog handler. + # Default is /dev/log + from logging.handlers import SysLogHandler + lf = '[%(asctime)s] %(levelname)s [%(module)s.%(funcName)s:%(lineno)d] %(message)s' + log = logging.getLogger(logger_name) + log_level = os.environ.get('AVI_LOG_LEVEL', 'DEBUG') + if log_level: + log.setLevel(getattr(logging, log_level)) + formatter = logging.Formatter(lf) + sh = SysLogHandler(address=os.environ.get('AVI_SYSLOG_ADDRESS', '/dev/log')) + sh.setFormatter(formatter) + log.addHandler(sh) + return log + + +class ObjectNotFound(Exception): + pass + + +class APIError(Exception): + def __init__(self, arg, rsp=None): + self.args = [arg, rsp] + self.rsp = rsp + + +class AviServerError(APIError): + def __init__(self, arg, rsp=None): + super(AviServerError, self).__init__(arg, rsp) + + +class APINotImplemented(Exception): + pass + + +class ApiResponse(Response): + """ + Returns copy of the requests.Response object provides additional helper + routines + 1. obj: returns dictionary of Avi Object + """ + def __init__(self, rsp): + super(ApiResponse, self).__init__() + for k, v in list(rsp.__dict__.items()): + setattr(self, k, v) + + def json(self): + """ + Extends the session default json interface to handle special errors + and raise Exceptions + returns the Avi object as a dictionary from rsp.text + """ + if self.status_code in (200, 201): + if not self.text: + # In cases like status_code == 201 the response text could be + # empty string. + return None + return super(ApiResponse, self).json() + elif self.status_code == 204: + # No response needed; e.g., delete operation + return None + elif self.status_code == 404: + raise ObjectNotFound('HTTP Error: %s Error Msg %s' % ( + self.status_code, self.text), self) + elif self.status_code >= 500: + raise AviServerError('HTTP Error: %s Error Msg %s' % ( + self.status_code, self.text), self) + else: + raise APIError('HTTP Error: %s Error Msg %s' % ( + self.status_code, self.text), self) + + def count(self): + """ + return the number of objects in the collection response. If it is not + a collection response then it would simply return 1. + """ + obj = self.json() + if 'count' in obj: + # this was a resposne to collection + return obj['count'] + return 1 + + @staticmethod + def to_avi_response(resp): + if type(resp) == Response: + return ApiResponse(resp) + return resp + + +class AviCredentials(object): + controller = '' + username = '' + password = '' + api_version = '16.4.4' + tenant = None + tenant_uuid = None + token = None + port = None + timeout = 300 + session_id = None + csrftoken = None + + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + def update_from_ansible_module(self, m): + """ + :param m: ansible module + :return: + """ + if m.params.get('avi_credentials'): + for k, v in m.params['avi_credentials'].items(): + if hasattr(self, k): + setattr(self, k, v) + if m.params['controller']: + self.controller = m.params['controller'] + if m.params['username']: + self.username = m.params['username'] + if m.params['password']: + self.password = m.params['password'] + if (m.params['api_version'] and + (m.params['api_version'] != '16.4.4')): + self.api_version = m.params['api_version'] + if m.params['tenant']: + self.tenant = m.params['tenant'] + if m.params['tenant_uuid']: + self.tenant_uuid = m.params['tenant_uuid'] + if m.params.get('session_id'): + self.session_id = m.params['session_id'] + if m.params.get('csrftoken'): + self.csrftoken = m.params['csrftoken'] + + def __str__(self): + return 'controller %s user %s api %s tenant %s' % ( + self.controller, self.username, self.api_version, self.tenant) + + +class ApiSession(Session): + """ + Extends the Request library's session object to provide helper + utilities to work with Avi Controller like authentication, api massaging + etc. + """ + + # This keeps track of the process which created the cache. + # At anytime the pid of the process changes then it would create + # a new cache for that process. + AVI_SLUG = 'Slug' + SESSION_CACHE_EXPIRY = 20 * 60 + SHARED_USER_HDRS = ['X-CSRFToken', 'Session-Id', 'Referer', 'Content-Type'] + MAX_API_RETRIES = 3 + + def __init__(self, controller_ip=None, username=None, password=None, + token=None, tenant=None, tenant_uuid=None, verify=False, + port=None, timeout=60, api_version=None, + retry_conxn_errors=True, data_log=False, + avi_credentials=None, session_id=None, csrftoken=None, + lazy_authentication=False, max_api_retries=None): + """ + ApiSession takes ownership of avi_credentials and may update the + information inside it. + + Initialize new session object with authenticated token from login api. + It also keeps a cache of user sessions that are cleaned up if inactive + for more than 20 mins. + + Notes: + 01. If mode is https and port is none or 443, we don't embed the + port in the prefix. The prefix would be 'https://ip'. If port + is a non-default value then we concatenate https://ip:port + in the prefix. + 02. If mode is http and the port is none or 80, we don't embed the + port in the prefix. The prefix would be 'http://ip'. If port is + a non-default value, then we concatenate http://ip:port in + the prefix. + """ + super(ApiSession, self).__init__() + if not avi_credentials: + tenant = tenant if tenant else "admin" + self.avi_credentials = AviCredentials( + controller=controller_ip, username=username, password=password, + api_version=api_version, tenant=tenant, tenant_uuid=tenant_uuid, + token=token, port=port, timeout=timeout, + session_id=session_id, csrftoken=csrftoken) + else: + self.avi_credentials = avi_credentials + self.headers = {} + self.verify = verify + self.retry_conxn_errors = retry_conxn_errors + self.remote_api_version = {} + self.session_cookie_name = '' + self.user_hdrs = {} + self.data_log = data_log + self.num_session_retries = 0 + self.retry_wait_time = 0 + self.max_session_retries = ( + self.MAX_API_RETRIES if max_api_retries is None + else int(max_api_retries)) + # Refer Notes 01 and 02 + k_port = port if port else 443 + if self.avi_credentials.controller.startswith('http'): + k_port = 80 if not self.avi_credentials.port else k_port + if self.avi_credentials.port is None or self.avi_credentials.port\ + == 80: + self.prefix = self.avi_credentials.controller + else: + self.prefix = '{x}:{y}'.format( + x=self.avi_credentials.controller, + y=self.avi_credentials.port) + else: + if port is None or port == 443: + self.prefix = 'https://{x}'.format( + x=self.avi_credentials.controller) + else: + self.prefix = 'https://{x}:{y}'.format( + x=self.avi_credentials.controller, + y=self.avi_credentials.port) + self.timeout = timeout + self.key = '%s:%s:%s' % (self.avi_credentials.controller, + self.avi_credentials.username, k_port) + # Added api token and session id to sessionDict for handle single + # session + if self.avi_credentials.csrftoken: + sessionDict[self.key] = { + 'api': self, + "csrftoken": self.avi_credentials.csrftoken, + "session_id": self.avi_credentials.session_id, + "last_used": datetime.utcnow() + } + elif lazy_authentication: + sessionDict.get(self.key, {}).update( + {'api': self, "last_used": datetime.utcnow()}) + else: + self.authenticate_session() + + self.num_session_retries = 0 + self.pid = os.getpid() + ApiSession._clean_inactive_sessions() + return + + @property + def controller_ip(self): + return self.avi_credentials.controller + + @controller_ip.setter + def controller_ip(self, controller_ip): + self.avi_credentials.controller = controller_ip + + @property + def username(self): + return self.avi_credentials.username + + @property + def connected(self): + return sessionDict.get(self.key, {}).get('connected', False) + + @username.setter + def username(self, username): + self.avi_credentials.username = username + + @property + def password(self): + return self.avi_credentials.password + + @password.setter + def password(self, password): + self.avi_credentials.password = password + + @property + def keystone_token(self): + return sessionDict.get(self.key, {}).get('csrftoken', None) + + @keystone_token.setter + def keystone_token(self, token): + sessionDict[self.key]['csrftoken'] = token + + @property + def tenant_uuid(self): + self.avi_credentials.tenant_uuid + + @tenant_uuid.setter + def tenant_uuid(self, tenant_uuid): + self.avi_credentials.tenant_uuid = tenant_uuid + + @property + def tenant(self): + return self.avi_credentials.tenant + + @tenant.setter + def tenant(self, tenant): + if tenant: + self.avi_credentials.tenant = tenant + else: + self.avi_credentials.tenant = 'admin' + + @property + def port(self): + self.avi_credentials.port + + @port.setter + def port(self, port): + self.avi_credentials.port = port + + @property + def api_version(self): + return self.avi_credentials.api_version + + @api_version.setter + def api_version(self, api_version): + self.avi_credentials.api_version = api_version + + @property + def session_id(self): + return sessionDict[self.key]['session_id'] + + def get_context(self): + return { + 'session_id': sessionDict[self.key]['session_id'], + 'csrftoken': sessionDict[self.key]['csrftoken'] + } + + @staticmethod + def clear_cached_sessions(): + global sessionDict + sessionDict = {} + + @staticmethod + def get_session( + controller_ip=None, username=None, password=None, token=None, tenant=None, + tenant_uuid=None, verify=False, port=None, timeout=60, + retry_conxn_errors=True, api_version=None, data_log=False, + avi_credentials=None, session_id=None, csrftoken=None, + lazy_authentication=False, max_api_retries=None): + """ + returns the session object for same user and tenant + calls init if session dose not exist and adds it to session cache + :param controller_ip: controller IP address + :param username: + :param password: + :param token: Token to use; example, a valid keystone token + :param tenant: Name of the tenant on Avi Controller + :param tenant_uuid: Don't specify tenant when using tenant_id + :param port: Rest-API may use a different port other than 443 + :param timeout: timeout for API calls; Default value is 60 seconds + :param retry_conxn_errors: retry on connection errors + :param api_version: Controller API version + """ + if not avi_credentials: + tenant = tenant if tenant else "admin" + avi_credentials = AviCredentials( + controller=controller_ip, username=username, password=password, + api_version=api_version, tenant=tenant, tenant_uuid=tenant_uuid, + token=token, port=port, timeout=timeout, + session_id=session_id, csrftoken=csrftoken) + + k_port = avi_credentials.port if avi_credentials.port else 443 + if avi_credentials.controller.startswith('http'): + k_port = 80 if not avi_credentials.port else k_port + key = '%s:%s:%s' % (avi_credentials.controller, + avi_credentials.username, k_port) + cached_session = sessionDict.get(key) + if cached_session: + user_session = cached_session['api'] + if not (user_session.avi_credentials.csrftoken or + lazy_authentication): + user_session.authenticate_session() + else: + user_session = ApiSession( + controller_ip, username, password, token=token, tenant=tenant, + tenant_uuid=tenant_uuid, verify=verify, port=port, + timeout=timeout, retry_conxn_errors=retry_conxn_errors, + api_version=api_version, data_log=data_log, + avi_credentials=avi_credentials, + lazy_authentication=lazy_authentication, + max_api_retries=max_api_retries) + ApiSession._clean_inactive_sessions() + return user_session + + def reset_session(self): + """ + resets and re-authenticates the current session. + """ + sessionDict[self.key]['connected'] = False + logger.info('resetting session for %s', self.key) + self.user_hdrs = {} + for k, v in self.headers.items(): + if k not in self.SHARED_USER_HDRS: + self.user_hdrs[k] = v + self.headers = {} + self.authenticate_session() + + def authenticate_session(self): + """ + Performs session authentication with Avi controller and stores + session cookies and sets header options like tenant. + """ + body = {"username": self.avi_credentials.username} + if self.avi_credentials.password: + body["password"] = self.avi_credentials.password + elif self.avi_credentials.token: + body["token"] = self.avi_credentials.token + else: + raise APIError("Neither user password or token provided") + logger.debug('authenticating user %s prefix %s', + self.avi_credentials.username, self.prefix) + self.cookies.clear() + err = None + try: + rsp = super(ApiSession, self).post( + self.prefix + "/login", body, timeout=self.timeout, verify=self.verify) + + if rsp.status_code == 200: + self.num_session_retries = 0 + self.remote_api_version = rsp.json().get('version', {}) + self.session_cookie_name = rsp.json().get('session_cookie_name', 'sessionid') + self.headers.update(self.user_hdrs) + if rsp.cookies and 'csrftoken' in rsp.cookies: + csrftoken = rsp.cookies['csrftoken'] + sessionDict[self.key] = { + 'csrftoken': csrftoken, + 'session_id': rsp.cookies[self.session_cookie_name], + 'last_used': datetime.utcnow(), + 'api': self, + 'connected': True + } + logger.debug("authentication success for user %s", + self.avi_credentials.username) + return + # Check for bad request and invalid credentials response code + elif rsp.status_code in [401, 403]: + logger.error('Status Code %s msg %s', rsp.status_code, rsp.text) + err = APIError('Status Code %s msg %s' % ( + rsp.status_code, rsp.text), rsp) + raise err + else: + logger.error("Error status code %s msg %s", rsp.status_code, + rsp.text) + err = APIError('Status Code %s msg %s' % ( + rsp.status_code, rsp.text), rsp) + except (RequestsConnectionError, SSLError) as e: + if not self.retry_conxn_errors: + raise + logger.warning('Connection error retrying %s', e) + err = e + # comes here only if there was either exception or login was not + # successful + if self.retry_wait_time: + time.sleep(self.retry_wait_time) + self.num_session_retries += 1 + if self.num_session_retries > self.max_session_retries: + self.num_session_retries = 0 + logger.error("giving up after %d retries connection failure %s", + self.max_session_retries, True) + ret_err = ( + err if err else APIError("giving up after %d retries connection failure %s" % + (self.max_session_retries, True))) + raise ret_err + self.authenticate_session() + return + + def _get_api_headers(self, tenant, tenant_uuid, timeout, headers, + api_version): + """ + returns the headers that are passed to the requests.Session api calls. + """ + api_hdrs = copy.deepcopy(self.headers) + api_hdrs.update({ + "Referer": self.prefix, + "Content-Type": "application/json" + }) + api_hdrs['timeout'] = str(timeout) + if self.key in sessionDict and 'csrftoken' in sessionDict.get(self.key): + api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken'] + else: + self.authenticate_session() + api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken'] + if api_version: + api_hdrs['X-Avi-Version'] = api_version + elif self.avi_credentials.api_version: + api_hdrs['X-Avi-Version'] = self.avi_credentials.api_version + if tenant: + tenant_uuid = None + elif tenant_uuid: + tenant = None + else: + tenant = self.avi_credentials.tenant + tenant_uuid = self.avi_credentials.tenant_uuid + if tenant_uuid: + api_hdrs.update({"X-Avi-Tenant-UUID": "%s" % tenant_uuid}) + api_hdrs.pop("X-Avi-Tenant", None) + elif tenant: + api_hdrs.update({"X-Avi-Tenant": "%s" % tenant}) + api_hdrs.pop("X-Avi-Tenant-UUID", None) + # Override any user headers that were passed by users. We don't know + # when the user had updated the user_hdrs + if self.user_hdrs: + api_hdrs.update(self.user_hdrs) + if headers: + # overwrite the headers passed via the API calls. + api_hdrs.update(headers) + return api_hdrs + + def _api(self, api_name, path, tenant, tenant_uuid, data=None, + headers=None, timeout=None, api_version=None, **kwargs): + """ + It calls the requests.Session APIs and handles session expiry + and other situations where session needs to be reset. + returns ApiResponse object + :param path: takes relative path to the AVI api. + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param headers: dictionary of headers that override the session + headers. + """ + if self.pid != os.getpid(): + logger.info('pid %d change detected new %d. Closing session', + self.pid, os.getpid()) + self.close() + self.pid = os.getpid() + if timeout is None: + timeout = self.timeout + fullpath = self._get_api_path(path) + fn = getattr(super(ApiSession, self), api_name) + api_hdrs = self._get_api_headers(tenant, tenant_uuid, timeout, headers, + api_version) + connection_error = False + err = None + cookies = { + 'csrftoken': api_hdrs['X-CSRFToken'], + } + try: + if self.session_cookie_name: + cookies[self.session_cookie_name] = sessionDict[self.key]['session_id'] + except KeyError: + pass + try: + if (data is not None) and (type(data) == dict): + resp = fn(fullpath, data=json.dumps(data), headers=api_hdrs, + timeout=timeout, cookies=cookies, **kwargs) + else: + resp = fn(fullpath, data=data, headers=api_hdrs, + timeout=timeout, cookies=cookies, **kwargs) + except (RequestsConnectionError, SSLError) as e: + logger.warning('Connection error retrying %s', e) + if not self.retry_conxn_errors: + raise + connection_error = True + err = e + except Exception as e: + logger.error('Error in Requests library %s', e) + raise + if not connection_error: + logger.debug('path: %s http_method: %s hdrs: %s params: ' + '%s data: %s rsp: %s', fullpath, api_name.upper(), + api_hdrs, kwargs, data, + (resp.text if self.data_log else 'None')) + if connection_error or resp.status_code in (401, 419): + if connection_error: + try: + self.close() + except Exception: + # ignoring exception in cleanup path + pass + logger.warning('Connection failed, retrying.') + # Adding sleep before retrying + if self.retry_wait_time: + time.sleep(self.retry_wait_time) + else: + logger.info('received error %d %s so resetting connection', + resp.status_code, resp.text) + ApiSession.reset_session(self) + self.num_session_retries += 1 + if self.num_session_retries > self.max_session_retries: + # Added this such that any code which re-tries can succeed + # eventually. + self.num_session_retries = 0 + if not connection_error: + err = APIError('Status Code %s msg %s' % ( + resp.status_code, resp.text), resp) + logger.error( + "giving up after %d retries conn failure %s err %s", + self.max_session_retries, connection_error, err) + ret_err = ( + err if err else APIError("giving up after %d retries connection failure %s" % + (self.max_session_retries, True))) + raise ret_err + # should restore the updated_hdrs to one passed down + resp = self._api(api_name, path, tenant, tenant_uuid, data, + headers=headers, api_version=api_version, + timeout=timeout, **kwargs) + self.num_session_retries = 0 + + if resp.cookies and 'csrftoken' in resp.cookies: + csrftoken = resp.cookies['csrftoken'] + self.headers.update({"X-CSRFToken": csrftoken}) + self._update_session_last_used() + return ApiResponse.to_avi_response(resp) + + def get_controller_details(self): + result = { + "controller_ip": self.controller_ip, + "controller_api_version": self.remote_api_version + } + return result + + def get(self, path, tenant='', tenant_uuid='', timeout=None, params=None, + api_version=None, **kwargs): + """ + It extends the Session Library interface to add AVI API prefixes, + handle session exceptions related to authentication and update + the global user session cache. + :param path: takes relative path to the AVI api. + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + get method takes relative path to service and kwargs as per Session + class get method + returns session's response object + """ + return self._api('get', path, tenant, tenant_uuid, timeout=timeout, + params=params, api_version=api_version, **kwargs) + + def get_object_by_name(self, path, name, tenant='', tenant_uuid='', + timeout=None, params=None, api_version=None, + **kwargs): + """ + Helper function to access Avi REST Objects using object + type and name. It behaves like python dictionary interface where it + returns None when the object is not present in the AviController. + Internally, it transforms the request to api/path?name=... + :param path: relative path to service + :param name: name of the object + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns dictionary object if successful else None + """ + obj = None + if not params: + params = {} + params['name'] = name + resp = self.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + timeout=timeout, + params=params, api_version=api_version, **kwargs) + if resp.status_code in (401, 419): + ApiSession.reset_session(self) + resp = self.get_object_by_name( + path, name, tenant, tenant_uuid, timeout=timeout, + params=params, **kwargs) + if resp.status_code > 499 or 'Invalid version' in resp.text: + logger.error('Error in get object by name for %s named %s. ' + 'Error: %s', path, name, resp.text) + raise AviServerError(resp.text, rsp=resp) + elif resp.status_code > 299: + return obj + try: + if 'results' in resp.json(): + obj = resp.json()['results'][0] + else: + # For apis returning single object eg. api/cluster + obj = resp.json() + except IndexError: + logger.warning('Warning: Object Not found for %s named %s', + path, name) + obj = None + self._update_session_last_used() + return obj + + def post(self, path, data=None, tenant='', tenant_uuid='', timeout=None, + force_uuid=None, params=None, api_version=None, **kwargs): + """ + It extends the Session Library interface to add AVI API prefixes, + handle session exceptions related to authentication and update + the global user session cache. + :param path: takes relative path to the AVI api.It is modified by + the library to conform to AVI Controller's REST API interface + :param data: dictionary of the data. Support for json string + is deprecated + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + if force_uuid is not None: + headers = kwargs.get('headers', {}) + headers[self.AVI_SLUG] = force_uuid + kwargs['headers'] = headers + return self._api('post', path, tenant, tenant_uuid, data=data, + timeout=timeout, params=params, + api_version=api_version, **kwargs) + + def put(self, path, data=None, tenant='', tenant_uuid='', + timeout=None, params=None, api_version=None, **kwargs): + """ + It extends the Session Library interface to add AVI API prefixes, + handle session exceptions related to authentication and update + the global user session cache. + :param path: takes relative path to the AVI api.It is modified by + the library to conform to AVI Controller's REST API interface + :param data: dictionary of the data. Support for json string + is deprecated + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + return self._api('put', path, tenant, tenant_uuid, data=data, + timeout=timeout, params=params, + api_version=api_version, **kwargs) + + def patch(self, path, data=None, tenant='', tenant_uuid='', + timeout=None, params=None, api_version=None, **kwargs): + """ + It extends the Session Library interface to add AVI API prefixes, + handle session exceptions related to authentication and update + the global user session cache. + :param path: takes relative path to the AVI api.It is modified by + the library to conform to AVI Controller's REST API interface + :param data: dictionary of the data. Support for json string + is deprecated + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + return self._api('patch', path, tenant, tenant_uuid, data=data, + timeout=timeout, params=params, + api_version=api_version, **kwargs) + + def put_by_name(self, path, name, data=None, tenant='', + tenant_uuid='', timeout=None, params=None, + api_version=None, **kwargs): + """ + Helper function to perform HTTP PUT on Avi REST Objects using object + type and name. + Internally, it transforms the request to api/path?name=... + :param path: relative path to service + :param name: name of the object + :param data: dictionary of the data. Support for json string + is deprecated + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + uuid = self._get_uuid_by_name( + path, name, tenant, tenant_uuid, api_version=api_version) + path = '%s/%s' % (path, uuid) + return self.put(path, data, tenant, tenant_uuid, timeout=timeout, + params=params, api_version=api_version, **kwargs) + + def delete(self, path, tenant='', tenant_uuid='', timeout=None, params=None, + data=None, api_version=None, **kwargs): + """ + It extends the Session Library interface to add AVI API prefixes, + handle session exceptions related to authentication and update + the global user session cache. + :param path: takes relative path to the AVI api.It is modified by + the library to conform to AVI Controller's REST API interface + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param data: dictionary of the data. Support for json string + is deprecated + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + return self._api('delete', path, tenant, tenant_uuid, data=data, + timeout=timeout, params=params, + api_version=api_version, **kwargs) + + def delete_by_name(self, path, name, tenant='', tenant_uuid='', + timeout=None, params=None, api_version=None, **kwargs): + """ + Helper function to perform HTTP DELETE on Avi REST Objects using object + type and name.Internally, it transforms the request to + api/path?name=... + :param path: relative path to service + :param name: name of the object + :param tenant: overrides the tenant used during session creation + :param tenant_uuid: overrides the tenant or tenant_uuid during session + creation + :param timeout: timeout for API calls; Default value is 60 seconds + :param params: dictionary of key value pairs to be sent as query + parameters + :param api_version: overrides x-avi-header in request header during + session creation + returns session's response object + """ + uuid = self._get_uuid_by_name(path, name, tenant, tenant_uuid, + api_version=api_version) + if not uuid: + raise ObjectNotFound("%s/?name=%s" % (path, name)) + path = '%s/%s' % (path, uuid) + return self.delete(path, tenant, tenant_uuid, timeout=timeout, + params=params, api_version=api_version, **kwargs) + + def get_obj_ref(self, obj): + """returns reference url from dict object""" + if not obj: + return None + if isinstance(obj, Response): + obj = json.loads(obj.text) + if obj.get(0, None): + return obj[0]['url'] + elif obj.get('url', None): + return obj['url'] + elif obj.get('results', None): + return obj['results'][0]['url'] + else: + return None + + def get_obj_uuid(self, obj): + """returns uuid from dict object""" + if not obj: + raise ObjectNotFound('Object %s Not found' % (obj)) + if isinstance(obj, Response): + obj = json.loads(obj.text) + if obj.get(0, None): + return obj[0]['uuid'] + elif obj.get('uuid', None): + return obj['uuid'] + elif obj.get('results', None): + return obj['results'][0]['uuid'] + else: + return None + + def _get_api_path(self, path, uuid=None): + """ + This function returns the full url from relative path and uuid. + """ + if path == 'logout': + return self.prefix + '/' + path + elif uuid: + return self.prefix + '/api/' + path + '/' + uuid + else: + return self.prefix + '/api/' + path + + def _get_uuid_by_name(self, path, name, tenant='admin', + tenant_uuid='', api_version=None): + """gets object by name and service path and returns uuid""" + resp = self.get_object_by_name( + path, name, tenant, tenant_uuid, api_version=api_version) + if not resp: + raise ObjectNotFound("%s/%s" % (path, name)) + return self.get_obj_uuid(resp) + + def _update_session_last_used(self): + if self.key in sessionDict: + sessionDict[self.key]["last_used"] = datetime.utcnow() + + @staticmethod + def _clean_inactive_sessions(): + """Removes sessions which are inactive more than 20 min""" + session_cache = sessionDict + logger.debug("cleaning inactive sessions in pid %d num elem %d", + os.getpid(), len(session_cache)) + keys_to_delete = [] + for key, session in list(session_cache.items()): + tdiff = avi_timedelta(datetime.utcnow() - session["last_used"]) + if tdiff < ApiSession.SESSION_CACHE_EXPIRY: + continue + keys_to_delete.append(key) + for key in keys_to_delete: + del session_cache[key] + logger.debug("Removed session for : %s", key) + + def delete_session(self): + """ Removes the session for cleanup""" + logger.debug("Removed session for : %s", self.key) + sessionDict.pop(self.key, None) + return +# End of file diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/bigswitch.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/bigswitch.py new file mode 100644 index 00000000..80912696 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/bigswitch/bigswitch.py @@ -0,0 +1,94 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016, Ted Elhourani +# +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible.module_utils.urls import fetch_url + + +class Response(object): + + def __init__(self, resp, info): + self.body = None + if resp: + self.body = resp.read() + self.info = info + + @property + def json(self): + if not self.body: + if "body" in self.info: + return json.loads(self.info["body"]) + return None + try: + return json.loads(self.body) + except ValueError: + return None + + @property + def status_code(self): + return self.info["status"] + + +class Rest(object): + + def __init__(self, module, headers, baseurl): + self.module = module + self.headers = headers + self.baseurl = baseurl + + def _url_builder(self, path): + if path[0] == '/': + path = path[1:] + return '%s/%s' % (self.baseurl, path) + + def send(self, method, path, data=None, headers=None): + url = self._url_builder(path) + data = self.module.jsonify(data) + + resp, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method) + + return Response(resp, info) + + def get(self, path, data=None, headers=None): + return self.send('GET', path, data, headers) + + def put(self, path, data=None, headers=None): + return self.send('PUT', path, data, headers) + + def post(self, path, data=None, headers=None): + return self.send('POST', path, data, headers) + + def patch(self, path, data=None, headers=None): + return self.send('PATCH', path, data, headers) + + def delete(self, path, data=None, headers=None): + return self.send('DELETE', path, data, headers) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/checkpoint/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/checkpoint/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/ce.py new file mode 100644 index 00000000..195e6666 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cloudengine/ce.py @@ -0,0 +1,427 @@ +# +# This code is part of Ansible, but is an independent component. +# +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2017 Red Hat, Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +import socket +import sys +import traceback + +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import exec_command, ConnectionError +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_native +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import NetconfConnection + + +try: + from ncclient.xml_ import to_xml, new_ele_ns + HAS_NCCLIENT = True +except ImportError: + HAS_NCCLIENT = False + + +try: + from lxml import etree +except ImportError: + from xml.etree import ElementTree as etree + +_DEVICE_CLI_CONNECTION = None +_DEVICE_NC_CONNECTION = None + +ce_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'use_ssl': dict(type='bool'), + 'validate_certs': dict(type='bool'), + 'timeout': dict(type='int'), + 'transport': dict(default='cli', choices=['cli', 'netconf']), +} +ce_argument_spec = { + 'provider': dict(type='dict', options=ce_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network'), +} + + +def to_string(data): + return re.sub(r'|>)', r' 2 and err[0] in ["<", "["] and err[-1] in [">", "]"]: + continue + err.strip('.,\r\n\t ') + if err: + msg.append(err) + + if cmd: + msg.insert(0, "Command: %s" % cmd) + + return ", ".join(msg).capitalize() + "." + + +def to_command(module, commands): + default_output = 'text' + transform = ComplexList(dict( + command=dict(key=True), + output=dict(default=default_output), + prompt=dict(), + answer=dict() + ), module) + + commands = transform(to_list(commands)) + + return commands + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + conn = get_connection(module) + return conn.get_config(flags) + + +def run_commands(module, commands, check_rc=True): + conn = get_connection(module) + return conn.run_commands(to_command(module, commands), check_rc) + + +def load_config(module, config): + """load_config""" + conn = get_connection(module) + return conn.load_config(config) + + +def ce_unknown_host_cb(host, fingerprint): + """ ce_unknown_host_cb """ + + return True + + +def get_nc_set_id(xml_str): + """get netconf set-id value""" + + result = re.findall(r'= 0 and index >= len(xml_list): + return None + if index < 0 and abs(index) > len(xml_list): + return None + + ele = xml_list[index] + if not ele.replace(" ", ""): + xml_list.pop(index) + ele = None + return ele + + +def merge_nc_xml(xml1, xml2): + """merge xml1 and xml2""" + + xml1_list = xml1.split("")[0].split("\n") + xml2_list = xml2.split("")[1].split("\n") + + while True: + xml1_ele1 = get_xml_line(xml1_list, -1) + xml1_ele2 = get_xml_line(xml1_list, -2) + xml2_ele1 = get_xml_line(xml2_list, 0) + xml2_ele2 = get_xml_line(xml2_list, 1) + if not xml1_ele1 or not xml1_ele2 or not xml2_ele1 or not xml2_ele2: + return xml1 + + if "xmlns" in xml2_ele1: + xml2_ele1 = xml2_ele1.lstrip().split(" ")[0] + ">" + if "xmlns" in xml2_ele2: + xml2_ele2 = xml2_ele2.lstrip().split(" ")[0] + ">" + if xml1_ele1.replace(" ", "").replace("/", "") == xml2_ele1.replace(" ", "").replace("/", ""): + if xml1_ele2.replace(" ", "").replace("/", "") == xml2_ele2.replace(" ", "").replace("/", ""): + xml1_list.pop() + xml2_list.pop(0) + else: + break + else: + break + + return "\n".join(xml1_list + xml2_list) + + +def get_nc_connection(module): + global _DEVICE_NC_CONNECTION + if not _DEVICE_NC_CONNECTION: + load_params(module) + conn = NetconfConnection(module._socket_path) + _DEVICE_NC_CONNECTION = conn + return _DEVICE_NC_CONNECTION + + +def set_nc_config(module, xml_str): + """ set_config """ + + conn = get_nc_connection(module) + try: + out = conn.edit_config(target='running', config=xml_str, default_operation='merge', + error_option='rollback-on-error') + except Exception as e: + message = re.findall(r'(.*)', str(e)) + if message: + module.fail_json(msg='Error: %s' % message[0]) + else: + module.fail_json(msg='Error: %s' % str(e)) + else: + return to_string(to_xml(out)) + # finally: + # conn.unlock(target = 'candidate') + # pass + + +def get_nc_next(module, xml_str): + """ get_nc_next for exchange capability """ + + conn = get_nc_connection(module) + result = None + if xml_str is not None: + response = conn.get(xml_str, if_rpc_reply=True) + result = response.find('./*') + set_id = response.get('set-id') + while True and set_id is not None: + try: + fetch_node = new_ele_ns('get-next', 'http://www.huawei.com/netconf/capability/base/1.0', {'set-id': set_id}) + next_xml = conn.dispatch_rpc(etree.tostring(fetch_node)) + if next_xml is not None: + result.extend(next_xml.find('./*')) + set_id = next_xml.get('set-id') + except ConnectionError: + break + if result is not None: + return to_string(to_xml(result)) + return result + + +def get_nc_config(module, xml_str): + """ get_config """ + + conn = get_nc_connection(module) + if xml_str is not None: + try: + response = conn.get(xml_str) + except Exception as e: + message = re.findall(r'(.*)', str(e)) + if message: + module.fail_json(msg='Error: %s' % message[0]) + else: + module.fail_json(msg='Error: %s' % str(e)) + else: + return to_string(to_xml(response)) + else: + return None + + +def execute_nc_action(module, xml_str): + """ huawei execute-action """ + + conn = get_nc_connection(module) + response = conn.execute_action(xml_str) + return to_string(to_xml(response)) + + +def execute_nc_cli(module, xml_str): + """ huawei execute-cli """ + + if xml_str is not None: + try: + conn = get_nc_connection(module) + out = conn.execute_nc_cli(command=xml_str) + return to_string(to_xml(out)) + except Exception as exc: + raise Exception(exc) + + +def check_ip_addr(ipaddr): + """ check ip address, Supports IPv4 and IPv6 """ + + if not ipaddr or '\x00' in ipaddr: + return False + + try: + res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_NUMERICHOST) + return bool(res) + except socket.gaierror: + err = sys.exc_info()[1] + if err.args[0] == socket.EAI_NONAME: + return False + raise diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos.py new file mode 100644 index 00000000..92177e4a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos.py @@ -0,0 +1,664 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by +# Ansible still belong to the author of the module, and may assign their own +# license to the complete work. +# +# Copyright (C) 2017 Lenovo, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Contains utility methods +# Lenovo Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import time +import socket +import re +import json +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos_errorcodes + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos_devicerules + HAS_LIB = True +except Exception: + HAS_LIB = False +from distutils.cmd import Command +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection +from ansible.module_utils.connection import Connection, exec_command +from ansible.module_utils.connection import ConnectionError + +_DEVICE_CONFIGS = {} +_CONNECTION = None +_VALID_USER_ROLES = ['network-admin', 'network-operator'] + +cnos_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), + no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), + type='path'), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), + type='bool'), + 'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), + no_log=True), + 'timeout': dict(type='int'), + 'context': dict(), + 'passwords': dict(no_log=True) +} + +cnos_argument_spec = { + 'provider': dict(type='dict', options=cnos_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network'), +} + +command_spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict(), + 'check_all': dict() +} + + +def get_provider_argspec(): + return cnos_provider_spec + + +def check_args(module, warnings): + pass + + +def get_user_roles(): + return _VALID_USER_ROLES + + +def get_connection(module): + global _CONNECTION + if _CONNECTION: + return _CONNECTION + _CONNECTION = Connection(module._socket_path) + + context = None + try: + context = module.params['context'] + except KeyError: + context = None + + if context: + if context == 'system': + command = 'changeto system' + else: + command = 'changeto context %s' % context + _CONNECTION.get(command) + + return _CONNECTION + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + passwords = None + try: + passwords = module.params['passwords'] + except KeyError: + passwords = None + if passwords: + cmd = 'more system:running-config' + else: + cmd = 'display running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + conn = get_connection(module) + out = conn.get(cmd) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def to_commands(module, commands): + if not isinstance(commands, list): + raise AssertionError('argument must be of type ') + + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + module.warn('only show commands are supported when using check ' + 'mode, not executing `%s`' % item['command']) + + return commands + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + connection.get('enable') + commands = to_commands(module, to_list(commands)) + + responses = list() + + for cmd in commands: + out = connection.get(**cmd) + responses.append(to_text(out, errors='surrogate_then_replace')) + + return responses + + +def run_cnos_commands(module, commands, check_rc=True): + retVal = '' + enter_config = {'command': 'configure terminal', 'prompt': None, + 'answer': None} + exit_config = {'command': 'end', 'prompt': None, 'answer': None} + commands.insert(0, enter_config) + commands.append(exit_config) + for cmd in commands: + retVal = retVal + '>> ' + cmd['command'] + '\n' + try: + responses = run_commands(module, commands, check_rc) + for response in responses: + retVal = retVal + '<< ' + response + '\n' + except Exception as e: + errMsg = '' + if hasattr(e, 'message'): + errMsg = e.message + else: + errMsg = str(e) + # Exception in Exceptions + if 'VLAN_ACCESS_MAP' in errMsg: + return retVal + '<<' + errMsg + '\n' + if 'confederation identifier' in errMsg: + return retVal + '<<' + errMsg + '\n' + # Add more here if required + retVal = retVal + '<< ' + 'Error-101 ' + errMsg + '\n' + return str(retVal) + + +def get_capabilities(module): + if hasattr(module, '_cnos_capabilities'): + return module._cnos_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._cnos_capabilities = json.loads(capabilities) + return module._cnos_capabilities + + +def load_config(module, config): + try: + conn = get_connection(module) + conn.get('enable') + resp = conn.edit_config(config) + return resp.get('response') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_defaults_flag(module): + rc, out, err = exec_command(module, 'display running-config ?') + out = to_text(out, errors='surrogate_then_replace') + + commands = set() + for line in out.splitlines(): + if line: + commands.add(line.strip().split()[0]) + + if 'all' in commands: + return 'all' + else: + return 'full' + + +def enterEnableModeForDevice(enablePassword, timeout, obj): + command = "enable\n" + pwdPrompt = "password:" + # debugOutput(enablePassword) + # debugOutput('\n') + obj.settimeout(int(timeout)) + # Executing enable + obj.send(command) + flag = False + retVal = "" + count = 5 + while not flag: + # If wait time is execeeded. + if(count == 0): + flag = True + else: + count = count - 1 + # A delay of one second + time.sleep(1) + try: + buffByte = obj.recv(9999) + buff = buffByte.decode() + retVal = retVal + buff + # debugOutput(buff) + gotit = buff.find(pwdPrompt) + if(gotit != -1): + time.sleep(1) + if(enablePassword is None or enablePassword == ""): + return "\n Error-106" + obj.send(enablePassword) + obj.send("\r") + obj.send("\n") + time.sleep(1) + innerBuffByte = obj.recv(9999) + innerBuff = innerBuffByte.decode() + retVal = retVal + innerBuff + # debugOutput(innerBuff) + innerGotit = innerBuff.find("#") + if(innerGotit != -1): + return retVal + else: + gotit = buff.find("#") + if(gotit != -1): + return retVal + except Exception: + retVal = retVal + "\n Error-101" + flag = True + if(retVal == ""): + retVal = "\n Error-101" + return retVal +# EOM + + +def waitForDeviceResponse(command, prompt, timeout, obj): + obj.settimeout(int(timeout)) + obj.send(command) + flag = False + retVal = "" + while not flag: + time.sleep(1) + try: + buffByte = obj.recv(9999) + buff = buffByte.decode() + retVal = retVal + buff + # debugOutput(retVal) + gotit = buff.find(prompt) + if(gotit != -1): + flag = True + except Exception: + # debugOutput(prompt) + if prompt == "(yes/no)?": + pass + elif prompt == "Password:": + pass + else: + retVal = retVal + "\n Error-101" + flag = True + return retVal +# EOM + + +def checkOutputForError(output): + retVal = "" + index = output.lower().find('error') + startIndex = index + 6 + if(index == -1): + index = output.lower().find('invalid') + startIndex = index + 8 + if(index == -1): + index = output.lower().find('cannot be enabled in l2 interface') + startIndex = index + 34 + if(index == -1): + index = output.lower().find('incorrect') + startIndex = index + 10 + if(index == -1): + index = output.lower().find('failure') + startIndex = index + 8 + if(index == -1): + return None + + endIndex = startIndex + 3 + errorCode = output[startIndex:endIndex] + result = errorCode.isdigit() + if(result is not True): + return "Device returned an Error. Please check Results for more \ + information" + + errorFile = "dictionary/ErrorCodes.lvo" + try: + # with open(errorFile, 'r') as f: + f = open(errorFile, 'r') + for line in f: + if('=' in line): + data = line.split('=') + if(data[0].strip() == errorCode): + errorString = data[1].strip() + return errorString + except Exception: + errorString = cnos_errorcodes.getErrorString(errorCode) + errorString = errorString.strip() + return errorString + return "Error Code Not Found" +# EOM + + +def checkSanityofVariable(deviceType, variableId, variableValue): + retVal = "" + ruleFile = "dictionary/" + deviceType + "_rules.lvo" + ruleString = getRuleStringForVariable(deviceType, ruleFile, variableId) + retVal = validateValueAgainstRule(ruleString, variableValue) + return retVal +# EOM + + +def getRuleStringForVariable(deviceType, ruleFile, variableId): + retVal = "" + try: + # with open(ruleFile, 'r') as f: + f = open(ruleFile, 'r') + for line in f: + # debugOutput(line) + if(':' in line): + data = line.split(':') + # debugOutput(data[0]) + if(data[0].strip() == variableId): + retVal = line + except Exception: + ruleString = cnos_devicerules.getRuleString(deviceType, variableId) + retVal = ruleString.strip() + return retVal +# EOM + + +def validateValueAgainstRule(ruleString, variableValue): + + retVal = "" + if(ruleString == ""): + return 1 + rules = ruleString.split(':') + variableType = rules[1].strip() + varRange = rules[2].strip() + if(variableType == "INTEGER"): + result = checkInteger(variableValue) + if(result is True): + return "ok" + else: + return "Error-111" + elif(variableType == "FLOAT"): + result = checkFloat(variableValue) + if(result is True): + return "ok" + else: + return "Error-112" + + elif(variableType == "INTEGER_VALUE"): + int_range = varRange.split('-') + r = range(int(int_range[0].strip()), int(int_range[1].strip())) + if(checkInteger(variableValue) is not True): + return "Error-111" + result = int(variableValue) in r + if(result is True): + return "ok" + else: + return "Error-113" + + elif(variableType == "INTEGER_VALUE_RANGE"): + int_range = varRange.split('-') + varLower = int_range[0].strip() + varHigher = int_range[1].strip() + r = range(int(varLower), int(varHigher)) + val_range = variableValue.split('-') + try: + valLower = val_range[0].strip() + valHigher = val_range[1].strip() + except Exception: + return "Error-113" + if((checkInteger(valLower) is not True) or + (checkInteger(valHigher) is not True)): + # debugOutput("Error-114") + return "Error-114" + result = (int(valLower) in r) and (int(valHigher) in r) \ + and (int(valLower) < int(valHigher)) + if(result is True): + return "ok" + else: + # debugOutput("Error-113") + return "Error-113" + + elif(variableType == "INTEGER_OPTIONS"): + int_options = varRange.split(',') + if(checkInteger(variableValue) is not True): + return "Error-111" + for opt in int_options: + if(opt.strip() is variableValue): + result = True + break + if(result is True): + return "ok" + else: + return "Error-115" + + elif(variableType == "LONG"): + result = checkLong(variableValue) + if(result is True): + return "ok" + else: + return "Error-116" + + elif(variableType == "LONG_VALUE"): + long_range = varRange.split('-') + r = range(int(long_range[0].strip()), int(long_range[1].strip())) + if(checkLong(variableValue) is not True): + # debugOutput(variableValue) + return "Error-116" + result = int(variableValue) in r + if(result is True): + return "ok" + else: + return "Error-113" + + elif(variableType == "LONG_VALUE_RANGE"): + long_range = varRange.split('-') + r = range(int(long_range[0].strip()), int(long_range[1].strip())) + val_range = variableValue.split('-') + if((checkLong(val_range[0]) is not True) or + (checkLong(val_range[1]) is not True)): + return "Error-117" + result = (val_range[0] in r) and ( + val_range[1] in r) and (val_range[0] < val_range[1]) + if(result is True): + return "ok" + else: + return "Error-113" + elif(variableType == "LONG_OPTIONS"): + long_options = varRange.split(',') + if(checkLong(variableValue) is not True): + return "Error-116" + for opt in long_options: + if(opt.strip() == variableValue): + result = True + break + if(result is True): + return "ok" + else: + return "Error-115" + + elif(variableType == "TEXT"): + if(variableValue == ""): + return "Error-118" + if(True is isinstance(variableValue, str)): + return "ok" + else: + return "Error-119" + + elif(variableType == "NO_VALIDATION"): + if(variableValue == ""): + return "Error-118" + else: + return "ok" + + elif(variableType == "TEXT_OR_EMPTY"): + if(variableValue is None or variableValue == ""): + return "ok" + if(result == isinstance(variableValue, str)): + return "ok" + else: + return "Error-119" + + elif(variableType == "MATCH_TEXT"): + if(variableValue == ""): + return "Error-118" + if(isinstance(variableValue, str)): + if(varRange == variableValue): + return "ok" + else: + return "Error-120" + else: + return "Error-119" + + elif(variableType == "MATCH_TEXT_OR_EMPTY"): + if(variableValue is None or variableValue == ""): + return "ok" + if(isinstance(variableValue, str)): + if(varRange == variableValue): + return "ok" + else: + return "Error-120" + else: + return "Error-119" + + elif(variableType == "TEXT_OPTIONS"): + str_options = varRange.split(',') + if(isinstance(variableValue, str) is not True): + return "Error-119" + result = False + for opt in str_options: + if(opt.strip() == variableValue): + result = True + break + if(result is True): + return "ok" + else: + return "Error-115" + + elif(variableType == "TEXT_OPTIONS_OR_EMPTY"): + if(variableValue is None or variableValue == ""): + return "ok" + str_options = varRange.split(',') + if(isinstance(variableValue, str) is not True): + return "Error-119" + for opt in str_options: + if(opt.strip() == variableValue): + result = True + break + if(result is True): + return "ok" + else: + return "Error-115" + + elif(variableType == "IPV4Address"): + try: + socket.inet_pton(socket.AF_INET, variableValue) + result = True + except socket.error: + result = False + if(result is True): + return "ok" + else: + return "Error-121" + elif(variableType == "IPV4AddressWithMask"): + if(variableValue is None or variableValue == ""): + return "Error-119" + str_options = variableValue.split('/') + ipaddr = str_options[0] + mask = str_options[1] + try: + socket.inet_pton(socket.AF_INET, ipaddr) + if(checkInteger(mask) is True): + result = True + else: + result = False + except socket.error: + result = False + if(result is True): + return "ok" + else: + return "Error-121" + + elif(variableType == "IPV6Address"): + try: + socket.inet_pton(socket.AF_INET6, variableValue) + result = True + except socket.error: + result = False + if(result is True): + return "ok" + else: + return "Error-122" + + return retVal +# EOM + + +def disablePaging(remote_conn): + remote_conn.send("terminal length 0\n") + time.sleep(1) + # Clear the buffer on the screen + outputByte = remote_conn.recv(1000) + output = outputByte.decode() + return output +# EOM + + +def checkInteger(s): + try: + int(s) + return True + except ValueError: + return False +# EOM + + +def checkFloat(s): + try: + float(s) + return True + except ValueError: + return False +# EOM + + +def checkLong(s): + try: + int(s) + return True + except ValueError: + return False + + +def debugOutput(command): + f = open('debugOutput.txt', 'a') + f.write(str(command)) # python will convert \n to os.linesep + f.close() # you can omit in most cases as the destructor will call it +# EOM diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_devicerules.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_devicerules.py new file mode 100644 index 00000000..5908dd25 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_devicerules.py @@ -0,0 +1,1924 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by +# Ansible still belong to the author of the module, and may assign their +# own license to the complete work. +# +# Copyright (C) 2017 Lenovo, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Contains device rule and methods +# Lenovo Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def getRuleString(deviceType, variableId): + retVal = variableId + ":" + if(deviceType == 'g8272_cnos'): + if variableId in g8272_cnos: + retVal = retVal + g8272_cnos[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'g8296_cnos'): + if variableId in g8296_cnos: + retVal = retVal + g8296_cnos[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'g8332_cnos'): + if variableId in g8332_cnos: + retVal = retVal + g8332_cnos[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE1072T'): + if variableId in NE1072T: + retVal = retVal + NE1072T[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE1032'): + if variableId in NE1032: + retVal = retVal + NE1032[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE1032T'): + if variableId in NE1032T: + retVal = retVal + NE1032T[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE10032'): + if variableId in NE10032: + retVal = retVal + NE10032[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE2572'): + if variableId in NE2572: + retVal = retVal + NE2572[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + elif(deviceType == 'NE0152T'): + if variableId in NE0152T: + retVal = retVal + NE0152T[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + else: + if variableId in default_cnos: + retVal = retVal + default_cnos[variableId] + else: + retVal = "The variable " + variableId + " is not supported" + return retVal +# EOM + + +default_cnos = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-32', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-32', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,\ + interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,\ + trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,\ + input,output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,\ + vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE0152T = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-52', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-52', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,\ + interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,\ + trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,\ + input,output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,\ + vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:10,100,1000,10000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE2572 = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-54', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-54', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE1032T = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-32', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-32', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE1032 = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-32', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-32', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE1072T = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-54', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-54', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +NE10032 = { + 'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-32', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-32', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:10000,100000,25000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', +} +g8272_cnos = {'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-64', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-54', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-54', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,input,\ + output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,40000', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', + } +g8296_cnos = {'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-128', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-96', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-96', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,\ + arp,dhcp,ospf,port,port-unreachable,redirects,router,\ + unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,\ + input,output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,40000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', + } +g8332_cnos = {'vlan_id': 'INTEGER_VALUE:1-3999', + 'vlan_id_range': 'INTEGER_VALUE_RANGE:1-3999', + 'vlan_name': 'TEXT:', + 'vlan_flood': 'TEXT_OPTIONS:ipv4,ipv6', + 'vlan_state': 'TEXT_OPTIONS:active,suspend', + 'vlan_last_member_query_interval': 'INTEGER_VALUE:1-25', + 'vlan_querier': 'IPV4Address:', + 'vlan_querier_timeout': 'INTEGER_VALUE:1-65535', + 'vlan_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_query_max_response_time': 'INTEGER_VALUE:1-25', + 'vlan_report_suppression': 'INTEGER_VALUE:1-25', + 'vlan_robustness_variable': 'INTEGER_VALUE:1-7', + 'vlan_startup_query_count': 'INTEGER_VALUE:1-10', + 'vlan_startup_query_interval': 'INTEGER_VALUE:1-18000', + 'vlan_snooping_version': 'INTEGER_VALUE:2-3', + 'vlan_access_map_name': 'TEXT: ', + 'vlan_ethernet_interface': 'TEXT:', + 'vlan_portagg_number': 'INTEGER_VALUE:1-4096', + 'vlan_accessmap_action': 'TEXT_OPTIONS:drop,forward,redirect', + 'vlan_dot1q_tag': 'MATCH_TEXT_OR_EMPTY:egress-only', + 'vlan_filter_name': 'TEXT:', + 'vlag_auto_recovery': 'INTEGER_VALUE:240-3600', + 'vlag_config_consistency': 'TEXT_OPTIONS:disable,strict', + 'vlag_instance': 'INTEGER_VALUE:1-128', + 'vlag_port_aggregation': 'INTEGER_VALUE:1-4096', + 'vlag_priority': 'INTEGER_VALUE:0-65535', + 'vlag_startup_delay': 'INTEGER_VALUE:0-3600', + 'vlag_tier_id': 'INTEGER_VALUE:1-512', + 'vlag_hlthchk_options': 'TEXT_OPTIONS:keepalive-attempts,\ + keepalive-interval,peer-ip,retry-interval', + 'vlag_keepalive_attempts': 'INTEGER_VALUE:1-24', + 'vlag_keepalive_interval': 'INTEGER_VALUE:2-300', + 'vlag_retry_interval': 'INTEGER_VALUE:1-300', + 'vlag_peerip': 'IPV4Address:', + 'vlag_peerip_vrf': 'TEXT_OPTIONS:default,management', + 'bgp_as_number': 'NO_VALIDATION:1-4294967295', + 'bgp_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_bgp_local_count': 'INTEGER_VALUE:2-64', + 'cluster_id_as_ip': 'IPV4Address:', + 'cluster_id_as_number': 'NO_VALIDATION:1-4294967295', + 'confederation_identifier': 'INTEGER_VALUE:1-65535', + 'condeferation_peers_as': 'INTEGER_VALUE:1-65535', + 'stalepath_delay_value': 'INTEGER_VALUE:1-3600', + 'maxas_limit_as': 'INTEGER_VALUE:1-2000', + 'neighbor_ipaddress': 'IPV4Address:', + 'neighbor_as': 'NO_VALIDATION:1-4294967295', + 'router_id': 'IPV4Address:', + 'bgp_keepalive_interval': 'INTEGER_VALUE:0-3600', + 'bgp_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_aggregate_prefix': 'IPV4AddressWithMask:', + 'addrfamily_routemap_name': 'TEXT:', + 'reachability_half_life': 'INTEGER_VALUE:1-45', + 'start_reuse_route_value': 'INTEGER_VALUE:1-20000', + 'start_suppress_route_value': 'INTEGER_VALUE:1-20000', + 'max_duration_to_suppress_route': 'INTEGER_VALUE:1-255', + 'unreachability_halftime_for_penalty': 'INTEGER_VALUE:1-45', + 'distance_external_AS': 'INTEGER_VALUE:1-255', + 'distance_internal_AS': 'INTEGER_VALUE:1-255', + 'distance_local_routes': 'INTEGER_VALUE:1-255', + 'maxpath_option': 'TEXT_OPTIONS:ebgp,ibgp', + 'maxpath_numbers': 'INTEGER_VALUE:2-32', + 'network_ip_prefix_with_mask': 'IPV4AddressWithMask:', + 'network_ip_prefix_value': 'IPV4Address:', + 'network_ip_prefix_mask': 'IPV4Address:', + 'nexthop_crtitical_delay': 'NO_VALIDATION:1-4294967295', + 'nexthop_noncrtitical_delay': 'NO_VALIDATION:1-4294967295', + 'addrfamily_redistribute_option': 'TEXT_OPTIONS:direct,ospf,\ + static', + 'bgp_neighbor_af_occurances': 'INTEGER_VALUE:1-10', + 'bgp_neighbor_af_filtername': 'TEXT:', + 'bgp_neighbor_af_maxprefix': 'INTEGER_VALUE:1-15870', + 'bgp_neighbor_af_prefixname': 'TEXT:', + 'bgp_neighbor_af_routemap': 'TEXT:', + 'bgp_neighbor_address_family': 'TEXT_OPTIONS:ipv4,ipv6', + 'bgp_neighbor_connection_retrytime': 'INTEGER_VALUE:1-65535', + 'bgp_neighbor_description': 'TEXT:', + 'bgp_neighbor_maxhopcount': 'INTEGER_VALUE:1-255', + 'bgp_neighbor_local_as': 'NO_VALIDATION:1-4294967295', + 'bgp_neighbor_maxpeers': 'INTEGER_VALUE:1-96', + 'bgp_neighbor_password': 'TEXT:', + 'bgp_neighbor_timers_Keepalive': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_timers_holdtime': 'INTEGER_VALUE:0-3600', + 'bgp_neighbor_ttl_hops': 'INTEGER_VALUE:1-254', + 'bgp_neighbor_update_options': 'TEXT_OPTIONS:ethernet,loopback,\ + vlan', + 'bgp_neighbor_update_ethernet': 'TEXT:', + 'bgp_neighbor_update_loopback': 'INTEGER_VALUE:0-7', + 'bgp_neighbor_update_vlan': 'INTEGER_VALUE:1-4094', + 'bgp_neighbor_weight': 'INTEGER_VALUE:0-65535', + 'ethernet_interface_value': 'INTEGER_VALUE:1-32', + 'ethernet_interface_range': 'INTEGER_VALUE_RANGE:1-32', + 'ethernet_interface_string': 'TEXT:', + 'loopback_interface_value': 'INTEGER_VALUE:0-7', + 'mgmt_interface_value': 'INTEGER_VALUE:0-0', + 'vlan_interface_value': 'INTEGER_VALUE:1-4094', + 'portchannel_interface_value': 'INTEGER_VALUE:1-4096', + 'portchannel_interface_range': 'INTEGER_VALUE_RANGE:1-4096', + 'portchannel_interface_string': 'TEXT:', + 'aggregation_group_no': 'INTEGER_VALUE:1-4096', + 'aggregation_group_mode': 'TEXT_OPTIONS:active,on,passive', + 'bfd_options': 'TEXT_OPTIONS:authentication,echo,interval,ipv4,\ + ipv6,neighbor', + 'bfd_interval': 'INTEGER_VALUE:50-999', + 'bfd_minrx': 'INTEGER_VALUE:50-999', + 'bfd_ multiplier': 'INTEGER_VALUE:3-50', + 'bfd_ipv4_options': 'TEXT_OPTIONS:authentication,echo,interval', + 'bfd_auth_options': 'TEXT_OPTIONS:keyed-md5,keyed-sha1,\ + meticulous-keyed-md5,meticulous-keyed-sha1,simple', + 'bfd_key_options': 'TEXT_OPTIONS:key-chain,key-id', + 'bfd_key_chain': 'TEXT:', + 'bfd_key_id': 'INTEGER_VALUE:0-255', + 'bfd_key_name': 'TEXT:', + 'bfd_neighbor_ip': 'TEXT:', + 'bfd_neighbor_options': 'TEXT_OPTIONS:admin-down,multihop,\ + non-persistent', + 'bfd_access_vlan': 'INTEGER_VALUE:1-3999', + 'bfd_bridgeport_mode': 'TEXT_OPTIONS:access,dot1q-tunnel,trunk', + 'trunk_options': 'TEXT_OPTIONS:allowed,native', + 'trunk_vlanid': 'INTEGER_VALUE:1-3999', + 'portCh_description': 'TEXT:', + 'duplex_option': 'TEXT_OPTIONS:auto,full,half', + 'flowcontrol_options': 'TEXT_OPTIONS:receive,send', + 'portchannel_ip_options': 'TEXT_OPTIONS:access-group,address,arp,\ + dhcp,ospf,port,port-unreachable,redirects,router,unreachables', + 'accessgroup_name': 'TEXT:', + 'portchannel_ipv4': 'IPV4Address:', + 'portchannel_ipv4_mask': 'TEXT:', + 'arp_ipaddress': 'IPV4Address:', + 'arp_macaddress': 'TEXT:', + 'arp_timeout_value': 'INTEGER_VALUE:60-28800', + 'relay_ipaddress': 'IPV4Address:', + 'ip_ospf_options': 'TEXT_OPTIONS:authentication,\ + authentication-key,bfd,cost,database-filter,dead-interval,\ + hello-interval,message-digest-key,mtu,mtu-ignore,network,\ + passive-interface,priority,retransmit-interval,shutdown,\ + transmit-delay', + 'ospf_id_decimal_value': 'NO_VALIDATION:1-4294967295', + 'ospf_id_ipaddres_value': 'IPV4Address:', + 'lacp_options': 'TEXT_OPTIONS:port-priority,suspend-individual,\ + timeout', + 'port_priority': 'INTEGER_VALUE:1-65535', + 'lldp_options': 'TEXT_OPTIONS:receive,tlv-select,transmit,\ + trap-notification', + 'lldp_tlv_options': 'TEXT_OPTIONS:link-aggregation,\ + mac-phy-status,management-address,max-frame-size,\ + port-description,port-protocol-vlan,port-vlan,power-mdi,\ + protocol-identity,system-capabilities,system-description,\ + system-name,vid-management,vlan-name', + 'load_interval_delay': 'INTEGER_VALUE:30-300', + 'load_interval_counter': 'INTEGER_VALUE:1-3', + 'mac_accessgroup_name': 'TEXT:', + 'mac_address': 'TEXT:', + 'microburst_threshold': 'NO_VALIDATION:1-4294967295', + 'mtu_value': 'INTEGER_VALUE:64-9216', + 'service_instance': 'NO_VALIDATION:1-4294967295', + 'service_policy_options': 'TEXT_OPTIONS:copp-system-policy,\ + input,output,type', + 'service_policy_name': 'TEXT:', + 'spanning_tree_options': 'TEXT_OPTIONS:bpdufilter,bpduguard,\ + cost,disable,enable,guard,link-type,mst,port,port-priority,vlan', + 'spanning_tree_cost': 'NO_VALIDATION:1-200000000', + 'spanning_tree_interfacerange': 'INTEGER_VALUE_RANGE:1-3999', + 'spanning_tree_portpriority': 'TEXT_OPTIONS:0,32,64,96,128,160,\ + 192,224', + 'portchannel_ipv6_neighbor_mac': 'TEXT:', + 'portchannel_ipv6_neighbor_address': 'IPV6Address:', + 'portchannel_ipv6_linklocal': 'IPV6Address:', + 'portchannel_ipv6_dhcp_vlan': 'INTEGER_VALUE:1-4094', + 'portchannel_ipv6_dhcp_ethernet': 'TEXT:', + 'portchannel_ipv6_dhcp': 'IPV6Address:', + 'portchannel_ipv6_address': 'IPV6Address:', + 'portchannel_ipv6_options': 'TEXT_OPTIONS:address,dhcp,\ + link-local,nd,neighbor', + 'interface_speed': 'TEXT_OPTIONS:1000,10000,40000,50000,auto', + 'stormcontrol_options': 'TEXT_OPTIONS:broadcast,multicast,\ + unicast', + 'stormcontrol_level': 'FLOAT:', + 'portchannel_dot1q_tag': 'TEXT_OPTIONS:disable,enable,\ + egress-only', + 'vrrp_id': 'INTEGER_VALUE:1-255', + } diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_errorcodes.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_errorcodes.py new file mode 100644 index 00000000..1ced20a3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/cnos/cnos_errorcodes.py @@ -0,0 +1,259 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by +# Ansible still belong to the author of the module, and may assign their own +# license to the complete work. +# +# Copyright (C) 2017 Lenovo, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Contains error codes and methods +# Lenovo Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +errorDict = {0: 'Success', + 1: 'NOK', + 101: 'Device Response Timed out', + 102: 'Command Not supported - Use CLI command', + 103: 'Invalid Context', + 104: 'Command Value Not Supported as of Now. Use vlan Id only', + 105: 'Invalid interface Range', + 106: 'Please provide Enable Password.', + 108: '', + 109: '', + 110: 'Invalid protocol option', + 111: 'The Value is not Integer', + 112: 'The Value is not Float', + 113: 'Value is not in Range', + 114: 'Range value is not Integer', + 115: 'Value is not in Options', + 116: 'The Value is not Long', + 117: 'Range value is not Long', + 118: 'The Value cannot be empty', + 119: 'The Value is not String', + 120: 'The Value is not Matching', + 121: 'The Value is not IPV4 Address', + 122: 'The Value is not IPV6 Address', + 123: '', + 124: '', + 125: '', + 126: '', + 127: '', + 128: '', + 129: '', + 130: 'Invalid Access Map Name', + 131: 'Invalid Vlan Dot1q Tag', + 132: 'Invalid Vlan filter value', + 133: 'Invalid Vlan Range Value', + 134: 'Invalid Vlan Id', + 135: 'Invalid Vlan Access Map Action', + 136: 'Invalid Vlan Access Map Name', + 137: 'Invalid Access List', + 138: 'Invalid Vlan Access Map parameter', + 139: 'Invalid Vlan Name', + 140: 'Invalid Vlan Flood value,', + 141: 'Invalid Vlan State Value', + 142: 'Invalid Vlan Last Member query Interval', + 143: 'Invalid Querier IP address', + 144: 'Invalid Querier Time out', + 145: 'Invalid Query Interval', + 146: 'Invalid Vlan query max response time', + 147: 'Invalid vlan robustness variable', + 148: 'Invalid Vlan Startup Query count', + 149: 'Invalid vlan Startup Query Interval', + 150: 'Invalid Vlan snooping version', + 151: 'Invalid Vlan Ethernet Interface', + 152: 'Invalid Vlan Port Tag Number', + 153: 'Invalid mrouter option', + 154: 'Invalid Vlan Option', + 155: '', + 156: '', + 157: '', + 158: '', + 159: '', + 160: 'Invalid Vlag Auto Recovery Value', + 161: 'Invalid Vlag Config Consistency Value', + 162: 'Invalid Vlag Port Aggregation Number', + 163: 'Invalid Vlag Priority Value', + 164: 'Invalid Vlag Startup delay value', + 165: 'Invalid Vlag Trie Id', + 166: 'Invalid Vlag Instance Option', + 167: 'Invalid Vlag Keep Alive Attempts', + 168: 'Invalid Vlag Keep Alive Interval', + 169: 'Invalid Vlag Retry Interval', + 170: 'Invalid Vlag Peer Ip VRF Value', + 171: 'Invalid Vlag Health Check Options', + 172: 'Invalid Vlag Option', + 173: '', + 174: '', + 175: '', + 176: 'Invalid BGP As Number', + 177: 'Invalid Routing protocol option', + 178: 'Invalid BGP Address Family', + 179: 'Invalid AS Path options', + 180: 'Invalid BGP med options', + 181: 'Invalid Best Path option', + 182: 'Invalid BGP Local count number', + 183: 'Cluster Id has to either IP or AS Number', + 184: 'Invalid confederation identifier', + 185: 'Invalid Confederation Peer AS Value', + 186: 'Invalid Confederation Option', + 187: 'Invalid state path relay value', + 188: 'Invalid Maxas Limit AS Value', + 189: 'Invalid Neighbor IP Address or Neighbor AS Number', + 190: 'Invalid Router Id', + 191: 'Invalid BGP Keep Alive Interval', + 192: 'Invalid BGP Hold time', + 193: 'Invalid BGP Option', + 194: 'Invalid BGP Address Family option', + 195: 'Invalid BGP Address Family Redistribution option. ', + 196: 'Invalid BGP Address Family Route Map Name', + 197: 'Invalid Next Hop Critical Delay', + 198: 'Invalid Next Hop Non Critical Delay', + 199: 'Invalid Multipath Number Value', + 200: 'Invalid Aggegation Group Mode', + 201: 'Invalid Aggregation Group No', + 202: 'Invalid BFD Access Vlan', + 203: 'Invalid CFD Bridgeport Mode', + 204: 'Invalid Trunk Option', + 205: 'Invalid BFD Option', + 206: 'Invalid Portchannel description', + 207: 'Invalid Portchannel duplex option', + 208: 'Invalid Flow control option state', + 209: 'Invalid Flow control option', + 210: 'Invalid LACP Port priority', + 211: 'Invalid LACP Time out options', + 212: 'Invalid LACP Command options', + 213: 'Invalid LLDP TLV Option', + 214: 'Invalid LLDP Option', + 215: 'Invalid Load interval delay', + 216: 'Invalid Load interval Counter Number', + 217: 'Invalid Load Interval option', + 218: 'Invalid Mac Access Group Name', + 219: 'Invalid Mac Address', + 220: 'Invalid Microburst threshold value', + 221: 'Invalid MTU Value', + 222: 'Invalid Service instance value', + 223: 'Invalid service policy name', + 224: 'Invalid service policy options', + 225: 'Invalid Interface speed value', + 226: 'Invalid Storm control level value', + 227: 'Invalid Storm control option', + 228: 'Invalid Portchannel dot1q tag', + 229: 'Invalid VRRP Id Value', + 230: 'Invalid VRRP Options', + 231: 'Invalid portchannel source interface option', + 232: 'Invalid portchannel load balance options', + 233: 'Invalid Portchannel configuration attribute', + 234: 'Invalid BFD Interval Value', + 235: 'Invalid BFD minrx Value', + 236: 'Invalid BFD multiplier Value', + 237: 'Invalid Key Chain Value', + 238: 'Invalid key name option', + 239: 'Invalid key id value', + 240: 'Invalid Key Option', + 241: 'Invalid authentication option', + 242: 'Invalid destination Ip', + 243: 'Invalid source Ip', + 244: 'Invalid IP Option', + 245: 'Invalid Access group option', + 246: 'Invalid Access group name', + 247: 'Invalid ARP MacAddress Value', + 248: 'Invalid ARP timeout value', + 249: 'Invalid ARP Option', + 250: 'Invalid dhcp request option', + 251: 'Invalid dhcp Client option', + 252: 'Invalid relay Ip Address', + 253: 'Invalid dhcp Option', + 254: 'Invalid OSPF Option', + 255: 'Invalid OSPF Id IP Address Value', + 256: 'Invalid Ip Router Option', + 257: 'Invalid Spanning tree bpdufilter Options', + 258: 'Invalid Spanning tree bpduguard Options', + 259: 'Invalid Spanning tree cost Options', + 260: 'Invalid Spanning tree guard Options', + 261: 'Invalid Spanning tree link-type Options', + 262: 'Invalid Spanning tree link-type Options', + 263: 'Invalid Spanning tree options', + 264: 'Port-priority in increments of 32 is required', + 265: 'Invalid Spanning tree vlan options', + 266: 'Invalid IPv6 option', + 267: 'Invalid IPV6 neighbor IP Address', + 268: 'Invalid IPV6 neighbor mac address', + 269: 'Invalid IPV6 dhcp option', + 270: 'Invalid IPV6 relay address option', + 271: 'Invalid IPV6 Ethernet option', + 272: 'Invalid IPV6 Vlan option', + 273: 'Invalid IPV6 Link Local option', + 274: 'Invalid IPV6 dhcp option', + 275: 'Invalid IPV6 Address', + 276: 'Invalid IPV6 Address option', + 277: 'Invalid BFD neighbor options', + 278: 'Invalid Secondary option', + 289: 'Invalid PortChannel IPV4 address', + 290: 'Invalid Max Path Options', + 291: 'Invalid Distance Local Route value', + 292: 'Invalid Distance Internal AS value', + 293: 'Invalid Distance External AS value', + 294: 'Invalid BGP Reachability Half Life', + 295: 'Invalid BGP Dampening parameter', + 296: 'Invalid BGP Aggregate Prefix value', + 297: 'Invalid BGP Aggregate Prefix Option', + 298: 'Invalid BGP Address Family Route Map Name', + 299: 'Invalid BGP Net IP Mask Value', + 300: 'Invalid BGP Net IP Prefix Value', + 301: 'Invalid BGP Neighbor configuration option', + 302: 'Invalid BGP Neighbor Weight Value', + 303: 'Invalid Neigbor update source option', + 304: 'Invalid Ethernet slot/chassis number', + 305: 'Invalid Loopback Interface number', + 306: 'Invalid vlan id', + 307: 'Invalid Number of hops', + 308: 'Invalid Neighbor Keepalive interval', + 309: 'Invalid Neighbor timer hold time', + 310: 'Invalid neighbor password ', + 311: 'Invalid Max peer limit', + 312: 'Invalid Local AS Number', + 313: 'Invalid maximum hop count', + 314: 'Invalid neighbor description', + 315: 'Invalid Neighbor connect timer value', + 316: 'Invalid Neighbor address family option', + 317: 'Invalid neighbor address family option', + 318: 'Invalid route-map name', + 319: 'Invalid route-map', + 320: 'Invalid Name of a prefix list', + 321: 'Invalid Filter incoming option', + 322: 'Invalid AS path access-list name', + 323: 'Invalid Filter route option', + 324: 'Invalid route-map name', + 325: 'Invalid Number of occurrences of AS number', + 326: 'Invalid Prefix Limit'} + + +def getErrorString(errorCode): + retVal = errorDict[int(errorCode)] + return retVal +# EOM diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/edgeos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/edgeos.py new file mode 100644 index 00000000..5ca5c49f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeos/edgeos.py @@ -0,0 +1,136 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONFIGS = None + + +def get_connection(module): + if hasattr(module, '_edgeos_connection'): + return module._edgeos_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._edgeos_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._edgeos_connection + + +def get_capabilities(module): + if hasattr(module, '_edgeos_capabilities'): + return module._edgeos_capabilities + + capabilities = Connection(module._socket_path).get_capabilities() + module._edgeos_capabilities = json.loads(capabilities) + return module._edgeos_capabilities + + +def get_config(module): + global _DEVICE_CONFIGS + + if _DEVICE_CONFIGS is not None: + return _DEVICE_CONFIGS + else: + connection = get_connection(module) + out = connection.get_config() + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS = cfg + return cfg + + +def run_commands(module, commands, check_rc=True): + responses = list() + connection = get_connection(module) + + for cmd in to_list(commands): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + else: + command = cmd + prompt = None + answer = None + + try: + out = connection.get(command, prompt, answer) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + try: + out = to_text(out, errors='surrogate_or_strict') + except UnicodeError: + module.fail_json(msg=u'Failed to decode output from %s: %s' % + (cmd, to_text(out))) + + responses.append(out) + + return responses + + +def load_config(module, commands, commit=False, comment=None): + connection = get_connection(module) + + try: + out = connection.edit_config(commands) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + diff = None + if module._diff: + out = connection.get('compare') + out = to_text(out, errors='surrogate_or_strict') + + if not out.startswith('No changes'): + out = connection.get('show') + diff = to_text(out, errors='surrogate_or_strict').strip() + + if commit: + try: + out = connection.commit(comment) + except ConnectionError: + connection.discard_changes() + module.fail_json(msg='commit failed: %s' % out) + + if not commit: + connection.discard_changes() + else: + connection.get('exit') + + if diff: + return diff diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch.py new file mode 100644 index 00000000..55b05c47 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch.py @@ -0,0 +1,172 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +import re + +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec + +_DEVICE_CONFIGS = {} + + +def build_aggregate_spec(element_spec, required, *extra_spec): + aggregate_spec = deepcopy(element_spec) + for elt in required: + aggregate_spec[elt] = dict(required=True) + remove_default_spec(aggregate_spec) + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec) + ) + argument_spec.update(element_spec) + for elt in extra_spec: + argument_spec.update(elt) + return argument_spec + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + obj.append(d) + else: + obj.append(module.params) + + return obj + + +def get_connection(module): + if hasattr(module, '_edgeswitch_connection'): + return module._edgeswitch_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._edgeswitch_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._edgeswitch_connection + + +def get_capabilities(module): + if hasattr(module, '_edgeswitch_capabilities'): + return module._edgeswitch_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._edgeswitch_capabilities = json.loads(capabilities) + return module._edgeswitch_capabilities + + +def get_defaults_flag(module): + connection = get_connection(module) + try: + out = connection.get_defaults_flag() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return to_text(out, errors='surrogate_then_replace').strip() + + +def get_config(module, flags=None): + flag_str = ' '.join(to_list(flags)) + + try: + return _DEVICE_CONFIGS[flag_str] + except KeyError: + connection = get_connection(module) + try: + out = connection.get_config(flags=flags) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[flag_str] = cfg + return cfg + + +def get_interfaces_config(module): + config = get_config(module) + lines = config.split('\n') + interfaces = {} + interface = None + for line in lines: + if line == 'exit': + if interface: + interfaces[interface[0]] = interface + interface = None + elif interface: + interface.append(line) + else: + match = re.match(r'^interface (.*)$', line) + if match: + interface = list() + interface.append(line) + + return interfaces + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def load_config(module, commands): + connection = get_connection(module) + + try: + resp = connection.edit_config(commands) + return resp.get('response') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch_interface.py new file mode 100644 index 00000000..617ecb78 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/edgeswitch/edgeswitch_interface.py @@ -0,0 +1,94 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + + +class InterfaceConfiguration: + def __init__(self): + self.commands = [] + self.merged = False + + def has_same_commands(self, interface): + len1 = len(self.commands) + len2 = len(interface.commands) + return len1 == len2 and len1 == len(frozenset(self.commands).intersection(interface.commands)) + + +def merge_interfaces(interfaces): + """ to reduce commands generated by an edgeswitch module + we take interfaces one by one and we try to merge them with neighbors if everyone has same commands to run + """ + merged = {} + + for i, interface in interfaces.items(): + if interface.merged: + continue + interface.merged = True + + match = re.match(r'(\d+)\/(\d+)', i) + group = int(match.group(1)) + start = int(match.group(2)) + end = start + + while True: + try: + start = start - 1 + key = '{0}/{1}'.format(group, start) + neighbor = interfaces[key] + if not neighbor.merged and interface.has_same_commands(neighbor): + neighbor.merged = True + else: + break + except KeyError: + break + start = start + 1 + + while True: + try: + end = end + 1 + key = '{0}/{1}'.format(group, end) + neighbor = interfaces[key] + if not neighbor.merged and interface.has_same_commands(neighbor): + neighbor.merged = True + else: + break + except KeyError: + break + end = end - 1 + + if end == start: + key = '{0}/{1}'.format(group, start) + else: + key = '{0}/{1}-{2}/{3}'.format(group, start, group, end) + + merged[key] = interface + return merged diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/enos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/enos.py new file mode 100644 index 00000000..5fd5415b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/enos/enos.py @@ -0,0 +1,176 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by +# Ansible still belong to the author of the module, and may assign their own +# license to the complete work. +# +# Copyright (C) 2017 Lenovo. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Contains utility methods +# Lenovo Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection +from ansible.module_utils.connection import Connection, exec_command +from ansible.module_utils.connection import ConnectionError + +_DEVICE_CONFIGS = {} +_CONNECTION = None + +enos_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), + 'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True), + 'timeout': dict(type='int'), + 'context': dict(), + 'passwords': dict(no_log=True) +} + +enos_argument_spec = { + 'provider': dict(type='dict', options=enos_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network'), +} + +command_spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() +} + + +def get_provider_argspec(): + return enos_provider_spec + + +def check_args(module, warnings): + pass + + +def get_connection(module): + global _CONNECTION + if _CONNECTION: + return _CONNECTION + _CONNECTION = Connection(module._socket_path) + + context = None + try: + context = module.params['context'] + except KeyError: + context = None + + if context: + if context == 'system': + command = 'changeto system' + else: + command = 'changeto context %s' % context + _CONNECTION.get(command) + + return _CONNECTION + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + passwords = None + try: + passwords = module.params['passwords'] + except KeyError: + passwords = None + if passwords: + cmd = 'more system:running-config' + else: + cmd = 'show running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + conn = get_connection(module) + out = conn.get(cmd) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def to_commands(module, commands): + if not isinstance(commands, list): + raise AssertionError('argument must be of type ') + + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + module.warn('only show commands are supported when using check ' + 'mode, not executing `%s`' % item['command']) + + return commands + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + + commands = to_commands(module, to_list(commands)) + + responses = list() + + for cmd in commands: + out = connection.get(**cmd) + responses.append(to_text(out, errors='surrogate_then_replace')) + + return responses + + +def load_config(module, config): + try: + conn = get_connection(module) + conn.get('enable') + conn.edit_config(config) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_defaults_flag(module): + rc, out, err = exec_command(module, 'show running-config ?') + out = to_text(out, errors='surrogate_then_replace') + + commands = set() + for line in out.splitlines(): + if line: + commands.add(line.strip().split()[0]) + + if 'all' in commands: + return 'all' + else: + return 'full' diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/eric_eccli.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/eric_eccli.py new file mode 100644 index 00000000..19a526ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/eric_eccli/eric_eccli.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2019 Ericsson AB. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONFIGS = {} + + +def get_connection(module): + if hasattr(module, '_eric_eccli_connection'): + return module._eric_eccli_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._eric_eccli_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._eric_eccli_connection + + +def get_capabilities(module): + if hasattr(module, '_eric_eccli_capabilities'): + return module._eric_eccli_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._eric_eccli_capabilities = json.loads(capabilities) + return module._eric_eccli_capabilities + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/facts.py new file mode 100644 index 00000000..4ab2e934 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/facts/facts.py @@ -0,0 +1,23 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the exos facts module. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class FactsArgs(object): # pylint: disable=R0903 + """ The arg spec for the exos facts module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(type='list'), + } diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/l2_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..4b688b4b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/l2_interfaces/l2_interfaces.py @@ -0,0 +1,49 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the exos_l2_interfaces module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class L2_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the exos_l2_interfaces module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'access': {'options': {'vlan': {'type': 'int'}}, + 'type': 'dict'}, + 'name': {'required': True, 'type': 'str'}, + 'trunk': {'options': {'native_vlan': {'type': 'int'}, 'trunk_allowed_vlans': {'type': 'list'}}, + 'type': 'dict'}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'default': 'merged', 'type': 'str'} + } # pylint: disable=C0301 diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/lldp_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/lldp_global.py new file mode 100644 index 00000000..4106c534 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_global/lldp_global.py @@ -0,0 +1,57 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the exos_lldp_global module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Lldp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the exos_lldp_global module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'options': { + 'interval': {'default': 30, 'type': 'int'}, + 'tlv_select': { + 'options': { + 'management_address': {'type': 'bool'}, + 'port_description': {'type': 'bool'}, + 'system_capabilities': {'type': 'bool'}, + 'system_description': { + 'default': True, + 'type': 'bool'}, + 'system_name': {'default': True, 'type': 'bool'}}, + 'type': 'dict'}}, + 'type': 'dict'}, + 'state': { + 'choices': ['merged', 'replaced', 'deleted'], + 'default': 'merged', + 'type': 'str'}} # pylint: disable=C0301 diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/lldp_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..c2a981f9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,49 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the exos_lldp_interfaces module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Lldp_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the exos_lldp_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'enabled': {'type': 'bool'}, + 'name': {'required': True, 'type': 'str'}}, + 'type': 'list'}, + 'state': { + 'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} # pylint: disable=C0301 diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/vlans.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/vlans.py new file mode 100644 index 00000000..538a155a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/argspec/vlans/vlans.py @@ -0,0 +1,53 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the exos_vlans module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class VlansArgs(object): # pylint: disable=R0903 + """The arg spec for the exos_vlans module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'name': {'type': 'str'}, + 'state': { + 'choices': ['active', 'suspend'], + 'default': 'active', + 'type': 'str'}, + 'vlan_id': {'required': True, 'type': 'int'}}, + 'type': 'list'}, + 'state': { + 'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} # pylint: disable=C0301 diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/l2_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..57904b4e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/l2_interfaces/l2_interfaces.py @@ -0,0 +1,294 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos_l2_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class L2_interfaces(ConfigBase): + """ + The exos_l2_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'l2_interfaces', + ] + + L2_INTERFACE_NATIVE = { + "data": { + "openconfig-vlan:config": { + "interface-mode": "TRUNK", + "native-vlan": None, + "trunk-vlans": [] + } + }, + "method": "PATCH", + "path": None + } + + L2_INTERFACE_TRUNK = { + "data": { + "openconfig-vlan:config": { + "interface-mode": "TRUNK", + "trunk-vlans": [] + } + }, + "method": "PATCH", + "path": None + } + + L2_INTERFACE_ACCESS = { + "data": { + "openconfig-vlan:config": { + "interface-mode": "ACCESS", + "access-vlan": None + } + }, + "method": "PATCH", + "path": None + } + + L2_PATH = "/rest/restconf/data/openconfig-interfaces:interfaces/interface=" + + def __init__(self, module): + super(L2_interfaces, self).__init__(module) + + def get_l2_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, self.gather_network_resources) + l2_interfaces_facts = facts['ansible_network_resources'].get( + 'l2_interfaces') + if not l2_interfaces_facts: + return [] + return l2_interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + requests = list() + + existing_l2_interfaces_facts = self.get_l2_interfaces_facts() + requests.extend(self.set_config(existing_l2_interfaces_facts)) + if requests: + if not self._module.check_mode: + send_requests(self._module, requests=requests) + result['changed'] = True + result['requests'] = requests + + changed_l2_interfaces_facts = self.get_l2_interfaces_facts() + + result['before'] = existing_l2_interfaces_facts + if result['changed']: + result['after'] = changed_l2_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_l2_interfaces_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_l2_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + if state == 'overridden': + requests = self._state_overridden(want, have) + elif state == 'deleted': + requests = self._state_deleted(want, have) + elif state == 'merged': + requests = self._state_merged(want, have) + elif state == 'replaced': + requests = self._state_replaced(want, have) + return requests + + def _state_replaced(self, want, have): + """ The request generator when state is replaced + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + for w in want: + for h in have: + if w["name"] == h["name"]: + if dict_diff(w, h): + l2_request = self._update_patch_request(w, h) + l2_request["data"] = json.dumps(l2_request["data"]) + requests.append(l2_request) + break + + return requests + + def _state_overridden(self, want, have): + """ The request generator when state is overridden + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + have_copy = [] + for w in want: + for h in have: + if w["name"] == h["name"]: + if dict_diff(w, h): + l2_request = self._update_patch_request(w, h) + l2_request["data"] = json.dumps(l2_request["data"]) + requests.append(l2_request) + have_copy.append(h) + break + + for h in have: + if h not in have_copy: + l2_delete = self._update_delete_request(h) + if l2_delete["path"]: + l2_delete["data"] = json.dumps(l2_delete["data"]) + requests.append(l2_delete) + + return requests + + def _state_merged(self, want, have): + """ The request generator when state is merged + + :rtype: A list + :returns: the requests necessary to merge the provided into + the current configuration + """ + requests = [] + for w in want: + for h in have: + if w["name"] == h["name"]: + if dict_diff(h, w): + l2_request = self._update_patch_request(w, h) + l2_request["data"] = json.dumps(l2_request["data"]) + requests.append(l2_request) + break + + return requests + + def _state_deleted(self, want, have): + """ The request generator when state is deleted + + :rtype: A list + :returns: the requests necessary to remove the current configuration + of the provided objects + """ + requests = [] + if want: + for w in want: + for h in have: + if w["name"] == h["name"]: + l2_delete = self._update_delete_request(h) + if l2_delete["path"]: + l2_delete["data"] = json.dumps(l2_delete["data"]) + requests.append(l2_delete) + break + + else: + for h in have: + l2_delete = self._update_delete_request(h) + if l2_delete["path"]: + l2_delete["data"] = json.dumps(l2_delete["data"]) + requests.append(l2_delete) + + return requests + + def _update_patch_request(self, want, have): + + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, ['vlans', ]) + vlans_facts = facts['ansible_network_resources'].get('vlans') + + vlan_id = [] + + for vlan in vlans_facts: + vlan_id.append(vlan['vlan_id']) + + if want.get("access"): + if want["access"]["vlan"] in vlan_id: + l2_request = deepcopy(self.L2_INTERFACE_ACCESS) + l2_request["data"]["openconfig-vlan:config"]["access-vlan"] = want["access"]["vlan"] + l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" + else: + self._module.fail_json(msg="VLAN %s does not exist" % (want["access"]["vlan"])) + + elif want.get("trunk"): + if want["trunk"]["native_vlan"]: + if want["trunk"]["native_vlan"] in vlan_id: + l2_request = deepcopy(self.L2_INTERFACE_NATIVE) + l2_request["data"]["openconfig-vlan:config"]["native-vlan"] = want["trunk"]["native_vlan"] + l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" + for vlan in want["trunk"]["trunk_allowed_vlans"]: + if int(vlan) in vlan_id: + l2_request["data"]["openconfig-vlan:config"]["trunk-vlans"].append(int(vlan)) + else: + self._module.fail_json(msg="VLAN %s does not exist" % (vlan)) + else: + self._module.fail_json(msg="VLAN %s does not exist" % (want["trunk"]["native_vlan"])) + else: + l2_request = deepcopy(self.L2_INTERFACE_TRUNK) + l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" + for vlan in want["trunk"]["trunk_allowed_vlans"]: + if int(vlan) in vlan_id: + l2_request["data"]["openconfig-vlan:config"]["trunk-vlans"].append(int(vlan)) + else: + self._module.fail_json(msg="VLAN %s does not exist" % (vlan)) + return l2_request + + def _update_delete_request(self, have): + + l2_request = deepcopy(self.L2_INTERFACE_ACCESS) + + if have["access"] and have["access"]["vlan"] != 1 or have["trunk"] or not have["access"]: + l2_request["data"]["openconfig-vlan:config"]["access-vlan"] = 1 + l2_request["path"] = self.L2_PATH + str(have["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" + + return l2_request diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/lldp_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/lldp_global.py new file mode 100644 index 00000000..e5ee3eb6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_global/lldp_global.py @@ -0,0 +1,199 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos_lldp_global class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + +import json +from copy import deepcopy + + +class Lldp_global(ConfigBase): + """ + The exos_lldp_global class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'lldp_global', + ] + + LLDP_DEFAULT_INTERVAL = 30 + LLDP_DEFAULT_TLV = { + 'system_name': True, + 'system_description': True, + 'system_capabilities': False, + 'port_description': False, + 'management_address': False + } + LLDP_REQUEST = { + "data": {"openconfig-lldp:config": {}}, + "method": "PUT", + "path": "/rest/restconf/data/openconfig-lldp:lldp/config" + } + + def __init__(self, module): + super(Lldp_global, self).__init__(module) + + def get_lldp_global_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, self.gather_network_resources) + lldp_global_facts = facts['ansible_network_resources'].get('lldp_global') + if not lldp_global_facts: + return {} + return lldp_global_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + requests = list() + + existing_lldp_global_facts = self.get_lldp_global_facts() + requests.extend(self.set_config(existing_lldp_global_facts)) + if requests: + if not self._module.check_mode: + send_requests(self._module, requests) + result['changed'] = True + result['requests'] = requests + + changed_lldp_global_facts = self.get_lldp_global_facts() + + result['before'] = existing_lldp_global_facts + if result['changed']: + result['after'] = changed_lldp_global_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_lldp_global_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_lldp_global_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + + if state == 'deleted': + requests = self._state_deleted(want, have) + elif state == 'merged': + requests = self._state_merged(want, have) + elif state == 'replaced': + requests = self._state_replaced(want, have) + + return requests + + def _state_replaced(self, want, have): + """ The request generator when state is replaced + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + requests.extend(self._state_deleted(want, have)) + requests.extend(self._state_merged(want, have)) + return requests + + def _state_merged(self, want, have): + """ The request generator when state is merged + + :rtype: A list + :returns: the requests necessary to merge the provided into + the current configuration + """ + requests = [] + + request = deepcopy(self.LLDP_REQUEST) + self._update_lldp_config_body_if_diff(want, have, request) + + if len(request["data"]["openconfig-lldp:config"]): + request["data"] = json.dumps(request["data"]) + requests.append(request) + + return requests + + def _state_deleted(self, want, have): + """ The request generator when state is deleted + + :rtype: A list + :returns: the requests necessary to remove the current configuration + of the provided objects + """ + requests = [] + + request = deepcopy(self.LLDP_REQUEST) + if want: + self._update_lldp_config_body_if_diff(want, have, request) + else: + if self.LLDP_DEFAULT_INTERVAL != have['interval']: + request["data"]["openconfig-lldp:config"].update( + {"hello-timer": self.LLDP_DEFAULT_INTERVAL}) + + if have['tlv_select'] != self.LLDP_DEFAULT_TLV: + request["data"]["openconfig-lldp:config"].update( + {"suppress-tlv-advertisement": [key.upper() for key, value in self.LLDP_DEFAULT_TLV.items() if not value]}) + request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort() + if len(request["data"]["openconfig-lldp:config"]): + request["data"] = json.dumps(request["data"]) + requests.append(request) + + return requests + + def _update_lldp_config_body_if_diff(self, want, have, request): + if want.get('interval'): + if want['interval'] != have['interval']: + request["data"]["openconfig-lldp:config"].update( + {"hello-timer": want['interval']}) + if want.get('tlv_select'): + # Create list of TLVs to be suppressed which aren't already + want_suppress = [key.upper() for key, value in want["tlv_select"].items() if have["tlv_select"][key] != value and value is False] + if want_suppress: + # Add previously suppressed TLVs to the list as we are doing a PUT op + want_suppress.extend([key.upper() for key, value in have["tlv_select"].items() if value is False]) + request["data"]["openconfig-lldp:config"].update( + {"suppress-tlv-advertisement": want_suppress}) + request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/lldp_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..286a6d14 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,243 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos_lldp_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class Lldp_interfaces(ConfigBase): + """ + The exos_lldp_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'lldp_interfaces', + ] + + LLDP_INTERFACE = { + "data": { + "openconfig-lldp:config": { + "name": None, + "enabled": True + } + }, + "method": "PATCH", + "path": None + } + + LLDP_PATH = "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=" + + def __init__(self, module): + super(Lldp_interfaces, self).__init__(module) + + def get_lldp_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, self.gather_network_resources) + lldp_interfaces_facts = facts['ansible_network_resources'].get( + 'lldp_interfaces') + if not lldp_interfaces_facts: + return [] + return lldp_interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + requests = list() + + existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + requests.extend(self.set_config(existing_lldp_interfaces_facts)) + if requests: + if not self._module.check_mode: + send_requests(self._module, requests=requests) + result['changed'] = True + result['requests'] = requests + + changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + + result['before'] = existing_lldp_interfaces_facts + if result['changed']: + result['after'] = changed_lldp_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_lldp_interfaces_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_lldp_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + if state == 'overridden': + requests = self._state_overridden(want, have) + elif state == 'deleted': + requests = self._state_deleted(want, have) + elif state == 'merged': + requests = self._state_merged(want, have) + elif state == 'replaced': + requests = self._state_replaced(want, have) + return requests + + def _state_replaced(self, want, have): + """ The request generator when state is replaced + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + + for w in want: + for h in have: + if w['name'] == h['name']: + lldp_request = self._update_patch_request(w, h) + if lldp_request["path"]: + lldp_request["data"] = json.dumps(lldp_request["data"]) + requests.append(lldp_request) + + return requests + + def _state_overridden(self, want, have): + """ The request generator when state is overridden + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + have_copy = [] + for w in want: + for h in have: + if w['name'] == h['name']: + lldp_request = self._update_patch_request(w, h) + if lldp_request["path"]: + lldp_request["data"] = json.dumps(lldp_request["data"]) + requests.append(lldp_request) + have_copy.append(h) + + for h in have: + if h not in have_copy: + if not h['enabled']: + lldp_delete = self._update_delete_request(h) + if lldp_delete["path"]: + lldp_delete["data"] = json.dumps(lldp_delete["data"]) + requests.append(lldp_delete) + + return requests + + def _state_merged(self, want, have): + """ The request generator when state is merged + + :rtype: A list + :returns: the requests necessary to merge the provided into + the current configuration + """ + requests = [] + for w in want: + for h in have: + if w['name'] == h['name']: + lldp_request = self._update_patch_request(w, h) + if lldp_request["path"]: + lldp_request["data"] = json.dumps(lldp_request["data"]) + requests.append(lldp_request) + + return requests + + def _state_deleted(self, want, have): + """ The request generator when state is deleted + + :rtype: A list + :returns: the requests necessary to remove the current configuration + of the provided objects + """ + requests = [] + if want: + for w in want: + for h in have: + if w['name'] == h['name']: + if not h['enabled']: + lldp_delete = self._update_delete_request(h) + if lldp_delete["path"]: + lldp_delete["data"] = json.dumps( + lldp_delete["data"]) + requests.append(lldp_delete) + else: + for h in have: + if not h['enabled']: + lldp_delete = self._update_delete_request(h) + if lldp_delete["path"]: + lldp_delete["data"] = json.dumps(lldp_delete["data"]) + requests.append(lldp_delete) + + return requests + + def _update_patch_request(self, want, have): + + lldp_request = deepcopy(self.LLDP_INTERFACE) + + if have['enabled'] != want['enabled']: + lldp_request["data"]["openconfig-lldp:config"]["name"] = want[ + 'name'] + lldp_request["data"]["openconfig-lldp:config"]["enabled"] = want[ + 'enabled'] + lldp_request["path"] = self.LLDP_PATH + str( + want['name']) + "/config" + + return lldp_request + + def _update_delete_request(self, have): + + lldp_delete = deepcopy(self.LLDP_INTERFACE) + + lldp_delete["data"]["openconfig-lldp:config"]["name"] = have['name'] + lldp_delete["data"]["openconfig-lldp:config"]["enabled"] = True + lldp_delete["path"] = self.LLDP_PATH + str(have['name']) + "/config" + + return lldp_delete diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/vlans.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/vlans.py new file mode 100644 index 00000000..bdd81114 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/config/vlans/vlans.py @@ -0,0 +1,277 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos_vlans class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests +from ansible_collections.community.network.plugins.module_utils.network.exos.utils.utils import search_obj_in_list + + +class Vlans(ConfigBase): + """ + The exos_vlans class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'vlans', + ] + + VLAN_POST = { + "data": {"openconfig-vlan:vlans": []}, + "method": "POST", + "path": "/rest/restconf/data/openconfig-vlan:vlans/" + } + + VLAN_PATCH = { + "data": {"openconfig-vlan:vlans": {"vlan": []}}, + "method": "PATCH", + "path": "/rest/restconf/data/openconfig-vlan:vlans/" + } + + VLAN_DELETE = { + "method": "DELETE", + "path": None + } + + DEL_PATH = "/rest/restconf/data/openconfig-vlan:vlans/vlan=" + + REQUEST_BODY = { + "config": {"name": None, "status": "ACTIVE", "tpid": "oc-vlan-types:TPID_0x8100", "vlan-id": None} + } + + def __init__(self, module): + super(Vlans, self).__init__(module) + + def get_vlans_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, self.gather_network_resources) + vlans_facts = facts['ansible_network_resources'].get('vlans') + if not vlans_facts: + return [] + return vlans_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + requests = list() + + existing_vlans_facts = self.get_vlans_facts() + requests.extend(self.set_config(existing_vlans_facts)) + if requests: + if not self._module.check_mode: + send_requests(self._module, requests=requests) + result['changed'] = True + result['requests'] = requests + + changed_vlans_facts = self.get_vlans_facts() + + result['before'] = existing_vlans_facts + if result['changed']: + result['after'] = changed_vlans_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_vlans_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_vlans_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + if state == 'overridden': + requests = self._state_overridden(want, have) + elif state == 'deleted': + requests = self._state_deleted(want, have) + elif state == 'merged': + requests = self._state_merged(want, have) + elif state == 'replaced': + requests = self._state_replaced(want, have) + return requests + + def _state_replaced(self, want, have): + """ The request generator when state is replaced + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + request_patch = deepcopy(self.VLAN_PATCH) + + for w in want: + if w.get('vlan_id'): + h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') + if h: + if dict_diff(w, h): + request_body = self._update_patch_request(w) + request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body) + else: + request_post = self._update_post_request(w) + requests.append(request_post) + + if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]): + request_patch["data"] = json.dumps(request_patch["data"]) + requests.append(request_patch) + + return requests + + def _state_overridden(self, want, have): + """ The request generator when state is overridden + + :rtype: A list + :returns: the requests necessary to migrate the current configuration + to the desired configuration + """ + requests = [] + request_patch = deepcopy(self.VLAN_PATCH) + + have_copy = [] + for w in want: + if w.get('vlan_id'): + h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') + if h: + if dict_diff(w, h): + request_body = self._update_patch_request(w) + request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body) + have_copy.append(h) + else: + request_post = self._update_post_request(w) + requests.append(request_post) + + for h in have: + if h not in have_copy and h['vlan_id'] != 1: + request_delete = self._update_delete_request(h) + requests.append(request_delete) + + if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]): + request_patch["data"] = json.dumps(request_patch["data"]) + requests.append(request_patch) + + return requests + + def _state_merged(self, want, have): + """ The requests generator when state is merged + + :rtype: A list + :returns: the requests necessary to merge the provided into + the current configuration + """ + requests = [] + + request_patch = deepcopy(self.VLAN_PATCH) + + for w in want: + if w.get('vlan_id'): + h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') + if h: + if dict_diff(w, h): + request_body = self._update_patch_request(w) + request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body) + else: + request_post = self._update_post_request(w) + requests.append(request_post) + + if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]): + request_patch["data"] = json.dumps(request_patch["data"]) + requests.append(request_patch) + return requests + + def _state_deleted(self, want, have): + """ The requests generator when state is deleted + + :rtype: A list + :returns: the requests necessary to remove the current configuration + of the provided objects + """ + requests = [] + + if want: + for w in want: + if w.get('vlan_id'): + h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') + if h: + request_delete = self._update_delete_request(h) + requests.append(request_delete) + + else: + if not have: + return requests + for h in have: + if h['vlan_id'] == 1: + continue + else: + request_delete = self._update_delete_request(h) + requests.append(request_delete) + + return requests + + def _update_vlan_config_body(self, want, request): + request["config"]["name"] = want["name"] + request["config"]["status"] = "SUSPENDED" if want["state"] == "suspend" else want["state"].upper() + request["config"]["vlan-id"] = want["vlan_id"] + return request + + def _update_patch_request(self, want): + request_body = deepcopy(self.REQUEST_BODY) + request_body = self._update_vlan_config_body(want, request_body) + return request_body + + def _update_post_request(self, want): + request_post = deepcopy(self.VLAN_POST) + request_body = deepcopy(self.REQUEST_BODY) + request_body = self._update_vlan_config_body(want, request_body) + request_post["data"]["openconfig-vlan:vlans"].append(request_body) + request_post["data"] = json.dumps(request_post["data"]) + return request_post + + def _update_delete_request(self, have): + request_delete = deepcopy(self.VLAN_DELETE) + request_delete["path"] = self.DEL_PATH + str(have['vlan_id']) + return request_delete diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/exos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/exos.py new file mode 100644 index 00000000..856dd07d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/exos.py @@ -0,0 +1,222 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.common._collections_compat import Mapping +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONNECTION = None + + +class Cli: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._connection = None + + def get_capabilities(self): + """Returns platform info of the remove device + """ + connection = self._get_connection() + return json.loads(connection.get_capabilities()) + + def _get_connection(self): + if not self._connection: + self._connection = Connection(self._module._socket_path) + return self._connection + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + if self._device_configs == {}: + connection = self._get_connection() + try: + out = connection.get_config(flags=flags) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + self._device_configs = to_text(out, errors='surrogate_then_replace').strip() + return self._device_configs + + def run_commands(self, commands, check_rc=True): + """Runs list of commands on remote device and returns results + """ + connection = self._get_connection() + try: + response = connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return response + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + conn = self._get_connection() + try: + diff = conn.get_diff(candidate=candidate, running=running, diff_match=diff_match, + diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return diff + + +class HttpApi: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._connection_obj = None + + def get_capabilities(self): + """Returns platform info of the remove device + """ + try: + capabilities = self._connection.get_capabilities() + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + return json.loads(capabilities) + + @property + def _connection(self): + if not self._connection_obj: + self._connection_obj = Connection(self._module._socket_path) + return self._connection_obj + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + if self._device_configs == {}: + try: + out = self._connection.get_config(flags=flags) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + self._device_configs = to_text(out, errors='surrogate_then_replace').strip() + return self._device_configs + + def run_commands(self, commands, check_rc=True): + """Runs list of commands on remote device and returns results + """ + try: + response = self._connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return response + + def send_requests(self, requests): + """Send a list of http requests to remote device and return results + """ + if requests is None: + raise ValueError("'requests' value is required") + + responses = list() + for req in to_list(requests): + try: + response = self._connection.send_request(**req) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + responses.append(response) + return responses + + def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + try: + diff = self._connection.get_diff(candidate=candidate, running=running, diff_match=diff_match, + diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return diff + + +def get_capabilities(module): + conn = get_connection(module) + return conn.get_capabilities() + + +def get_connection(module): + global _DEVICE_CONNECTION + if not _DEVICE_CONNECTION: + connection_proxy = Connection(module._socket_path) + cap = json.loads(connection_proxy.get_capabilities()) + if cap['network_api'] == 'cliconf': + conn = Cli(module) + elif cap['network_api'] == 'exosapi': + conn = HttpApi(module) + else: + module.fail_json(msg='Invalid connection type %s' % cap['network_api']) + _DEVICE_CONNECTION = conn + return _DEVICE_CONNECTION + + +def get_config(module, flags=None): + flags = None if flags is None else flags + conn = get_connection(module) + return conn.get_config(flags) + + +def load_config(module, commands): + conn = get_connection(module) + return conn.run_commands(to_command(module, commands)) + + +def run_commands(module, commands, check_rc=True): + conn = get_connection(module) + return conn.run_commands(to_command(module, commands), check_rc=check_rc) + + +def to_command(module, commands): + transform = ComplexList(dict( + command=dict(key=True), + output=dict(default='text'), + prompt=dict(type='list'), + answer=dict(type='list'), + sendonly=dict(type='bool', default=False), + check_all=dict(type='bool', default=False), + ), module) + return transform(to_list(commands)) + + +def send_requests(module, requests): + conn = get_connection(module) + return conn.send_requests(to_request(module, requests)) + + +def to_request(module, requests): + transform = ComplexList(dict( + path=dict(key=True), + method=dict(), + data=dict(type='dict'), + ), module) + return transform(to_list(requests)) + + +def get_diff(module, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): + conn = get_connection(module) + return conn.get_diff(candidate=candidate, running=running, diff_match=diff_match, diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/facts.py new file mode 100644 index 00000000..cdfb8a4c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/facts.py @@ -0,0 +1,61 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The facts class for exos +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.facts.facts import FactsArgs +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import FactsBase +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.lldp_global.lldp_global import Lldp_globalFacts +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.vlans.vlans import VlansFacts +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.legacy.base import Default, Hardware, Interfaces, Config +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts + +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config) + +FACT_RESOURCE_SUBSETS = dict( + lldp_global=Lldp_globalFacts, + vlans=VlansFacts, + lldp_interfaces=Lldp_interfacesFacts, + l2_interfaces=L2_interfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for exos + """ + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """ Collect the facts for exos + + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + + return self.ansible_facts, self._warnings diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/l2_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..75b390f8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/l2_interfaces/l2_interfaces.py @@ -0,0 +1,92 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos l2_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class L2_interfacesFacts(object): + """ The exos l2_interfaces fact class + """ + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = L2_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for l2_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + if not data: + request = [{ + "path": "/rest/restconf/data/openconfig-interfaces:interfaces", + "method": "GET" + }] + data = send_requests(self._module, requests=request) + + objs = [] + if data: + for d in data[0]["openconfig-interfaces:interfaces"]["interface"]: + obj = self.render_config(self.generated_spec, d) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('l2_interfaces', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['l2_interfaces'] = params['config'] + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + if conf["config"]["type"] == "ethernetCsmacd": + conf_dict = conf["openconfig-if-ethernet:ethernet"]["openconfig-vlan:switched-vlan"]["config"] + config["name"] = conf["name"] + if conf_dict["interface-mode"] == "ACCESS": + config["access"]["vlan"] = conf_dict.get("access-vlan") + else: + if 'native-vlan' in conf_dict: + config["trunk"]["native_vlan"] = conf_dict.get("native-vlan") + config["trunk"]["trunk_allowed_vlans"] = conf_dict.get("trunk-vlans") + return utils.remove_empties(config) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/base.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/base.py new file mode 100644 index 00000000..5c0bf20b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/legacy/base.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The exos legacy fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import re +import json + +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.warnings = list() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS) + + def run(self, cmd): + return run_commands(self.module, cmd) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show switch' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'System Type:\s+(.*$)', data, re.M) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'SysName:\s+(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M) + if match: + return match.group(1) + # For stack, return serial number of the first switch in the stack. + match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M) + if match: + return match.group(1) + # Handle unique formatting for VM + match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) + + def parse_memtotal(self, data): + match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M) + if match: + return match.group(1) + # Handle unique formatting for VM + match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show configuration detail'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show switch', + {'command': 'show port config', 'output': 'json'}, + {'command': 'show port description', 'output': 'json'}, + {'command': 'show vlan detail', 'output': 'json'}, + {'command': 'show lldp neighbors', 'output': 'json'} + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + sysmac = self.parse_sysmac(data) + + data = self.responses[1] + if data: + self.facts['interfaces'] = self.populate_interfaces(data, sysmac) + + data = self.responses[2] + if data: + self.populate_interface_descriptions(data) + + data = self.responses[3] + if data: + self.populate_vlan_interfaces(data, sysmac) + + data = self.responses[4] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def parse_sysmac(self, data): + match = re.search(r'System MAC:\s+(\S+)', data, re.M) + if match: + return match.group(1) + + def populate_interfaces(self, interfaces, sysmac): + facts = dict() + for elem in interfaces: + intf = dict() + + if 'show_ports_config' not in elem: + continue + + key = str(elem['show_ports_config']['port']) + + if elem['show_ports_config']['linkState'] == 2: + # Link state is "not present", don't include + continue + + intf['type'] = 'Ethernet' + intf['macaddress'] = sysmac + intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg']) + intf['bandwidth'] = str(elem['show_ports_config']['speedActual']) + intf['duplex_configured'] = elem['show_ports_config']['duplexCfg'] + intf['duplex'] = elem['show_ports_config']['duplexActual'] + if elem['show_ports_config']['linkState'] == 1: + intf['lineprotocol'] = 'up' + else: + intf['lineprotocol'] = 'down' + if elem['show_ports_config']['portState'] == 1: + intf['operstatus'] = 'up' + else: + intf['operstatus'] = 'admin down' + + facts[key] = intf + return facts + + def populate_interface_descriptions(self, data): + for elem in data: + if 'show_ports_description' not in elem: + continue + key = str(elem['show_ports_description']['port']) + + if 'descriptionString' in elem['show_ports_description']: + desc = elem['show_ports_description']['descriptionString'] + self.facts['interfaces'][key]['description'] = desc + + def populate_vlan_interfaces(self, data, sysmac): + for elem in data: + if 'vlanProc' in elem: + key = elem['vlanProc']['name1'] + if key not in self.facts['interfaces']: + intf = dict() + intf['type'] = 'VLAN' + intf['macaddress'] = sysmac + self.facts['interfaces'][key] = intf + + if elem['vlanProc']['ipAddress'] != '0.0.0.0': + self.facts['interfaces'][key]['ipv4'] = list() + addr = elem['vlanProc']['ipAddress'] + subnet = elem['vlanProc']['maskForDisplay'] + ipv4 = dict(address=addr, subnet=subnet) + self.add_ip_address(addr, 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + if 'rtifIpv6Address' in elem: + key = elem['rtifIpv6Address']['rtifName'] + if key not in self.facts['interfaces']: + intf = dict() + intf['type'] = 'VLAN' + intf['macaddress'] = sysmac + self.facts['interfaces'][key] = intf + self.facts['interfaces'][key]['ipv6'] = list() + addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/') + ipv6 = dict(address=addr, subnet=subnet) + self.add_ip_address(addr, 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + if address not in self.facts['all_ipv4_addresses']: + self.facts['all_ipv4_addresses'].append(address) + else: + if address not in self.facts['all_ipv6_addresses']: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, data): + facts = dict() + for elem in data: + if 'lldpPortNbrInfoShort' not in elem: + continue + intf = str(elem['lldpPortNbrInfoShort']['port']) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName'] + fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID']) + facts[intf].append(fact) + return facts diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/lldp_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/lldp_global.py new file mode 100644 index 00000000..84bbf2d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_global/lldp_global.py @@ -0,0 +1,97 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos lldp_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_global.lldp_global \ + import Lldp_globalArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class Lldp_globalFacts(object): + """ The exos lldp_global fact class + """ + + TLV_SELECT_OPTIONS = [ + "SYSTEM_NAME", + "SYSTEM_DESCRIPTION", + "SYSTEM_CAPABILITIES", + "MANAGEMENT_ADDRESS", + "PORT_DESCRIPTION"] + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Lldp_globalArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for lldp_global + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + request = { + "path": "/rest/restconf/data/openconfig-lldp:lldp/config/", + "method": "GET", + } + data = send_requests(self._module, request) + + obj = {} + if data: + lldp_obj = self.render_config(self.generated_spec, data[0]) + if lldp_obj: + obj = lldp_obj + + ansible_facts['ansible_network_resources'].pop('lldp_global', None) + facts = {} + + params = utils.validate_config(self.argument_spec, {'config': obj}) + facts['lldp_global'] = params['config'] + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + config['interval'] = conf["openconfig-lldp:config"]["hello-timer"] + + for item in self.TLV_SELECT_OPTIONS: + config["tlv_select"][item.lower()] = ( + False if (item in conf["openconfig-lldp:config"]["suppress-tlv-advertisement"]) + else True) + + return utils.remove_empties(config) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/lldp_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..445ac13a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,88 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos lldp_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class Lldp_interfacesFacts(object): + """ The exos lldp_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Lldp_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for lldp_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + if not data: + request = [{ + "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4", + "method": "GET" + }] + data = send_requests(self._module, requests=request) + + objs = [] + if data: + for d in data[0]["openconfig-lldp:interfaces"]["interface"]: + obj = self.render_config(self.generated_spec, d["config"]) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('lldp_interfaces', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['lldp_interfaces'] = params['config'] + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + config["name"] = conf["name"] + config["enabled"] = bool(conf["enabled"]) + + return utils.remove_empties(config) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/vlans.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/vlans.py new file mode 100644 index 00000000..9b637cc9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/facts/vlans/vlans.py @@ -0,0 +1,89 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The exos vlans fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.vlans.vlans import VlansArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import send_requests + + +class VlansFacts(object): + """ The exos vlans fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = VlansArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for vlans + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + if not data: + request = [{ + "path": "/rest/restconf/data/openconfig-vlan:vlans?depth=5", + "method": "GET" + }] + data = send_requests(self._module, requests=request) + + objs = [] + if data: + for d in data[0]["openconfig-vlan:vlans"]["vlan"]: + obj = self.render_config(self.generated_spec, d["config"]) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('vlans', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['vlans'] = params['config'] + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + config["name"] = conf["name"] + config["state"] = "suspend" if conf["status"] == "SUSPENDED" else conf["status"].lower() + config["vlan_id"] = conf["vlan-id"] + + return utils.remove_empties(config) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/utils.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/utils.py new file mode 100644 index 00000000..d40f8171 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/exos/utils/utils.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def search_obj_in_list(item, lst, key): + for o in lst: + if o[key] == item: + return o + return None diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/common.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/common.py new file mode 100644 index 00000000..50cd95cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/common.py @@ -0,0 +1,291 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2017 Fortinet, Inc +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +# BEGIN STATIC DATA AND MESSAGES +class FAZMethods: + GET = "get" + SET = "set" + EXEC = "exec" + EXECUTE = "exec" + UPDATE = "update" + ADD = "add" + DELETE = "delete" + REPLACE = "replace" + CLONE = "clone" + MOVE = "move" + + +BASE_HEADERS = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' +} + + +# FAZ RETURN CODES +FAZ_RC = { + "faz_return_codes": { + 0: { + "msg": "OK", + "changed": True, + "stop_on_success": True + }, + -100000: { + "msg": "Module returned without actually running anything. " + "Check parameters, and please contact the authors if needed.", + "failed": True + }, + -2: { + "msg": "Object already exists.", + "skipped": True, + "changed": False, + "good_codes": [0, -2] + }, + -6: { + "msg": "Invalid Url. Sometimes this can happen because the path is mapped to a hostname or object that" + " doesn't exist. Double check your input object parameters." + }, + -3: { + "msg": "Object doesn't exist.", + "skipped": True, + "changed": False, + "good_codes": [0, -3] + }, + -10131: { + "msg": "Object dependency failed. Do all named objects in parameters exist?", + "changed": False, + "skipped": True + }, + -9998: { + "msg": "Duplicate object. Try using mode='set', if using add. STOPPING. Use 'ignore_errors=yes' in playbook" + "to override and mark successful.", + }, + -20042: { + "msg": "Device Unreachable.", + "skipped": True + }, + -10033: { + "msg": "Duplicate object. Try using mode='set', if using add.", + "changed": False, + "skipped": True + }, + -10000: { + "msg": "Duplicate object. Try using mode='set', if using add.", + "changed": False, + "skipped": True + }, + -20010: { + "msg": "Device already added to FortiAnalyzer. Serial number already in use.", + "good_codes": [0, -20010], + "changed": False, + "stop_on_failure": False + }, + -20002: { + "msg": "Invalid Argument -- Does this Device exist on FortiAnalyzer?", + "changed": False, + "skipped": True, + } + } +} + +DEFAULT_RESULT_OBJ = (-100000, {"msg": "Nothing Happened. Check that handle_response is being called!"}) +FAIL_SOCKET_MSG = {"msg": "Socket Path Empty! The persistent connection manager is messed up. " + "Try again in a few moments."} + + +# BEGIN ERROR EXCEPTIONS +class FAZBaseException(Exception): + """Wrapper to catch the unexpected""" + + def __init__(self, msg=None, *args, **kwargs): + if msg is None: + msg = "An exception occurred within the fortianalyzer.py httpapi connection plugin." + super(FAZBaseException, self).__init__(msg, *args) + +# END ERROR CLASSES + + +# BEGIN CLASSES +class FAZCommon(object): + + @staticmethod + def format_request(method, url, *args, **kwargs): + """ + Formats the payload from the module, into a payload the API handler can use. + + :param url: Connection URL to access + :type url: string + :param method: The preferred API Request method (GET, ADD, POST, etc....) + :type method: basestring + :param kwargs: The payload dictionary from the module to be converted. + + :return: Properly formatted dictionary payload for API Request via Connection Plugin. + :rtype: dict + """ + + params = [{"url": url}] + if args: + for arg in args: + params[0].update(arg) + if kwargs: + keylist = list(kwargs) + for k in keylist: + kwargs[k.replace("__", "-")] = kwargs.pop(k) + if method == "get" or method == "clone": + params[0].update(kwargs) + else: + if kwargs.get("data", False): + params[0]["data"] = kwargs["data"] + else: + params[0]["data"] = kwargs + return params + + @staticmethod + def split_comma_strings_into_lists(obj): + """ + Splits a CSV String into a list. Also takes a dictionary, and converts any CSV strings in any key, to a list. + + :param obj: object in CSV format to be parsed. + :type obj: str or dict + + :return: A list containing the CSV items. + :rtype: list + """ + return_obj = () + if isinstance(obj, dict): + if len(obj) > 0: + for k, v in obj.items(): + if isinstance(v, str): + new_list = list() + if "," in v: + new_items = v.split(",") + for item in new_items: + new_list.append(item.strip()) + obj[k] = new_list + return_obj = obj + elif isinstance(obj, str): + return_obj = obj.replace(" ", "").split(",") + + return return_obj + + @staticmethod + def cidr_to_netmask(cidr): + """ + Converts a CIDR Network string to full blown IP/Subnet format in decimal format. + Decided not use IP Address module to keep includes to a minimum. + + :param cidr: String object in CIDR format to be processed + :type cidr: str + + :return: A string object that looks like this "x.x.x.x/y.y.y.y" + :rtype: str + """ + if isinstance(cidr, str): + cidr = int(cidr) + mask = (0xffffffff >> (32 - cidr)) << (32 - cidr) + return (str((0xff000000 & mask) >> 24) + '.' + + str((0x00ff0000 & mask) >> 16) + '.' + + str((0x0000ff00 & mask) >> 8) + '.' + + str((0x000000ff & mask))) + + @staticmethod + def paramgram_child_list_override(list_overrides, paramgram, module): + """ + If a list of items was provided to a "parent" paramgram attribute, the paramgram needs to be rewritten. + The child keys of the desired attribute need to be deleted, and then that "parent" keys' contents is replaced + With the list of items that was provided. + + :param list_overrides: Contains the response from the FortiAnalyzer. + :type list_overrides: list + :param paramgram: Contains the paramgram passed to the modules' local modify function. + :type paramgram: dict + :param module: Contains the Ansible Module Object being used by the module. + :type module: classObject + + :return: A new "paramgram" refactored to allow for multiple entries being added. + :rtype: dict + """ + if len(list_overrides) > 0: + for list_variable in list_overrides: + try: + list_variable = list_variable.replace("-", "_") + override_data = module.params[list_variable] + if override_data: + del paramgram[list_variable] + paramgram[list_variable] = override_data + except BaseException as e: + raise FAZBaseException("Error occurred merging custom lists for the paramgram parent: " + str(e)) + return paramgram + + @staticmethod + def syslog(module, msg): + try: + module.log(msg=msg) + except BaseException: + pass + + +# RECURSIVE FUNCTIONS START +def prepare_dict(obj): + """ + Removes any keys from a dictionary that are only specific to our use in the module. FortiAnalyzer will reject + requests with these empty/None keys in it. + + :param obj: Dictionary object to be processed. + :type obj: dict + + :return: Processed dictionary. + :rtype: dict + """ + + list_of_elems = ["mode", "adom", "host", "username", "password"] + + if isinstance(obj, dict): + obj = dict((key, prepare_dict(value)) for (key, value) in obj.items() if key not in list_of_elems) + return obj + + +def scrub_dict(obj): + """ + Removes any keys from a dictionary that are EMPTY -- this includes parent keys. FortiAnalyzer doesn't + like empty keys in dictionaries + + :param obj: Dictionary object to be processed. + :type obj: dict + + :return: Processed dictionary. + :rtype: dict + """ + + if isinstance(obj, dict): + return dict((k, scrub_dict(v)) for k, v in obj.items() if v and scrub_dict(v)) + else: + return obj diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/fortianalyzer.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/fortianalyzer.py new file mode 100644 index 00000000..b1a29655 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/fortianalyzer/fortianalyzer.py @@ -0,0 +1,476 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2017 Fortinet, Inc +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZ_RC +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZBaseException +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZCommon +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import scrub_dict +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZMethods + + +# ACTIVE BUG WITH OUR DEBUG IMPORT CALL - BECAUSE IT'S UNDER MODULE_UTILITIES +# WHEN module_common.recursive_finder() runs under the module loader, it looks for this namespace debug import +# and because it's not there, it always fails, regardless of it being under a try/catch here. +# we're going to move it to a different namespace. +# # check for debug lib +# try: +# from ansible.module_utils.network.fortianalyzer.fortianalyzer_debug import debug_dump +# HAS_FAZ_DEBUG = True +# except: +# HAS_FAZ_DEBUG = False + + +# BEGIN HANDLER CLASSES +class FortiAnalyzerHandler(object): + def __init__(self, conn, module): + self._conn = conn + self._module = module + self._tools = FAZCommon + self._uses_workspace = None + self._uses_adoms = None + self._locked_adom_list = list() + self._lock_info = None + + self.workspace_check() + if self._uses_workspace: + self.get_lock_info(adom=self._module.paramgram["adom"]) + + def process_request(self, url, datagram, method): + """ + Formats and Runs the API Request via Connection Plugin. Streamlined for use from Modules. + + :param url: Connection URL to access + :type url: string + :param datagram: The prepared payload for the API Request in dictionary format + :type datagram: dict + :param method: The preferred API Request method (GET, ADD, POST, etc....) + :type method: basestring + + :return: Dictionary containing results of the API Request via Connection Plugin. + :rtype: dict + """ + try: + adom = self._module.paramgram["adom"] + if self.uses_workspace and adom not in self._locked_adom_list and method != FAZMethods.GET: + self.lock_adom(adom=adom) + except BaseException as err: + raise FAZBaseException(err) + + data = self._tools.format_request(method, url, **datagram) + response = self._conn.send_request(method, data) + + try: + adom = self._module.paramgram["adom"] + if self.uses_workspace and adom in self._locked_adom_list \ + and response[0] == 0 and method != FAZMethods.GET: + self.commit_changes(adom=adom) + except BaseException as err: + raise FAZBaseException(err) + + # if HAS_FAZ_DEBUG: + # try: + # debug_dump(response, datagram, self._module.paramgram, url, method) + # except BaseException: + # pass + + return response + + def workspace_check(self): + """ + Checks FortiAnalyzer for the use of Workspace mode. + """ + url = "/cli/global/system/global" + data = {"fields": ["workspace-mode", "adom-status"]} + resp_obj = self.process_request(url, data, FAZMethods.GET) + try: + if resp_obj[1]["workspace-mode"] in ["workflow", "normal"]: + self.uses_workspace = True + elif resp_obj[1]["workspace-mode"] == "disabled": + self.uses_workspace = False + except KeyError: + self.uses_workspace = False + except BaseException as err: + raise FAZBaseException(msg="Couldn't determine workspace-mode in the plugin. Error: " + str(err)) + try: + if resp_obj[1]["adom-status"] in [1, "enable"]: + self.uses_adoms = True + else: + self.uses_adoms = False + except KeyError: + self.uses_adoms = False + except BaseException as err: + raise FAZBaseException(msg="Couldn't determine adom-status in the plugin. Error: " + str(err)) + + def run_unlock(self): + """ + Checks for ADOM status, if locked, it will unlock + """ + for adom_locked in self._locked_adom_list: + self.unlock_adom(adom_locked) + + def lock_adom(self, adom=None): + """ + Locks an ADOM for changes + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/lock" + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lock/" + else: + url = "/dvmdb/adom/{adom}/workspace/lock/".format(adom=adom) + datagram = {} + data = self._tools.format_request(FAZMethods.EXEC, url, **datagram) + resp_obj = self._conn.send_request(FAZMethods.EXEC, data) + code = resp_obj[0] + if code == 0 and resp_obj[1]["status"]["message"].lower() == "ok": + self.add_adom_to_lock_list(adom) + else: + lockinfo = self.get_lock_info(adom=adom) + self._module.fail_json(msg=("An error occurred trying to lock the adom. Error: " + + str(resp_obj) + ", LOCK INFO: " + str(lockinfo))) + return resp_obj + + def unlock_adom(self, adom=None): + """ + Unlocks an ADOM after changes + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/unlock" + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/unlock/" + else: + url = "/dvmdb/adom/{adom}/workspace/unlock/".format(adom=adom) + datagram = {} + data = self._tools.format_request(FAZMethods.EXEC, url, **datagram) + resp_obj = self._conn.send_request(FAZMethods.EXEC, data) + code = resp_obj[0] + if code == 0 and resp_obj[1]["status"]["message"].lower() == "ok": + self.remove_adom_from_lock_list(adom) + else: + self._module.fail_json(msg=("An error occurred trying to unlock the adom. Error: " + str(resp_obj))) + return resp_obj + + def get_lock_info(self, adom=None): + """ + Gets ADOM lock info so it can be displayed with the error messages. Or if determined to be locked by ansible + for some reason, then unlock it. + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/lockinfo" + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/lockinfo/" + else: + url = "/dvmdb/adom/{adom}/workspace/lockinfo/".format(adom=adom) + datagram = {} + data = self._tools.format_request(FAZMethods.GET, url, **datagram) + resp_obj = self._conn.send_request(FAZMethods.GET, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to get the ADOM Lock Info. Error: " + str(resp_obj))) + elif code == 0: + self._lock_info = resp_obj[1] + return resp_obj + + def commit_changes(self, adom=None, aux=False): + """ + Commits changes to an ADOM + """ + if not adom or adom == "root": + url = "/dvmdb/adom/root/workspace/commit" + else: + if aux: + url = "/pm/config/adom/{adom}/workspace/commit".format(adom=adom) + else: + if adom.lower() == "global": + url = "/dvmdb/global/workspace/commit/" + else: + url = "/dvmdb/adom/{adom}/workspace/commit".format(adom=adom) + datagram = {} + data = self._tools.format_request(FAZMethods.EXEC, url, **datagram) + resp_obj = self._conn.send_request(FAZMethods.EXEC, data) + code = resp_obj[0] + if code != 0: + self._module.fail_json(msg=("An error occurred trying to commit changes to the adom. Error: " + + str(resp_obj))) + + def govern_response(self, module, results, msg=None, good_codes=None, + stop_on_fail=None, stop_on_success=None, skipped=None, + changed=None, unreachable=None, failed=None, success=None, changed_if_success=None, + ansible_facts=None): + """ + This function will attempt to apply default values to canned responses from FortiAnalyzer we know of. + This saves time, and turns the response in the module into a "one-liner", while still giving us... + the flexibility to directly use return_response in modules if we have too. This function saves repeated code. + + :param module: The Ansible Module CLASS object, used to run fail/exit json + :type module: object + :param msg: An overridable custom message from the module that called this. + :type msg: string + :param results: A dictionary object containing an API call results + :type results: dict + :param good_codes: A list of exit codes considered successful from FortiAnalyzer + :type good_codes: list + :param stop_on_fail: If true, stops playbook run when return code is NOT IN good codes (default: true) + :type stop_on_fail: boolean + :param stop_on_success: If true, stops playbook run when return code is IN good codes (default: false) + :type stop_on_success: boolean + :param changed: If True, tells Ansible that object was changed (default: false) + :type skipped: boolean + :param skipped: If True, tells Ansible that object was skipped (default: false) + :type skipped: boolean + :param unreachable: If True, tells Ansible that object was unreachable (default: false) + :type unreachable: boolean + :param failed: If True, tells Ansible that execution was a failure. Overrides good_codes. (default: false) + :type unreachable: boolean + :param success: If True, tells Ansible that execution was a success. Overrides good_codes. (default: false) + :type unreachable: boolean + :param changed_if_success: If True, defaults to changed if successful if you specify or not" + :type changed_if_success: boolean + :param ansible_facts: A prepared dictionary of ansible facts from the execution. + :type ansible_facts: dict + """ + if module is None and results is None: + raise FAZBaseException("govern_response() was called without a module and/or results tuple! Fix!") + # Get the Return code from results + try: + rc = results[0] + except BaseException: + raise FAZBaseException("govern_response() was called without the return code at results[0]") + + # init a few items + rc_data = None + + # Get the default values for the said return code. + try: + rc_codes = FAZ_RC.get('faz_return_codes') + rc_data = rc_codes.get(rc) + except BaseException: + pass + + if not rc_data: + rc_data = {} + # ONLY add to overrides if not none -- This is very important that the keys aren't added at this stage + # if they are empty. And there aren't that many, so let's just do a few if then statements. + if good_codes is not None: + rc_data["good_codes"] = good_codes + if stop_on_fail is not None: + rc_data["stop_on_fail"] = stop_on_fail + if stop_on_success is not None: + rc_data["stop_on_success"] = stop_on_success + if skipped is not None: + rc_data["skipped"] = skipped + if changed is not None: + rc_data["changed"] = changed + if unreachable is not None: + rc_data["unreachable"] = unreachable + if failed is not None: + rc_data["failed"] = failed + if success is not None: + rc_data["success"] = success + if changed_if_success is not None: + rc_data["changed_if_success"] = changed_if_success + if results is not None: + rc_data["results"] = results + if msg is not None: + rc_data["msg"] = msg + if ansible_facts is None: + rc_data["ansible_facts"] = {} + else: + rc_data["ansible_facts"] = ansible_facts + + return self.return_response(module=module, + results=results, + msg=rc_data.get("msg", "NULL"), + good_codes=rc_data.get("good_codes", (0,)), + stop_on_fail=rc_data.get("stop_on_fail", True), + stop_on_success=rc_data.get("stop_on_success", False), + skipped=rc_data.get("skipped", False), + changed=rc_data.get("changed", False), + changed_if_success=rc_data.get("changed_if_success", False), + unreachable=rc_data.get("unreachable", False), + failed=rc_data.get("failed", False), + success=rc_data.get("success", False), + ansible_facts=rc_data.get("ansible_facts", dict())) + + def return_response(self, module, results, msg="NULL", good_codes=(0,), + stop_on_fail=True, stop_on_success=False, skipped=False, + changed=False, unreachable=False, failed=False, success=False, changed_if_success=True, + ansible_facts=()): + """ + This function controls the logout and error reporting after an method or function runs. The exit_json for + ansible comes from logic within this function. If this function returns just the msg, it means to continue + execution on the playbook. It is called from the ansible module, or from the self.govern_response function. + + :param module: The Ansible Module CLASS object, used to run fail/exit json + :type module: object + :param msg: An overridable custom message from the module that called this. + :type msg: string + :param results: A dictionary object containing an API call results + :type results: dict + :param good_codes: A list of exit codes considered successful from FortiAnalyzer + :type good_codes: list + :param stop_on_fail: If true, stops playbook run when return code is NOT IN good codes (default: true) + :type stop_on_fail: boolean + :param stop_on_success: If true, stops playbook run when return code is IN good codes (default: false) + :type stop_on_success: boolean + :param changed: If True, tells Ansible that object was changed (default: false) + :type skipped: boolean + :param skipped: If True, tells Ansible that object was skipped (default: false) + :type skipped: boolean + :param unreachable: If True, tells Ansible that object was unreachable (default: false) + :type unreachable: boolean + :param failed: If True, tells Ansible that execution was a failure. Overrides good_codes. (default: false) + :type unreachable: boolean + :param success: If True, tells Ansible that execution was a success. Overrides good_codes. (default: false) + :type unreachable: boolean + :param changed_if_success: If True, defaults to changed if successful if you specify or not" + :type changed_if_success: boolean + :param ansible_facts: A prepared dictionary of ansible facts from the execution. + :type ansible_facts: dict + + :return: A string object that contains an error message + :rtype: str + """ + + # VALIDATION ERROR + if (len(results) == 0) or (failed and success) or (changed and unreachable): + module.exit_json(msg="Handle_response was called with no results, or conflicting failed/success or " + "changed/unreachable parameters. Fix the exit code on module. " + "Generic Failure", failed=True) + + # IDENTIFY SUCCESS/FAIL IF NOT DEFINED + if not failed and not success: + if len(results) > 0: + if results[0] not in good_codes: + failed = True + elif results[0] in good_codes: + success = True + + if len(results) > 0: + # IF NO MESSAGE WAS SUPPLIED, GET IT FROM THE RESULTS, IF THAT DOESN'T WORK, THEN WRITE AN ERROR MESSAGE + if msg == "NULL": + try: + msg = results[1]['status']['message'] + except BaseException: + msg = "No status message returned at results[1][status][message], " \ + "and none supplied to msg parameter for handle_response." + + if failed: + # BECAUSE SKIPPED/FAILED WILL OFTEN OCCUR ON CODES THAT DON'T GET INCLUDED, THEY ARE CONSIDERED FAILURES + # HOWEVER, THEY ARE MUTUALLY EXCLUSIVE, SO IF IT IS MARKED SKIPPED OR UNREACHABLE BY THE MODULE LOGIC + # THEN REMOVE THE FAILED FLAG SO IT DOESN'T OVERRIDE THE DESIRED STATUS OF SKIPPED OR UNREACHABLE. + if failed and skipped: + failed = False + if failed and unreachable: + failed = False + if stop_on_fail: + if self._uses_workspace: + try: + self.run_unlock() + except BaseException as err: + raise FAZBaseException(msg=("Couldn't unlock ADOM! Error: " + str(err))) + module.exit_json(msg=msg, failed=failed, changed=changed, unreachable=unreachable, skipped=skipped, + results=results[1], ansible_facts=ansible_facts, rc=results[0], + invocation={"module_args": ansible_facts["ansible_params"]}) + elif success: + if changed_if_success: + changed = True + success = False + if stop_on_success: + if self._uses_workspace: + try: + self.run_unlock() + except BaseException as err: + raise FAZBaseException(msg=("Couldn't unlock ADOM! Error: " + str(err))) + module.exit_json(msg=msg, success=success, changed=changed, unreachable=unreachable, + skipped=skipped, results=results[1], ansible_facts=ansible_facts, rc=results[0], + invocation={"module_args": ansible_facts["ansible_params"]}) + return msg + + @staticmethod + def construct_ansible_facts(response, ansible_params, paramgram, *args, **kwargs): + """ + Constructs a dictionary to return to ansible facts, containing various information about the execution. + + :param response: Contains the response from the FortiAnalyzer. + :type response: dict + :param ansible_params: Contains the parameters Ansible was called with. + :type ansible_params: dict + :param paramgram: Contains the paramgram passed to the modules' local modify function. + :type paramgram: dict + :param args: Free-form arguments that could be added. + :param kwargs: Free-form keyword arguments that could be added. + + :return: A dictionary containing lots of information to append to Ansible Facts. + :rtype: dict + """ + + facts = { + "response": response, + "ansible_params": scrub_dict(ansible_params), + "paramgram": scrub_dict(paramgram), + } + + if args: + facts["custom_args"] = args + if kwargs: + facts.update(kwargs) + + return facts + + @property + def uses_workspace(self): + return self._uses_workspace + + @uses_workspace.setter + def uses_workspace(self, val): + self._uses_workspace = val + + @property + def uses_adoms(self): + return self._uses_adoms + + @uses_adoms.setter + def uses_adoms(self, val): + self._uses_adoms = val + + def add_adom_to_lock_list(self, adom): + if adom not in self._locked_adom_list: + self._locked_adom_list.append(adom) + + def remove_adom_from_lock_list(self, adom): + if adom in self._locked_adom_list: + self._locked_adom_list.remove(adom) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/common.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/common.py new file mode 100644 index 00000000..44579703 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/common.py @@ -0,0 +1,242 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.common.collections import is_string +from ansible.module_utils.six import iteritems + +INVALID_IDENTIFIER_SYMBOLS = r'[^a-zA-Z0-9_]' + +IDENTITY_PROPERTIES = ['id', 'version', 'ruleId'] +NON_COMPARABLE_PROPERTIES = IDENTITY_PROPERTIES + ['isSystemDefined', 'links', 'token', 'rulePosition'] + + +class HTTPMethod: + GET = 'get' + POST = 'post' + PUT = 'put' + DELETE = 'delete' + + +class ResponseParams: + SUCCESS = 'success' + STATUS_CODE = 'status_code' + RESPONSE = 'response' + + +class FtdConfigurationError(Exception): + def __init__(self, msg, obj=None): + super(FtdConfigurationError, self).__init__(msg) + self.msg = msg + self.obj = obj + + +class FtdServerError(Exception): + def __init__(self, response, code): + super(FtdServerError, self).__init__(response) + self.response = response + self.code = code + + +class FtdUnexpectedResponse(Exception): + """The exception to be raised in case of unexpected responses from 3d parties.""" + pass + + +def construct_ansible_facts(response, params): + facts = dict() + if response: + response_body = response['items'] if 'items' in response else response + if params.get('register_as'): + facts[params['register_as']] = response_body + elif type(response_body) is dict and response_body.get('name') and response_body.get('type'): + object_name = re.sub(INVALID_IDENTIFIER_SYMBOLS, '_', response_body['name'].lower()) + fact_name = '%s_%s' % (response_body['type'], object_name) + facts[fact_name] = response_body + return facts + + +def copy_identity_properties(source_obj, dest_obj): + for property_name in IDENTITY_PROPERTIES: + if property_name in source_obj: + dest_obj[property_name] = source_obj[property_name] + return dest_obj + + +def is_object_ref(d): + """ + Checks if a dictionary is a reference object. The dictionary is considered to be a + reference object when it contains non-empty 'id' and 'type' fields. + + :type d: dict + :return: True if passed dictionary is a reference object, otherwise False + """ + has_id = 'id' in d.keys() and d['id'] + has_type = 'type' in d.keys() and d['type'] + return has_id and has_type + + +def equal_object_refs(d1, d2): + """ + Checks whether two references point to the same object. + + :type d1: dict + :type d2: dict + :return: True if passed references point to the same object, otherwise False + """ + have_equal_ids = d1['id'] == d2['id'] + have_equal_types = d1['type'] == d2['type'] + return have_equal_ids and have_equal_types + + +def equal_lists(l1, l2): + """ + Checks whether two lists are equal. The order of elements in the arrays is important. + + :type l1: list + :type l2: list + :return: True if passed lists, their elements and order of elements are equal. Otherwise, returns False. + """ + if len(l1) != len(l2): + return False + + for v1, v2 in zip(l1, l2): + if not equal_values(v1, v2): + return False + + return True + + +def equal_dicts(d1, d2, compare_by_reference=True): + """ + Checks whether two dictionaries are equal. If `compare_by_reference` is set to True, dictionaries referencing + objects are compared using `equal_object_refs` method. Otherwise, every key and value is checked. + + :type d1: dict + :type d2: dict + :param compare_by_reference: if True, dictionaries referencing objects are compared using `equal_object_refs` method + :return: True if passed dicts are equal. Otherwise, returns False. + """ + if compare_by_reference and is_object_ref(d1) and is_object_ref(d2): + return equal_object_refs(d1, d2) + + if len(d1) != len(d2): + return False + + for key, v1 in d1.items(): + if key not in d2: + return False + + v2 = d2[key] + if not equal_values(v1, v2): + return False + + return True + + +def equal_values(v1, v2): + """ + Checks whether types and content of two values are the same. In case of complex objects, the method might be + called recursively. + + :param v1: first value + :param v2: second value + :return: True if types and content of passed values are equal. Otherwise, returns False. + :rtype: bool + """ + + # string-like values might have same text but different types, so checking them separately + if is_string(v1) and is_string(v2): + return to_text(v1) == to_text(v2) + + if type(v1) != type(v2): + return False + value_type = type(v1) + + if value_type == list: + return equal_lists(v1, v2) + elif value_type == dict: + return equal_dicts(v1, v2) + else: + return v1 == v2 + + +def equal_objects(d1, d2): + """ + Checks whether two objects are equal. Ignores special object properties (e.g. 'id', 'version') and + properties with None and empty values. In case properties contains a reference to the other object, + only object identities (ids and types) are checked. Also, if an array field contains multiple references + to the same object, duplicates are ignored when comparing objects. + + :type d1: dict + :type d2: dict + :return: True if passed objects and their properties are equal. Otherwise, returns False. + """ + + def prepare_data_for_comparison(d): + d = dict((k, d[k]) for k in d.keys() if k not in NON_COMPARABLE_PROPERTIES and d[k]) + d = delete_ref_duplicates(d) + return d + + d1 = prepare_data_for_comparison(d1) + d2 = prepare_data_for_comparison(d2) + return equal_dicts(d1, d2, compare_by_reference=False) + + +def delete_ref_duplicates(d): + """ + Removes reference duplicates from array fields: if an array contains multiple items and some of + them refer to the same object, only unique references are preserved (duplicates are removed). + + :param d: dict with data + :type d: dict + :return: dict without reference duplicates + """ + + def delete_ref_duplicates_from_list(refs): + if all(type(i) == dict and is_object_ref(i) for i in refs): + unique_refs = set() + unique_list = list() + for i in refs: + key = (i['id'], i['type']) + if key not in unique_refs: + unique_refs.add(key) + unique_list.append(i) + + return list(unique_list) + + else: + return refs + + if not d: + return d + + modified_d = {} + for k, v in iteritems(d): + if type(v) == list: + modified_d[k] = delete_ref_duplicates_from_list(v) + elif type(v) == dict: + modified_d[k] = delete_ref_duplicates(v) + else: + modified_d[k] = v + return modified_d diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/configuration.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/configuration.py new file mode 100644 index 00000000..1d7ad643 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/configuration.py @@ -0,0 +1,569 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import copy +from functools import partial + +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import HTTPMethod, equal_objects, FtdConfigurationError, \ + FtdServerError, ResponseParams, copy_identity_properties, FtdUnexpectedResponse +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import OperationField, ValidationError +from ansible.module_utils.six import iteritems + +DEFAULT_PAGE_SIZE = 10 +DEFAULT_OFFSET = 0 + +UNPROCESSABLE_ENTITY_STATUS = 422 +INVALID_UUID_ERROR_MESSAGE = "Validation failed due to an invalid UUID" +DUPLICATE_NAME_ERROR_MESSAGE = "Validation failed due to a duplicate name" + +MULTIPLE_DUPLICATES_FOUND_ERROR = ( + "Multiple objects matching specified filters are found. " + "Please, define filters more precisely to match one object exactly." +) +DUPLICATE_ERROR = ( + "Cannot add a new object. " + "An object with the same name but different parameters already exists." +) +ADD_OPERATION_NOT_SUPPORTED_ERROR = ( + "Cannot add a new object while executing an upsert request. " + "Creation of objects with this type is not supported." +) + +PATH_PARAMS_FOR_DEFAULT_OBJ = {'objId': 'default'} + + +class OperationNamePrefix: + ADD = 'add' + EDIT = 'edit' + GET = 'get' + DELETE = 'delete' + UPSERT = 'upsert' + + +class QueryParams: + FILTER = 'filter' + + +class ParamName: + QUERY_PARAMS = 'query_params' + PATH_PARAMS = 'path_params' + DATA = 'data' + FILTERS = 'filters' + + +class CheckModeException(Exception): + pass + + +class FtdInvalidOperationNameError(Exception): + def __init__(self, operation_name): + super(FtdInvalidOperationNameError, self).__init__(operation_name) + self.operation_name = operation_name + + +class OperationChecker(object): + + @classmethod + def is_add_operation(cls, operation_name, operation_spec): + """ + Check if operation defined with 'operation_name' is add object operation according to 'operation_spec'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :return: True if the called operation is add object operation, otherwise False + :rtype: bool + """ + # Some endpoints have non-CRUD operations, so checking operation name is required in addition to the HTTP method + return operation_name.startswith(OperationNamePrefix.ADD) and is_post_request(operation_spec) + + @classmethod + def is_edit_operation(cls, operation_name, operation_spec): + """ + Check if operation defined with 'operation_name' is edit object operation according to 'operation_spec'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :return: True if the called operation is edit object operation, otherwise False + :rtype: bool + """ + # Some endpoints have non-CRUD operations, so checking operation name is required in addition to the HTTP method + return operation_name.startswith(OperationNamePrefix.EDIT) and is_put_request(operation_spec) + + @classmethod + def is_delete_operation(cls, operation_name, operation_spec): + """ + Check if operation defined with 'operation_name' is delete object operation according to 'operation_spec'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :return: True if the called operation is delete object operation, otherwise False + :rtype: bool + """ + # Some endpoints have non-CRUD operations, so checking operation name is required in addition to the HTTP method + return operation_name.startswith(OperationNamePrefix.DELETE) \ + and operation_spec[OperationField.METHOD] == HTTPMethod.DELETE + + @classmethod + def is_get_list_operation(cls, operation_name, operation_spec): + """ + Check if operation defined with 'operation_name' is get list of objects operation according to 'operation_spec'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :return: True if the called operation is get a list of objects operation, otherwise False + :rtype: bool + """ + return operation_spec[OperationField.METHOD] == HTTPMethod.GET \ + and operation_spec[OperationField.RETURN_MULTIPLE_ITEMS] + + @classmethod + def is_get_operation(cls, operation_name, operation_spec): + """ + Check if operation defined with 'operation_name' is get objects operation according to 'operation_spec'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :return: True if the called operation is get object operation, otherwise False + :rtype: bool + """ + return operation_spec[OperationField.METHOD] == HTTPMethod.GET \ + and not operation_spec[OperationField.RETURN_MULTIPLE_ITEMS] + + @classmethod + def is_upsert_operation(cls, operation_name): + """ + Check if operation defined with 'operation_name' is upsert objects operation according to 'operation_name'. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :return: True if the called operation is upsert object operation, otherwise False + :rtype: bool + """ + return operation_name.startswith(OperationNamePrefix.UPSERT) + + @classmethod + def is_find_by_filter_operation(cls, operation_name, params, operation_spec): + """ + Checks whether the called operation is 'find by filter'. This operation fetches all objects and finds + the matching ones by the given filter. As filtering is done on the client side, this operation should be used + only when selected filters are not implemented on the server side. + + :param operation_name: name of the operation being called by the user + :type operation_name: str + :param operation_spec: specification of the operation being called by the user + :type operation_spec: dict + :param params: params - params should contain 'filters' + :return: True if the called operation is find by filter, otherwise False + :rtype: bool + """ + is_get_list = cls.is_get_list_operation(operation_name, operation_spec) + return is_get_list and ParamName.FILTERS in params and params[ParamName.FILTERS] + + @classmethod + def is_upsert_operation_supported(cls, operations): + """ + Checks if all operations required for upsert object operation are defined in 'operations'. + + :param operations: specification of the operations supported by model + :type operations: dict + :return: True if all criteria required to provide requested called operation are satisfied, otherwise False + :rtype: bool + """ + has_edit_op = next((name for name, spec in iteritems(operations) if cls.is_edit_operation(name, spec)), None) + has_get_list_op = next((name for name, spec in iteritems(operations) + if cls.is_get_list_operation(name, spec)), None) + return has_edit_op and has_get_list_op + + +class BaseConfigurationResource(object): + + def __init__(self, conn, check_mode=False): + self._conn = conn + self.config_changed = False + self._operation_spec_cache = {} + self._models_operations_specs_cache = {} + self._check_mode = check_mode + self._operation_checker = OperationChecker + self._system_info = None + + def execute_operation(self, op_name, params): + """ + Allow user request execution of simple operations(natively supported by API provider) as well as complex + operations(operations that are implemented as a set of simple operations). + + :param op_name: name of the operation being called by the user + :type op_name: str + :param params: definition of the params that operation should be executed with + :type params: dict + :return: Result of the operation being executed + :rtype: dict + """ + if self._operation_checker.is_upsert_operation(op_name): + return self.upsert_object(op_name, params) + else: + return self.crud_operation(op_name, params) + + def crud_operation(self, op_name, params): + """ + Allow user request execution of simple operations(natively supported by API provider) only. + + :param op_name: name of the operation being called by the user + :type op_name: str + :param params: definition of the params that operation should be executed with + :type params: dict + :return: Result of the operation being executed + :rtype: dict + """ + op_spec = self.get_operation_spec(op_name) + if op_spec is None: + raise FtdInvalidOperationNameError(op_name) + + if self._operation_checker.is_add_operation(op_name, op_spec): + resp = self.add_object(op_name, params) + elif self._operation_checker.is_edit_operation(op_name, op_spec): + resp = self.edit_object(op_name, params) + elif self._operation_checker.is_delete_operation(op_name, op_spec): + resp = self.delete_object(op_name, params) + elif self._operation_checker.is_find_by_filter_operation(op_name, params, op_spec): + resp = list(self.get_objects_by_filter(op_name, params)) + else: + resp = self.send_general_request(op_name, params) + return resp + + def get_operation_spec(self, operation_name): + if operation_name not in self._operation_spec_cache: + self._operation_spec_cache[operation_name] = self._conn.get_operation_spec(operation_name) + return self._operation_spec_cache[operation_name] + + def get_operation_specs_by_model_name(self, model_name): + if model_name not in self._models_operations_specs_cache: + model_op_specs = self._conn.get_operation_specs_by_model_name(model_name) + self._models_operations_specs_cache[model_name] = model_op_specs + for op_name, op_spec in iteritems(model_op_specs): + self._operation_spec_cache.setdefault(op_name, op_spec) + return self._models_operations_specs_cache[model_name] + + def get_objects_by_filter(self, operation_name, params): + + def match_filters(filter_params, obj): + for k, v in iteritems(filter_params): + if k not in obj or obj[k] != v: + return False + return True + + dummy, query_params, path_params = _get_user_params(params) + # copy required params to avoid mutation of passed `params` dict + url_params = {ParamName.QUERY_PARAMS: dict(query_params), ParamName.PATH_PARAMS: dict(path_params)} + + filters = params.get(ParamName.FILTERS) or {} + if QueryParams.FILTER not in url_params[ParamName.QUERY_PARAMS] and 'name' in filters: + # most endpoints only support filtering by name, so remaining `filters` are applied on returned objects + url_params[ParamName.QUERY_PARAMS][QueryParams.FILTER] = self._stringify_name_filter(filters) + + item_generator = iterate_over_pageable_resource( + partial(self.send_general_request, operation_name=operation_name), url_params + ) + return (i for i in item_generator if match_filters(filters, i)) + + def _stringify_name_filter(self, filters): + build_version = self.get_build_version() + if build_version >= '6.4.0': + return "fts~%s" % filters['name'] + return "name:%s" % filters['name'] + + def _fetch_system_info(self): + if not self._system_info: + params = {ParamName.PATH_PARAMS: PATH_PARAMS_FOR_DEFAULT_OBJ} + self._system_info = self.send_general_request('getSystemInformation', params) + + return self._system_info + + def get_build_version(self): + system_info = self._fetch_system_info() + return system_info['databaseInfo']['buildVersion'] + + def add_object(self, operation_name, params): + def is_duplicate_name_error(err): + return err.code == UNPROCESSABLE_ENTITY_STATUS and DUPLICATE_NAME_ERROR_MESSAGE in str(err) + + try: + return self.send_general_request(operation_name, params) + except FtdServerError as e: + if is_duplicate_name_error(e): + return self._check_equality_with_existing_object(operation_name, params, e) + else: + raise e + + def _check_equality_with_existing_object(self, operation_name, params, e): + """ + Looks for an existing object that caused "object duplicate" error and + checks whether it corresponds to the one specified in `params`. + + In case a single object is found and it is equal to one we are trying + to create, the existing object is returned. + + When the existing object is not equal to the object being created or + several objects are returned, an exception is raised. + """ + model_name = self.get_operation_spec(operation_name)[OperationField.MODEL_NAME] + existing_obj = self._find_object_matching_params(model_name, params) + + if existing_obj is not None: + if equal_objects(existing_obj, params[ParamName.DATA]): + return existing_obj + else: + raise FtdConfigurationError(DUPLICATE_ERROR, existing_obj) + + raise e + + def _find_object_matching_params(self, model_name, params): + get_list_operation = self._find_get_list_operation(model_name) + if not get_list_operation: + return None + + data = params[ParamName.DATA] + if not params.get(ParamName.FILTERS): + params[ParamName.FILTERS] = {'name': data['name']} + + obj = None + filtered_objs = self.get_objects_by_filter(get_list_operation, params) + + for i, obj in enumerate(filtered_objs): + if i > 0: + raise FtdConfigurationError(MULTIPLE_DUPLICATES_FOUND_ERROR) + obj = obj + + return obj + + def _find_get_list_operation(self, model_name): + operations = self.get_operation_specs_by_model_name(model_name) or {} + return next(( + op for op, op_spec in operations.items() + if self._operation_checker.is_get_list_operation(op, op_spec)), None) + + def _find_get_operation(self, model_name): + operations = self.get_operation_specs_by_model_name(model_name) or {} + return next(( + op for op, op_spec in operations.items() + if self._operation_checker.is_get_operation(op, op_spec)), None) + + def delete_object(self, operation_name, params): + def is_invalid_uuid_error(err): + return err.code == UNPROCESSABLE_ENTITY_STATUS and INVALID_UUID_ERROR_MESSAGE in str(err) + + try: + return self.send_general_request(operation_name, params) + except FtdServerError as e: + if is_invalid_uuid_error(e): + return {'status': 'Referenced object does not exist'} + else: + raise e + + def edit_object(self, operation_name, params): + data, dummy, path_params = _get_user_params(params) + + model_name = self.get_operation_spec(operation_name)[OperationField.MODEL_NAME] + get_operation = self._find_get_operation(model_name) + + if get_operation: + existing_object = self.send_general_request(get_operation, {ParamName.PATH_PARAMS: path_params}) + if not existing_object: + raise FtdConfigurationError('Referenced object does not exist') + elif equal_objects(existing_object, data): + return existing_object + + return self.send_general_request(operation_name, params) + + def send_general_request(self, operation_name, params): + def stop_if_check_mode(): + if self._check_mode: + raise CheckModeException() + + self.validate_params(operation_name, params) + stop_if_check_mode() + + data, query_params, path_params = _get_user_params(params) + op_spec = self.get_operation_spec(operation_name) + url, method = op_spec[OperationField.URL], op_spec[OperationField.METHOD] + + return self._send_request(url, method, data, path_params, query_params) + + def _send_request(self, url_path, http_method, body_params=None, path_params=None, query_params=None): + def raise_for_failure(resp): + if not resp[ResponseParams.SUCCESS]: + raise FtdServerError(resp[ResponseParams.RESPONSE], resp[ResponseParams.STATUS_CODE]) + + response = self._conn.send_request(url_path=url_path, http_method=http_method, body_params=body_params, + path_params=path_params, query_params=query_params) + raise_for_failure(response) + if http_method != HTTPMethod.GET: + self.config_changed = True + return response[ResponseParams.RESPONSE] + + def validate_params(self, operation_name, params): + report = {} + op_spec = self.get_operation_spec(operation_name) + data, query_params, path_params = _get_user_params(params) + + def validate(validation_method, field_name, user_params): + key = 'Invalid %s provided' % field_name + try: + is_valid, validation_report = validation_method(operation_name, user_params) + if not is_valid: + report[key] = validation_report + except Exception as e: + report[key] = str(e) + return report + + validate(self._conn.validate_query_params, ParamName.QUERY_PARAMS, query_params) + validate(self._conn.validate_path_params, ParamName.PATH_PARAMS, path_params) + if is_post_request(op_spec) or is_put_request(op_spec): + validate(self._conn.validate_data, ParamName.DATA, data) + + if report: + raise ValidationError(report) + + @staticmethod + def _get_operation_name(checker, operations): + return next((op_name for op_name, op_spec in iteritems(operations) if checker(op_name, op_spec)), None) + + def _add_upserted_object(self, model_operations, params): + add_op_name = self._get_operation_name(self._operation_checker.is_add_operation, model_operations) + if not add_op_name: + raise FtdConfigurationError(ADD_OPERATION_NOT_SUPPORTED_ERROR) + return self.add_object(add_op_name, params) + + def _edit_upserted_object(self, model_operations, existing_object, params): + edit_op_name = self._get_operation_name(self._operation_checker.is_edit_operation, model_operations) + _set_default(params, 'path_params', {}) + _set_default(params, 'data', {}) + + params['path_params']['objId'] = existing_object['id'] + copy_identity_properties(existing_object, params['data']) + return self.edit_object(edit_op_name, params) + + def upsert_object(self, op_name, params): + """ + Updates an object if it already exists, or tries to create a new one if there is no + such object. If multiple objects match filter criteria, or add operation is not supported, + the exception is raised. + + :param op_name: upsert operation name + :type op_name: str + :param params: params that upsert operation should be executed with + :type params: dict + :return: upserted object representation + :rtype: dict + """ + + def extract_and_validate_model(): + model = op_name[len(OperationNamePrefix.UPSERT):] + if not self._conn.get_model_spec(model): + raise FtdInvalidOperationNameError(op_name) + return model + + model_name = extract_and_validate_model() + model_operations = self.get_operation_specs_by_model_name(model_name) + + if not self._operation_checker.is_upsert_operation_supported(model_operations): + raise FtdInvalidOperationNameError(op_name) + + existing_obj = self._find_object_matching_params(model_name, params) + if existing_obj: + equal_to_existing_obj = equal_objects(existing_obj, params[ParamName.DATA]) + return existing_obj if equal_to_existing_obj \ + else self._edit_upserted_object(model_operations, existing_obj, params) + else: + return self._add_upserted_object(model_operations, params) + + +def _set_default(params, field_name, value): + if field_name not in params or params[field_name] is None: + params[field_name] = value + + +def is_post_request(operation_spec): + return operation_spec[OperationField.METHOD] == HTTPMethod.POST + + +def is_put_request(operation_spec): + return operation_spec[OperationField.METHOD] == HTTPMethod.PUT + + +def _get_user_params(params): + return params.get(ParamName.DATA) or {}, params.get(ParamName.QUERY_PARAMS) or {}, params.get( + ParamName.PATH_PARAMS) or {} + + +def iterate_over_pageable_resource(resource_func, params): + """ + A generator function that iterates over a resource that supports pagination and lazily returns present items + one by one. + + :param resource_func: function that receives `params` argument and returns a page of objects + :type resource_func: callable + :param params: initial dictionary of parameters that will be passed to the resource_func. + Should contain `query_params` inside. + :type params: dict + :return: an iterator containing returned items + :rtype: iterator of dict + """ + # creating a copy not to mutate passed dict + params = copy.deepcopy(params) + params[ParamName.QUERY_PARAMS].setdefault('limit', DEFAULT_PAGE_SIZE) + params[ParamName.QUERY_PARAMS].setdefault('offset', DEFAULT_OFFSET) + limit = int(params[ParamName.QUERY_PARAMS]['limit']) + + def received_less_items_than_requested(items_in_response, items_expected): + if items_in_response == items_expected: + return False + elif items_in_response < items_expected: + return True + + raise FtdUnexpectedResponse( + "Get List of Objects Response from the server contains more objects than requested. " + "There are {0} item(s) in the response while {1} was(ere) requested".format( + items_in_response, items_expected) + ) + + while True: + result = resource_func(params=params) + + for item in result['items']: + yield item + + if received_less_items_than_requested(len(result['items']), limit): + break + + # creating a copy not to mutate existing dict + params = copy.deepcopy(params) + query_params = params[ParamName.QUERY_PARAMS] + query_params['offset'] = int(query_params['offset']) + limit diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/device.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/device.py new file mode 100644 index 00000000..95569260 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/device.py @@ -0,0 +1,141 @@ +# Copyright (c) 2019 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six.moves.urllib.parse import urlparse + +try: + from kick.device2.ftd5500x.actions.ftd5500x import Ftd5500x + from kick.device2.kp.actions import Kp + + HAS_KICK = True +except ImportError: + HAS_KICK = False + + +def assert_kick_is_installed(module): + if not HAS_KICK: + module.fail_json(msg='Firepower-kickstart library is required to run this module. ' + 'Please, install the library with `pip install firepower-kickstart` ' + 'command and run the playbook again.') + + +class FtdModel: + FTD_ASA5506_X = 'Cisco ASA5506-X Threat Defense' + FTD_ASA5508_X = 'Cisco ASA5508-X Threat Defense' + FTD_ASA5516_X = 'Cisco ASA5516-X Threat Defense' + + FTD_2110 = 'Cisco Firepower 2110 Threat Defense' + FTD_2120 = 'Cisco Firepower 2120 Threat Defense' + FTD_2130 = 'Cisco Firepower 2130 Threat Defense' + FTD_2140 = 'Cisco Firepower 2140 Threat Defense' + + @classmethod + def supported_models(cls): + return [getattr(cls, item) for item in dir(cls) if item.startswith('FTD_')] + + +class FtdPlatformFactory(object): + + @staticmethod + def create(model, module_params): + for cls in AbstractFtdPlatform.__subclasses__(): + if cls.supports_ftd_model(model): + return cls(module_params) + raise ValueError("FTD model '%s' is not supported by this module." % model) + + +class AbstractFtdPlatform(object): + PLATFORM_MODELS = [] + + def install_ftd_image(self, params): + raise NotImplementedError('The method should be overridden in subclass') + + @classmethod + def supports_ftd_model(cls, model): + return model in cls.PLATFORM_MODELS + + @staticmethod + def parse_rommon_file_location(rommon_file_location): + rommon_url = urlparse(rommon_file_location) + if rommon_url.scheme != 'tftp': + raise ValueError('The ROMMON image must be downloaded from TFTP server, other protocols are not supported.') + return rommon_url.netloc, rommon_url.path + + +class Ftd2100Platform(AbstractFtdPlatform): + PLATFORM_MODELS = [FtdModel.FTD_2110, FtdModel.FTD_2120, FtdModel.FTD_2130, FtdModel.FTD_2140] + + def __init__(self, params): + self._ftd = Kp(hostname=params["device_hostname"], + login_username=params["device_username"], + login_password=params["device_password"], + sudo_password=params.get("device_sudo_password") or params["device_password"]) + + def install_ftd_image(self, params): + line = self._ftd.ssh_console(ip=params["console_ip"], + port=params["console_port"], + username=params["console_username"], + password=params["console_password"]) + + try: + rommon_server, rommon_path = self.parse_rommon_file_location(params["rommon_file_location"]) + line.baseline_fp2k_ftd(tftp_server=rommon_server, + rommon_file=rommon_path, + uut_hostname=params["device_hostname"], + uut_username=params["device_username"], + uut_password=params.get("device_new_password") or params["device_password"], + uut_ip=params["device_ip"], + uut_netmask=params["device_netmask"], + uut_gateway=params["device_gateway"], + dns_servers=params["dns_server"], + search_domains=params["search_domains"], + fxos_url=params["image_file_location"], + ftd_version=params["image_version"]) + finally: + line.disconnect() + + +class FtdAsa5500xPlatform(AbstractFtdPlatform): + PLATFORM_MODELS = [FtdModel.FTD_ASA5506_X, FtdModel.FTD_ASA5508_X, FtdModel.FTD_ASA5516_X] + + def __init__(self, params): + self._ftd = Ftd5500x(hostname=params["device_hostname"], + login_password=params["device_password"], + sudo_password=params.get("device_sudo_password") or params["device_password"]) + + def install_ftd_image(self, params): + line = self._ftd.ssh_console(ip=params["console_ip"], + port=params["console_port"], + username=params["console_username"], + password=params["console_password"]) + try: + rommon_server, rommon_path = self.parse_rommon_file_location(params["rommon_file_location"]) + line.rommon_to_new_image(rommon_tftp_server=rommon_server, + rommon_image=rommon_path, + pkg_image=params["image_file_location"], + uut_ip=params["device_ip"], + uut_netmask=params["device_netmask"], + uut_gateway=params["device_gateway"], + dns_server=params["dns_server"], + search_domains=params["search_domains"], + hostname=params["device_hostname"]) + finally: + line.disconnect() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/fdm_swagger_client.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/fdm_swagger_client.py new file mode 100644 index 00000000..7e1e062f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/fdm_swagger_client.py @@ -0,0 +1,641 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import HTTPMethod +from ansible.module_utils.six import integer_types, string_types, iteritems + +FILE_MODEL_NAME = '_File' +SUCCESS_RESPONSE_CODE = '200' +DELETE_PREFIX = 'delete' + + +class OperationField: + URL = 'url' + METHOD = 'method' + PARAMETERS = 'parameters' + MODEL_NAME = 'modelName' + DESCRIPTION = 'description' + RETURN_MULTIPLE_ITEMS = 'returnMultipleItems' + TAGS = "tags" + + +class SpecProp: + DEFINITIONS = 'definitions' + OPERATIONS = 'operations' + MODELS = 'models' + MODEL_OPERATIONS = 'model_operations' + + +class PropName: + ENUM = 'enum' + TYPE = 'type' + REQUIRED = 'required' + INVALID_TYPE = 'invalid_type' + REF = '$ref' + ALL_OF = 'allOf' + BASE_PATH = 'basePath' + PATHS = 'paths' + OPERATION_ID = 'operationId' + SCHEMA = 'schema' + ITEMS = 'items' + PROPERTIES = 'properties' + RESPONSES = 'responses' + NAME = 'name' + DESCRIPTION = 'description' + + +class PropType: + STRING = 'string' + BOOLEAN = 'boolean' + INTEGER = 'integer' + NUMBER = 'number' + OBJECT = 'object' + ARRAY = 'array' + FILE = 'file' + + +class OperationParams: + PATH = 'path' + QUERY = 'query' + + +class QueryParams: + FILTER = 'filter' + + +class PathParams: + OBJ_ID = 'objId' + + +def _get_model_name_from_url(schema_ref): + path = schema_ref.split('/') + return path[len(path) - 1] + + +class IllegalArgumentException(ValueError): + """ + Exception raised when the function parameters: + - not all passed + - empty string + - wrong type + """ + pass + + +class ValidationError(ValueError): + pass + + +class FdmSwaggerParser: + _definitions = None + _base_path = None + + def parse_spec(self, spec, docs=None): + """ + This method simplifies a swagger format, resolves a model name for each operation, and adds documentation for + each operation and model if it is provided. + + :param spec: An API specification in the swagger format, see + + :type spec: dict + :param spec: A documentation map containing descriptions for models, operations and operation parameters. + :type docs: dict + :rtype: dict + :return: + Ex. + The models field contains model definition from swagger see + <#https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitions> + { + 'models':{ + 'model_name':{...}, + ... + }, + 'operations':{ + 'operation_name':{ + 'method': 'get', #post, put, delete + 'url': '/api/fdm/v2/object/networks', #url already contains a value from `basePath` + 'modelName': 'NetworkObject', # it is a link to the model from 'models' + # None - for a delete operation or we don't have information + # '_File' - if an endpoint works with files + 'returnMultipleItems': False, # shows if the operation returns a single item or an item list + 'parameters': { + 'path':{ + 'param_name':{ + 'type': 'string'#integer, boolean, number + 'required' True #False + } + ... + }, + 'query':{ + 'param_name':{ + 'type': 'string'#integer, boolean, number + 'required' True #False + } + ... + } + } + }, + ... + }, + 'model_operations':{ + 'model_name':{ # a list of operations available for the current model + 'operation_name':{ + ... # the same as in the operations section + }, + ... + }, + ... + } + } + """ + self._definitions = spec[SpecProp.DEFINITIONS] + self._base_path = spec[PropName.BASE_PATH] + operations = self._get_operations(spec) + + if docs: + operations = self._enrich_operations_with_docs(operations, docs) + self._definitions = self._enrich_definitions_with_docs(self._definitions, docs) + + return { + SpecProp.MODELS: self._definitions, + SpecProp.OPERATIONS: operations, + SpecProp.MODEL_OPERATIONS: self._get_model_operations(operations) + } + + @property + def base_path(self): + return self._base_path + + def _get_model_operations(self, operations): + model_operations = {} + for operations_name, params in iteritems(operations): + model_name = params[OperationField.MODEL_NAME] + model_operations.setdefault(model_name, {})[operations_name] = params + return model_operations + + def _get_operations(self, spec): + paths_dict = spec[PropName.PATHS] + operations_dict = {} + for url, operation_params in iteritems(paths_dict): + for method, params in iteritems(operation_params): + operation = { + OperationField.METHOD: method, + OperationField.URL: self._base_path + url, + OperationField.MODEL_NAME: self._get_model_name(method, params), + OperationField.RETURN_MULTIPLE_ITEMS: self._return_multiple_items(params), + OperationField.TAGS: params.get(OperationField.TAGS, []) + } + if OperationField.PARAMETERS in params: + operation[OperationField.PARAMETERS] = self._get_rest_params(params[OperationField.PARAMETERS]) + + operation_id = params[PropName.OPERATION_ID] + operations_dict[operation_id] = operation + return operations_dict + + def _enrich_operations_with_docs(self, operations, docs): + def get_operation_docs(op): + op_url = op[OperationField.URL][len(self._base_path):] + return docs[PropName.PATHS].get(op_url, {}).get(op[OperationField.METHOD], {}) + + for operation in operations.values(): + operation_docs = get_operation_docs(operation) + operation[OperationField.DESCRIPTION] = operation_docs.get(PropName.DESCRIPTION, '') + + if OperationField.PARAMETERS in operation: + param_descriptions = dict(( + (p[PropName.NAME], p[PropName.DESCRIPTION]) + for p in operation_docs.get(OperationField.PARAMETERS, {}) + )) + + for param_name, params_spec in operation[OperationField.PARAMETERS][OperationParams.PATH].items(): + params_spec[OperationField.DESCRIPTION] = param_descriptions.get(param_name, '') + + for param_name, params_spec in operation[OperationField.PARAMETERS][OperationParams.QUERY].items(): + params_spec[OperationField.DESCRIPTION] = param_descriptions.get(param_name, '') + + return operations + + def _enrich_definitions_with_docs(self, definitions, docs): + for model_name, model_def in definitions.items(): + model_docs = docs[SpecProp.DEFINITIONS].get(model_name, {}) + model_def[PropName.DESCRIPTION] = model_docs.get(PropName.DESCRIPTION, '') + for prop_name, prop_spec in model_def.get(PropName.PROPERTIES, {}).items(): + prop_spec[PropName.DESCRIPTION] = model_docs.get(PropName.PROPERTIES, {}).get(prop_name, '') + prop_spec[PropName.REQUIRED] = prop_name in model_def.get(PropName.REQUIRED, []) + return definitions + + def _get_model_name(self, method, params): + if method == HTTPMethod.GET: + return self._get_model_name_from_responses(params) + elif method == HTTPMethod.POST or method == HTTPMethod.PUT: + return self._get_model_name_for_post_put_requests(params) + elif method == HTTPMethod.DELETE: + return self._get_model_name_from_delete_operation(params) + else: + return None + + @staticmethod + def _return_multiple_items(op_params): + """ + Defines if the operation returns one item or a list of items. + + :param op_params: operation specification + :return: True if the operation returns a list of items, otherwise False + """ + try: + schema = op_params[PropName.RESPONSES][SUCCESS_RESPONSE_CODE][PropName.SCHEMA] + return PropName.ITEMS in schema[PropName.PROPERTIES] + except KeyError: + return False + + def _get_model_name_from_delete_operation(self, params): + operation_id = params[PropName.OPERATION_ID] + if operation_id.startswith(DELETE_PREFIX): + model_name = operation_id[len(DELETE_PREFIX):] + if model_name in self._definitions: + return model_name + return None + + def _get_model_name_for_post_put_requests(self, params): + model_name = None + if OperationField.PARAMETERS in params: + body_param_dict = self._get_body_param_from_parameters(params[OperationField.PARAMETERS]) + if body_param_dict: + schema_ref = body_param_dict[PropName.SCHEMA][PropName.REF] + model_name = self._get_model_name_byschema_ref(schema_ref) + if model_name is None: + model_name = self._get_model_name_from_responses(params) + return model_name + + @staticmethod + def _get_body_param_from_parameters(params): + return next((param for param in params if param['in'] == 'body'), None) + + def _get_model_name_from_responses(self, params): + responses = params[PropName.RESPONSES] + if SUCCESS_RESPONSE_CODE in responses: + response = responses[SUCCESS_RESPONSE_CODE][PropName.SCHEMA] + if PropName.REF in response: + return self._get_model_name_byschema_ref(response[PropName.REF]) + elif PropName.PROPERTIES in response: + ref = response[PropName.PROPERTIES][PropName.ITEMS][PropName.ITEMS][PropName.REF] + return self._get_model_name_byschema_ref(ref) + elif (PropName.TYPE in response) and response[PropName.TYPE] == PropType.FILE: + return FILE_MODEL_NAME + else: + return None + + def _get_rest_params(self, params): + path = {} + query = {} + operation_param = { + OperationParams.PATH: path, + OperationParams.QUERY: query + } + for param in params: + in_param = param['in'] + if in_param == OperationParams.QUERY: + query[param[PropName.NAME]] = self._simplify_param_def(param) + elif in_param == OperationParams.PATH: + path[param[PropName.NAME]] = self._simplify_param_def(param) + return operation_param + + @staticmethod + def _simplify_param_def(param): + return { + PropName.TYPE: param[PropName.TYPE], + PropName.REQUIRED: param[PropName.REQUIRED] + } + + def _get_model_name_byschema_ref(self, schema_ref): + model_name = _get_model_name_from_url(schema_ref) + model_def = self._definitions[model_name] + if PropName.ALL_OF in model_def: + return self._get_model_name_byschema_ref(model_def[PropName.ALL_OF][0][PropName.REF]) + else: + return model_name + + +class FdmSwaggerValidator: + def __init__(self, spec): + """ + :param spec: dict + data from FdmSwaggerParser().parse_spec() + """ + self._operations = spec[SpecProp.OPERATIONS] + self._models = spec[SpecProp.MODELS] + + def validate_data(self, operation_name, data=None): + """ + Validate data for the post|put requests + :param operation_name: string + The value must be non empty string. + The operation name is used to get a model specification + :param data: dict + The value must be in the format that the model(from operation) expects + :rtype: (bool, string|dict) + :return: + (True, None) - if data valid + Invalid: + (False, { + 'required': [ #list of the fields that are required but were not present in the data + 'field_name', + 'patent.field_name',# when the nested field is omitted + 'patent.list[2].field_name' # if data is array and one of the field is omitted + ], + 'invalid_type':[ #list of the fields with invalid data + { + 'path': 'objId', #field name or path to the field. Ex. objects[3].id, parent.name + 'expected_type': 'string',# expected type. Ex. 'object', 'array', 'string', 'integer', + # 'boolean', 'number' + 'actually_value': 1 # the value that user passed + } + ] + }) + :raises IllegalArgumentException + 'The operation_name parameter must be a non-empty string' if operation_name is not valid + 'The data parameter must be a dict' if data neither dict or None + '{operation_name} operation does not support' if the spec does not contain the operation + """ + if data is None: + data = {} + + self._check_validate_data_params(data, operation_name) + + operation = self._operations[operation_name] + model = self._models[operation[OperationField.MODEL_NAME]] + status = self._init_report() + + self._validate_object(status, model, data, '') + + if len(status[PropName.REQUIRED]) > 0 or len(status[PropName.INVALID_TYPE]) > 0: + return False, self._delete_empty_field_from_report(status) + return True, None + + def _check_validate_data_params(self, data, operation_name): + if not operation_name or not isinstance(operation_name, string_types): + raise IllegalArgumentException("The operation_name parameter must be a non-empty string") + if not isinstance(data, dict): + raise IllegalArgumentException("The data parameter must be a dict") + if operation_name not in self._operations: + raise IllegalArgumentException("{0} operation does not support".format(operation_name)) + + def validate_query_params(self, operation_name, params): + """ + Validate params for the get requests. Use this method for validating the query part of the url. + :param operation_name: string + The value must be non empty string. + The operation name is used to get a params specification + :param params: dict + should be in the format that the specification(from operation) expects + Ex. + { + 'objId': "string_value", + 'p_integer': 1, + 'p_boolean': True, + 'p_number': 2.3 + } + :rtype:(Boolean, msg) + :return: + (True, None) - if params valid + Invalid: + (False, { + 'required': [ #list of the fields that are required but are not present in the params + 'field_name' + ], + 'invalid_type':[ #list of the fields with invalid data and expected type of the params + { + 'path': 'objId', #field name + 'expected_type': 'string',#expected type. Ex. 'string', 'integer', 'boolean', 'number' + 'actually_value': 1 # the value that user passed + } + ] + }) + :raises IllegalArgumentException + 'The operation_name parameter must be a non-empty string' if operation_name is not valid + 'The params parameter must be a dict' if params neither dict or None + '{operation_name} operation does not support' if the spec does not contain the operation + """ + return self._validate_url_params(operation_name, params, resource=OperationParams.QUERY) + + def validate_path_params(self, operation_name, params): + """ + Validate params for the get requests. Use this method for validating the path part of the url. + :param operation_name: string + The value must be non empty string. + The operation name is used to get a params specification + :param params: dict + should be in the format that the specification(from operation) expects + + Ex. + { + 'objId': "string_value", + 'p_integer': 1, + 'p_boolean': True, + 'p_number': 2.3 + } + :rtype:(Boolean, msg) + :return: + (True, None) - if params valid + Invalid: + (False, { + 'required': [ #list of the fields that are required but are not present in the params + 'field_name' + ], + 'invalid_type':[ #list of the fields with invalid data and expected type of the params + { + 'path': 'objId', #field name + 'expected_type': 'string',#expected type. Ex. 'string', 'integer', 'boolean', 'number' + 'actually_value': 1 # the value that user passed + } + ] + }) + :raises IllegalArgumentException + 'The operation_name parameter must be a non-empty string' if operation_name is not valid + 'The params parameter must be a dict' if params neither dict or None + '{operation_name} operation does not support' if the spec does not contain the operation + """ + return self._validate_url_params(operation_name, params, resource=OperationParams.PATH) + + def _validate_url_params(self, operation, params, resource): + if params is None: + params = {} + + self._check_validate_url_params(operation, params) + + operation = self._operations[operation] + if OperationField.PARAMETERS in operation and resource in operation[OperationField.PARAMETERS]: + spec = operation[OperationField.PARAMETERS][resource] + status = self._init_report() + self._check_url_params(status, spec, params) + + if len(status[PropName.REQUIRED]) > 0 or len(status[PropName.INVALID_TYPE]) > 0: + return False, self._delete_empty_field_from_report(status) + return True, None + else: + return True, None + + def _check_validate_url_params(self, operation, params): + if not operation or not isinstance(operation, string_types): + raise IllegalArgumentException("The operation_name parameter must be a non-empty string") + if not isinstance(params, dict): + raise IllegalArgumentException("The params parameter must be a dict") + if operation not in self._operations: + raise IllegalArgumentException("{0} operation does not support".format(operation)) + + def _check_url_params(self, status, spec, params): + for prop_name in spec.keys(): + prop = spec[prop_name] + if prop[PropName.REQUIRED] and prop_name not in params: + status[PropName.REQUIRED].append(prop_name) + continue + if prop_name in params: + expected_type = prop[PropName.TYPE] + value = params[prop_name] + if prop_name in params and not self._is_correct_simple_types(expected_type, value, allow_null=False): + self._add_invalid_type_report(status, '', prop_name, expected_type, value) + + def _validate_object(self, status, model, data, path): + if self._is_enum(model): + self._check_enum(status, model, data, path) + elif self._is_object(model): + self._check_object(status, model, data, path) + + def _is_enum(self, model): + return self._is_string_type(model) and PropName.ENUM in model + + def _check_enum(self, status, model, value, path): + if value is not None and value not in model[PropName.ENUM]: + self._add_invalid_type_report(status, path, '', PropName.ENUM, value) + + def _add_invalid_type_report(self, status, path, prop_name, expected_type, actually_value): + status[PropName.INVALID_TYPE].append({ + 'path': self._create_path_to_field(path, prop_name), + 'expected_type': expected_type, + 'actually_value': actually_value + }) + + def _check_object(self, status, model, data, path): + if data is None: + return + + if not isinstance(data, dict): + self._add_invalid_type_report(status, path, '', PropType.OBJECT, data) + return None + + if PropName.REQUIRED in model: + self._check_required_fields(status, model[PropName.REQUIRED], data, path) + + model_properties = model[PropName.PROPERTIES] + for prop in model_properties.keys(): + if prop in data: + model_prop_val = model_properties[prop] + expected_type = model_prop_val[PropName.TYPE] + actually_value = data[prop] + self._check_types(status, actually_value, expected_type, model_prop_val, path, prop) + + def _check_types(self, status, actually_value, expected_type, model, path, prop_name): + if expected_type == PropType.OBJECT: + ref_model = self._get_model_by_ref(model) + + self._validate_object(status, ref_model, actually_value, + path=self._create_path_to_field(path, prop_name)) + elif expected_type == PropType.ARRAY: + self._check_array(status, model, actually_value, + path=self._create_path_to_field(path, prop_name)) + elif not self._is_correct_simple_types(expected_type, actually_value): + self._add_invalid_type_report(status, path, prop_name, expected_type, actually_value) + + def _get_model_by_ref(self, model_prop_val): + model = _get_model_name_from_url(model_prop_val[PropName.REF]) + return self._models[model] + + def _check_required_fields(self, status, required_fields, data, path): + missed_required_fields = [self._create_path_to_field(path, field) for field in + required_fields if field not in data.keys() or data[field] is None] + if len(missed_required_fields) > 0: + status[PropName.REQUIRED] += missed_required_fields + + def _check_array(self, status, model, data, path): + if data is None: + return + elif not isinstance(data, list): + self._add_invalid_type_report(status, path, '', PropType.ARRAY, data) + else: + item_model = model[PropName.ITEMS] + for i, item_data in enumerate(data): + self._check_types(status, item_data, item_model[PropName.TYPE], item_model, "{0}[{1}]".format(path, i), + '') + + @staticmethod + def _is_correct_simple_types(expected_type, value, allow_null=True): + def is_numeric_string(s): + try: + float(s) + return True + except ValueError: + return False + + if value is None and allow_null: + return True + elif expected_type == PropType.STRING: + return isinstance(value, string_types) + elif expected_type == PropType.BOOLEAN: + return isinstance(value, bool) + elif expected_type == PropType.INTEGER: + is_integer = isinstance(value, integer_types) and not isinstance(value, bool) + is_digit_string = isinstance(value, string_types) and value.isdigit() + return is_integer or is_digit_string + elif expected_type == PropType.NUMBER: + is_number = isinstance(value, (integer_types, float)) and not isinstance(value, bool) + is_numeric_string = isinstance(value, string_types) and is_numeric_string(value) + return is_number or is_numeric_string + return False + + @staticmethod + def _is_string_type(model): + return PropName.TYPE in model and model[PropName.TYPE] == PropType.STRING + + @staticmethod + def _init_report(): + return { + PropName.REQUIRED: [], + PropName.INVALID_TYPE: [] + } + + @staticmethod + def _delete_empty_field_from_report(status): + if not status[PropName.REQUIRED]: + del status[PropName.REQUIRED] + if not status[PropName.INVALID_TYPE]: + del status[PropName.INVALID_TYPE] + return status + + @staticmethod + def _create_path_to_field(path='', field=''): + separator = '' + if path and field: + separator = '.' + return "{0}{1}{2}".format(path, separator, field) + + @staticmethod + def _is_object(model): + return PropName.TYPE in model and model[PropName.TYPE] == PropType.OBJECT diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/operation.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/operation.py new file mode 100644 index 00000000..641cf9ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ftd/operation.py @@ -0,0 +1,44 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.network.plugins.module_utils.network.ftd.configuration import ParamName, PATH_PARAMS_FOR_DEFAULT_OBJ + + +class FtdOperations: + """ + Utility class for common operation names + """ + GET_SYSTEM_INFO = 'getSystemInformation' + GET_MANAGEMENT_IP_LIST = 'getManagementIPList' + GET_DNS_SETTING_LIST = 'getDeviceDNSSettingsList' + GET_DNS_SERVER_GROUP = 'getDNSServerGroup' + + +def get_system_info(resource): + """ + Executes `getSystemInformation` operation and returns information about the system. + + :param resource: a BaseConfigurationResource object to connect to the device + :return: a dictionary with system information about the device and its software + """ + path_params = {ParamName.PATH_PARAMS: PATH_PARAMS_FOR_DEFAULT_OBJ} + system_info = resource.execute_operation(FtdOperations.GET_SYSTEM_INFO, path_params) + return system_info diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/icx.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/icx.py new file mode 100644 index 00000000..cabece78 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/icx/icx.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Ansible Project +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONFIGS = {} + + +def get_connection(module): + return Connection(module._socket_path) + + +def load_config(module, commands): + connection = get_connection(module) + + try: + resp = connection.edit_config(candidate=commands) + return resp.get('response') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def exec_scp(module, command): + connection = Connection(module._socket_path) + return connection.scp(**command) + + +def get_config(module, flags=None, compare=None): + flag_str = ' '.join(to_list(flags)) + try: + return _DEVICE_CONFIGS[flag_str] + except KeyError: + connection = get_connection(module) + try: + out = connection.get_config(flags=flags, compare=compare) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[flag_str] = cfg + return cfg + + +def check_args(module, warnings): + pass + + +def get_defaults_flag(module): + connection = get_connection(module) + try: + out = connection.get_defaults_flag() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return to_text(out, errors='surrogate_then_replace').strip() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/common.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/common.py new file mode 100644 index 00000000..ff632520 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ingate/common.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ingate Systems AB +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +try: + from ingate import ingatesdk + HAS_INGATESDK = True +except ImportError: + HAS_INGATESDK = False + + +def ingate_argument_spec(**kwargs): + client_options = dict( + version=dict(choices=['v1'], default='v1'), + scheme=dict(choices=['http', 'https'], required=True), + address=dict(type='str', required=True), + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + port=dict(type='int'), + timeout=dict(type='int'), + validate_certs=dict(default=True, type='bool', aliases=['verify_ssl']), + ) + argument_spec = dict( + client=dict(type='dict', required=True, + options=client_options), + ) + argument_spec.update(kwargs) + return argument_spec + + +def ingate_create_client(**kwargs): + api_client = ingate_create_client_noauth(**kwargs) + + # Authenticate and get hold of a security token. + api_client.authenticate() + + # Return the client. + return api_client + + +def ingate_create_client_noauth(**kwargs): + client_params = kwargs['client'] + + # Create API client. + api_client = ingatesdk.Client(client_params['version'], + client_params['scheme'], + client_params['address'], + client_params['username'], + client_params['password'], + port=client_params['port'], + timeout=client_params['timeout']) + + # Check if we should skip SSL Certificate verification. + verify_ssl = client_params.get('validate_certs') + if not verify_ssl: + api_client.skip_verify_certificate() + + # Return the client. + return api_client + + +def is_ingatesdk_installed(module): + if not HAS_INGATESDK: + module.fail_json(msg="The Ingate Python SDK module is required for this module.") diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/ironware.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/ironware.py new file mode 100644 index 00000000..bb2bfdc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ironware/ironware.py @@ -0,0 +1,114 @@ +# +# Copyright (c) 2017, Paul Baker +# +# 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection +from ansible.module_utils.connection import Connection, exec_command + +_DEVICE_CONFIG = None +_CONNECTION = None + +ironware_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), + 'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True), + 'timeout': dict(type='int'), +} + +ironware_argument_spec = { + 'provider': dict(type='dict', options=ironware_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network') +} + +command_spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() +} + + +def get_provider_argspec(): + return ironware_provider_spec + + +def check_args(module): + pass + + +def get_connection(module): + global _CONNECTION + if _CONNECTION: + return _CONNECTION + _CONNECTION = Connection(module._socket_path) + + return _CONNECTION + + +def to_commands(module, commands): + if not isinstance(commands, list): + raise AssertionError('argument must be of type ') + + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + module.warn('only show commands are supported when using check ' + 'mode, not executing `%s`' % item['command']) + + return commands + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + + commands = to_commands(module, to_list(commands)) + + responses = list() + + for cmd in commands: + out = connection.get(**cmd) + responses.append(to_text(out, errors='surrogate_then_replace')) + + return responses + + +def get_config(module, source='running', flags=None): + global _DEVICE_CONFIG + if source == 'running' and flags is None and _DEVICE_CONFIG is not None: + return _DEVICE_CONFIG + else: + conn = get_connection(module) + out = conn.get_config(source=source, flags=flags) + cfg = to_text(out, errors='surrogate_then_replace').strip() + if source == 'running' and flags is None: + _DEVICE_CONFIG = cfg + return cfg + + +def load_config(module, config): + conn = get_connection(module) + conn.edit_config(config) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/netscaler.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/netscaler.py new file mode 100644 index 00000000..02e9a90c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netscaler/netscaler.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +import re +import sys + +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils._text import to_native + + +class ConfigProxy(object): + + def __init__(self, actual, client, attribute_values_dict, readwrite_attrs, transforms=None, readonly_attrs=None, immutable_attrs=None, json_encodes=None): + transforms = {} if transforms is None else transforms + readonly_attrs = [] if readonly_attrs is None else readonly_attrs + immutable_attrs = [] if immutable_attrs is None else immutable_attrs + json_encodes = [] if json_encodes is None else json_encodes + + # Actual config object from nitro sdk + self.actual = actual + + # nitro client + self.client = client + + # ansible attribute_values_dict + self.attribute_values_dict = attribute_values_dict + + self.readwrite_attrs = readwrite_attrs + self.readonly_attrs = readonly_attrs + self.immutable_attrs = immutable_attrs + self.json_encodes = json_encodes + self.transforms = transforms + + self.attribute_values_processed = {} + for attribute, value in self.attribute_values_dict.items(): + if value is None: + continue + if attribute in transforms: + for transform in self.transforms[attribute]: + if transform == 'bool_yes_no': + if value is True: + value = 'YES' + elif value is False: + value = 'NO' + elif transform == 'bool_on_off': + if value is True: + value = 'ON' + elif value is False: + value = 'OFF' + elif callable(transform): + value = transform(value) + else: + raise Exception('Invalid transform %s' % transform) + self.attribute_values_processed[attribute] = value + + self._copy_attributes_to_actual() + + def _copy_attributes_to_actual(self): + for attribute in self.readwrite_attrs: + if attribute in self.attribute_values_processed: + attribute_value = self.attribute_values_processed[attribute] + + if attribute_value is None: + continue + + # Fallthrough + if attribute in self.json_encodes: + attribute_value = json.JSONEncoder().encode(attribute_value).strip('"') + setattr(self.actual, attribute, attribute_value) + + def __getattr__(self, name): + if name in self.attribute_values_dict: + return self.attribute_values_dict[name] + else: + raise AttributeError('No attribute %s found' % name) + + def add(self): + self.actual.__class__.add(self.client, self.actual) + + def update(self): + return self.actual.__class__.update(self.client, self.actual) + + def delete(self): + self.actual.__class__.delete(self.client, self.actual) + + def get(self, *args, **kwargs): + result = self.actual.__class__.get(self.client, *args, **kwargs) + + return result + + def has_equal_attributes(self, other): + if self.diff_object(other) == {}: + return True + else: + return False + + def diff_object(self, other): + diff_dict = {} + for attribute in self.attribute_values_processed: + # Skip readonly attributes + if attribute not in self.readwrite_attrs: + continue + + # Skip attributes not present in module arguments + if self.attribute_values_processed[attribute] is None: + continue + + # Check existence + if hasattr(other, attribute): + attribute_value = getattr(other, attribute) + else: + diff_dict[attribute] = 'missing from other' + continue + + # Compare values + param_type = self.attribute_values_processed[attribute].__class__ + if attribute_value is None or param_type(attribute_value) != self.attribute_values_processed[attribute]: + str_tuple = ( + type(self.attribute_values_processed[attribute]), + self.attribute_values_processed[attribute], + type(attribute_value), + attribute_value, + ) + diff_dict[attribute] = 'difference. ours: (%s) %s other: (%s) %s' % str_tuple + return diff_dict + + def get_actual_rw_attributes(self, filter='name'): + if self.actual.__class__.count_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) == 0: + return {} + server_list = self.actual.__class__.get_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) + actual_instance = server_list[0] + ret_val = {} + for attribute in self.readwrite_attrs: + if not hasattr(actual_instance, attribute): + continue + ret_val[attribute] = getattr(actual_instance, attribute) + return ret_val + + def get_actual_ro_attributes(self, filter='name'): + if self.actual.__class__.count_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) == 0: + return {} + server_list = self.actual.__class__.get_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) + actual_instance = server_list[0] + ret_val = {} + for attribute in self.readonly_attrs: + if not hasattr(actual_instance, attribute): + continue + ret_val[attribute] = getattr(actual_instance, attribute) + return ret_val + + def get_missing_rw_attributes(self): + return list(set(self.readwrite_attrs) - set(self.get_actual_rw_attributes().keys())) + + def get_missing_ro_attributes(self): + return list(set(self.readonly_attrs) - set(self.get_actual_ro_attributes().keys())) + + +def get_immutables_intersection(config_proxy, keys): + immutables_set = set(config_proxy.immutable_attrs) + keys_set = set(keys) + # Return list of sets' intersection + return list(immutables_set & keys_set) + + +def ensure_feature_is_enabled(client, feature_str): + enabled_features = client.get_enabled_features() + + if enabled_features is None: + enabled_features = [] + + if feature_str not in enabled_features: + client.enable_features(feature_str) + client.save_config() + + +def get_nitro_client(module): + from nssrc.com.citrix.netscaler.nitro.service.nitro_service import nitro_service + + client = nitro_service(module.params['nsip'], module.params['nitro_protocol']) + client.set_credential(module.params['nitro_user'], module.params['nitro_pass']) + client.timeout = float(module.params['nitro_timeout']) + client.certvalidation = module.params['validate_certs'] + return client + + +netscaler_common_arguments = dict( + nsip=dict( + required=True, + fallback=(env_fallback, ['NETSCALER_NSIP']), + ), + nitro_user=dict( + required=True, + fallback=(env_fallback, ['NETSCALER_NITRO_USER']), + no_log=True + ), + nitro_pass=dict( + required=True, + fallback=(env_fallback, ['NETSCALER_NITRO_PASS']), + no_log=True + ), + nitro_protocol=dict( + choices=['http', 'https'], + fallback=(env_fallback, ['NETSCALER_NITRO_PROTOCOL']), + default='http' + ), + validate_certs=dict( + default=True, + type='bool' + ), + nitro_timeout=dict(default=310, type='float'), + state=dict( + choices=[ + 'present', + 'absent', + ], + default='present', + ), + save_config=dict( + type='bool', + default=True, + ), +) + + +loglines = [] + + +def complete_missing_attributes(actual, attrs_list, fill_value=None): + for attribute in attrs_list: + if not hasattr(actual, attribute): + setattr(actual, attribute, fill_value) + + +def log(msg): + loglines.append(msg) + + +def get_ns_version(client): + from nssrc.com.citrix.netscaler.nitro.resource.config.ns.nsversion import nsversion + result = nsversion.get(client) + m = re.match(r'^.*NS(\d+)\.(\d+).*$', result[0].version) + if m is None: + return None + else: + return int(m.group(1)), int(m.group(2)) + + +def get_ns_hardware(client): + from nssrc.com.citrix.netscaler.nitro.resource.config.ns.nshardware import nshardware + result = nshardware.get(client) + return result + + +def monkey_patch_nitro_api(): + + from nssrc.com.citrix.netscaler.nitro.resource.base.Json import Json + + def new_resource_to_string_convert(self, resrc): + # Line below is the actual patch + dict_valid_values = dict((k.replace('_', '', 1), v) for k, v in resrc.__dict__.items() if v) + return json.dumps(dict_valid_values) + Json.resource_to_string_convert = new_resource_to_string_convert + + from nssrc.com.citrix.netscaler.nitro.util.nitro_util import nitro_util + + @classmethod + def object_to_string_new(cls, obj): + output = [] + flds = obj.__dict__ + for k, v in ((k.replace('_', '', 1), v) for k, v in flds.items() if v): + if isinstance(v, bool): + output.append('"%s":%s' % (k, v)) + elif isinstance(v, (binary_type, text_type)): + v = to_native(v, errors='surrogate_or_strict') + output.append('"%s":"%s"' % (k, v)) + elif isinstance(v, int): + output.append('"%s":"%s"' % (k, v)) + return ','.join(output) + + @classmethod + def object_to_string_withoutquotes_new(cls, obj): + output = [] + flds = obj.__dict__ + for k, v in ((k.replace('_', '', 1), v) for k, v in flds.items() if v): + if isinstance(v, (int, bool)): + output.append('%s:%s' % (k, v)) + elif isinstance(v, (binary_type, text_type)): + v = to_native(v, errors='surrogate_or_strict') + output.append('%s:%s' % (k, cls.encode(v))) + return ','.join(output) + + nitro_util.object_to_string = object_to_string_new + nitro_util.object_to_string_withoutquotes = object_to_string_withoutquotes_new diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/netvisor.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/netvisor.py new file mode 100644 index 00000000..0be1af2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/netvisor.py @@ -0,0 +1,59 @@ +# Copyright: (c) 2018, Pluribus Networks +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError +from ansible.module_utils.connection import exec_command + + +def get_connection(module): + if hasattr(module, '_nvos_connection'): + return module._nvos_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._nvos_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._nvos_connection + + +def get_capabilities(module): + if hasattr(module, '_nvos_capabilities'): + return module._nvos_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._nvos_capabilities = json.loads(capabilities) + return module._nvos_capabilities + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + commands = to_commands(module, to_list(commands)) + for cmd in commands: + cmd = module.jsonify(cmd) + rc, out, err = exec_command(module, cmd) + if check_rc and rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc) + responses = (to_text(out, errors='surrogate_or_strict')) + + return rc, out, err diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/pn_nvos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/pn_nvos.py new file mode 100644 index 00000000..bdd55328 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/netvisor/pn_nvos.py @@ -0,0 +1,66 @@ +# Copyright: (c) 2018, Pluribus Networks +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def pn_cli(module, switch=None, username=None, password=None, switch_local=None): + """ + Method to generate the cli portion to launch the Netvisor cli. + :param module: The Ansible module to fetch username and password. + :return: The cli string for further processing. + """ + + cli = '' + + if username and password: + cli += '--user "%s":"%s" ' % (username, password) + if switch: + cli += ' switch ' + switch + if switch_local: + cli += ' switch-local ' + + return cli + + +def booleanArgs(arg, trueString, falseString): + if arg is True: + return " %s " % trueString + elif arg is False: + return " %s " % falseString + else: + return "" + + +def run_cli(module, cli, state_map): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param state_map: Provides state of the command. + :param module: The Ansible module to fetch command + """ + state = module.params['state'] + command = state_map[state] + + result, out, err = run_commands(module, cli) + + results = dict( + command=cli, + msg="%s operation completed" % cli, + changed=True + ) + # Response in JSON format + if result != 0: + module.exit_json( + command=cli, + msg="%s operation failed" % cli, + changed=False + ) + + module.exit_json(**results) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/nos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/nos.py new file mode 100644 index 00000000..3355befa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nos/nos.py @@ -0,0 +1,164 @@ +# +# (c) 2018 Extreme Networks 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.module_utils.connection import Connection, ConnectionError + + +def get_connection(module): + """Get switch connection + + Creates reusable SSH connection to the switch described in a given module. + + Args: + module: A valid AnsibleModule instance. + + Returns: + An instance of `ansible.module_utils.connection.Connection` with a + connection to the switch described in the provided module. + + Raises: + AnsibleConnectionFailure: An error occurred connecting to the device + """ + if hasattr(module, 'nos_connection'): + return module.nos_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module.nos_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module.nos_connection + + +def get_capabilities(module): + """Get switch capabilities + + Collects and returns a python object with the switch capabilities. + + Args: + module: A valid AnsibleModule instance. + + Returns: + A dictionary containing the switch capabilities. + """ + if hasattr(module, 'nos_capabilities'): + return module.nos_capabilities + + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module.nos_capabilities = json.loads(capabilities) + return module.nos_capabilities + + +def run_commands(module, commands): + """Run command list against connection. + + Get new or previously used connection and send commands to it one at a time, + collecting response. + + Args: + module: A valid AnsibleModule instance. + commands: Iterable of command strings. + + Returns: + A list of output strings. + """ + responses = list() + connection = get_connection(module) + + for cmd in to_list(commands): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + else: + command = cmd + prompt = None + answer = None + + try: + out = connection.get(command, prompt, answer) + out = to_text(out, errors='surrogate_or_strict') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + except UnicodeError: + module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) + + responses.append(out) + + return responses + + +def get_config(module): + """Get switch configuration + + Gets the described device's current configuration. If a configuration has + already been retrieved it will return the previously obtained configuration. + + Args: + module: A valid AnsibleModule instance. + + Returns: + A string containing the configuration. + """ + if not hasattr(module, 'device_configs'): + module.device_configs = {} + elif module.device_configs != {}: + return module.device_configs + + connection = get_connection(module) + try: + out = connection.get_config() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + cfg = to_text(out, errors='surrogate_then_replace').strip() + module.device_configs = cfg + return cfg + + +def load_config(module, commands): + """Apply a list of commands to a device. + + Given a list of commands apply them to the device to modify the + configuration in bulk. + + Args: + module: A valid AnsibleModule instance. + commands: Iterable of command strings. + + Returns: + None + """ + connection = get_connection(module) + + try: + resp = connection.edit_config(commands) + return resp.get('response') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/nso.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/nso.py new file mode 100644 index 00000000..718926d9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/nso/nso.py @@ -0,0 +1,825 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Cisco and/or its affiliates. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.urls import open_url +from ansible.module_utils._text import to_text + +import json +import re +import socket + +try: + unicode + HAVE_UNICODE = True +except NameError: + unicode = str + HAVE_UNICODE = False + + +nso_argument_spec = dict( + url=dict(type='str', required=True), + username=dict(type='str', required=True, fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + password=dict(type='str', required=True, no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD'])), + timeout=dict(type='int', default=300), + validate_certs=dict(type='bool', default=False) +) + + +class State(object): + SET = 'set' + PRESENT = 'present' + ABSENT = 'absent' + CHECK_SYNC = 'check-sync' + DEEP_CHECK_SYNC = 'deep-check-sync' + IN_SYNC = 'in-sync' + DEEP_IN_SYNC = 'deep-in-sync' + + SYNC_STATES = ('check-sync', 'deep-check-sync', 'in-sync', 'deep-in-sync') + + +class ModuleFailException(Exception): + def __init__(self, message): + super(ModuleFailException, self).__init__(message) + self.message = message + + +class NsoException(Exception): + def __init__(self, message, error): + super(NsoException, self).__init__(message) + self.message = message + self.error = error + + +class JsonRpc(object): + def __init__(self, url, timeout, validate_certs): + self._url = url + self._timeout = timeout + self._validate_certs = validate_certs + self._id = 0 + self._trans = {} + self._headers = {'Content-Type': 'application/json'} + self._conn = None + self._system_settings = {} + + def login(self, user, passwd): + payload = { + 'method': 'login', + 'params': {'user': user, 'passwd': passwd} + } + resp, resp_json = self._call(payload) + self._headers['Cookie'] = resp.headers['set-cookie'] + + def logout(self): + payload = {'method': 'logout', 'params': {}} + self._call(payload) + + def get_system_setting(self, setting): + if setting not in self._system_settings: + payload = {'method': 'get_system_setting', 'params': {'operation': setting}} + resp, resp_json = self._call(payload) + self._system_settings[setting] = resp_json['result'] + return self._system_settings[setting] + + def new_trans(self, **kwargs): + payload = {'method': 'new_trans', 'params': kwargs} + resp, resp_json = self._call(payload) + return resp_json['result']['th'] + + def get_trans(self, mode): + if mode not in self._trans: + th = self.new_trans(mode=mode) + self._trans[mode] = th + return self._trans[mode] + + def delete_trans(self, th): + payload = {'method': 'delete_trans', 'params': {'th': th}} + resp, resp_json = self._call(payload) + self._maybe_delete_trans(th) + + def validate_trans(self, th): + payload = {'method': 'validate_trans', 'params': {'th': th}} + resp, resp_json = self._write_call(payload) + return resp_json['result'] + + def get_trans_changes(self, th): + payload = {'method': 'get_trans_changes', 'params': {'th': th}} + resp, resp_json = self._write_call(payload) + return resp_json['result']['changes'] + + def validate_commit(self, th): + payload = {'method': 'validate_commit', 'params': {'th': th}} + resp, resp_json = self._write_call(payload) + return resp_json['result'].get('warnings', []) + + def commit(self, th): + payload = {'method': 'commit', 'params': {'th': th}} + resp, resp_json = self._write_call(payload) + if len(resp_json['result']) == 0: + self._maybe_delete_trans(th) + return resp_json['result'] + + def get_schema(self, **kwargs): + payload = {'method': 'get_schema', 'params': kwargs} + resp, resp_json = self._maybe_write_call(payload) + return resp_json['result'] + + def get_module_prefix_map(self, path=None): + if path is None: + payload = {'method': 'get_module_prefix_map', 'params': {}} + resp, resp_json = self._call(payload) + else: + payload = {'method': 'get_module_prefix_map', 'params': {'path': path}} + resp, resp_json = self._maybe_write_call(payload) + return resp_json['result'] + + def get_value(self, path): + payload = { + 'method': 'get_value', + 'params': {'path': path} + } + resp, resp_json = self._read_call(payload) + return resp_json['result'] + + def exists(self, path): + payload = {'method': 'exists', 'params': {'path': path}} + try: + resp, resp_json = self._read_call(payload) + return resp_json['result']['exists'] + except NsoException as ex: + # calling exists on a sub-list when the parent list does + # not exists will cause data.not_found errors on recent + # NSO + if 'type' in ex.error and ex.error['type'] == 'data.not_found': + return False + raise + + def create(self, th, path): + payload = {'method': 'create', 'params': {'th': th, 'path': path}} + self._write_call(payload) + + def delete(self, th, path): + payload = {'method': 'delete', 'params': {'th': th, 'path': path}} + self._write_call(payload) + + def set_value(self, th, path, value): + payload = { + 'method': 'set_value', + 'params': {'th': th, 'path': path, 'value': value} + } + resp, resp_json = self._write_call(payload) + return resp_json['result'] + + def show_config(self, path, operational=False): + payload = { + 'method': 'show_config', + 'params': { + 'path': path, + 'result_as': 'json', + 'with_oper': operational} + } + resp, resp_json = self._read_call(payload) + return resp_json['result'] + + def query(self, xpath, fields): + payload = { + 'method': 'query', + 'params': { + 'xpath_expr': xpath, + 'selection': fields + } + } + resp, resp_json = self._read_call(payload) + return resp_json['result']['results'] + + def run_action(self, th, path, params=None): + if params is None: + params = {} + + if is_version(self, [(4, 5), (4, 4, 3)]): + result_format = 'json' + else: + result_format = 'normal' + + payload = { + 'method': 'run_action', + 'params': { + 'format': result_format, + 'path': path, + 'params': params + } + } + if th is None: + resp, resp_json = self._read_call(payload) + else: + payload['params']['th'] = th + resp, resp_json = self._call(payload) + + if result_format == 'normal': + # this only works for one-level results, list entries, + # containers etc will have / in their name. + result = {} + for info in resp_json['result']: + result[info['name']] = info['value'] + else: + result = resp_json['result'] + + return result + + def _call(self, payload): + self._id += 1 + if 'id' not in payload: + payload['id'] = self._id + + if 'jsonrpc' not in payload: + payload['jsonrpc'] = '2.0' + + data = json.dumps(payload) + try: + resp = open_url( + self._url, timeout=self._timeout, + method='POST', data=data, headers=self._headers, + validate_certs=self._validate_certs) + if resp.code != 200: + raise NsoException( + 'NSO returned HTTP code {0}, expected 200'.format(resp.status), {}) + except socket.timeout: + raise NsoException('request timed out against NSO at {0}'.format(self._url), {}) + + resp_body = resp.read() + resp_json = json.loads(resp_body) + + if 'error' in resp_json: + self._handle_call_error(payload, resp_json) + return resp, resp_json + + def _handle_call_error(self, payload, resp_json): + method = payload['method'] + + error = resp_json['error'] + error_type = error['type'][len('rpc.method.'):] + if error_type in ('unexpected_params', + 'unknown_params_value', + 'invalid_params', + 'invalid_params_type', + 'data_not_found'): + key = error['data']['param'] + error_type_s = error_type.replace('_', ' ') + if key == 'path': + msg = 'NSO {0} {1}. path = {2}'.format( + method, error_type_s, payload['params']['path']) + else: + path = payload['params'].get('path', 'unknown') + msg = 'NSO {0} {1}. path = {2}. {3} = {4}'.format( + method, error_type_s, path, key, payload['params'][key]) + else: + msg = 'NSO {0} returned JSON-RPC error: {1}'.format(method, error) + + raise NsoException(msg, error) + + def _read_call(self, payload): + if 'th' not in payload['params']: + payload['params']['th'] = self.get_trans(mode='read') + return self._call(payload) + + def _write_call(self, payload): + if 'th' not in payload['params']: + payload['params']['th'] = self.get_trans(mode='read_write') + return self._call(payload) + + def _maybe_write_call(self, payload): + if 'read_write' in self._trans: + return self._write_call(payload) + else: + return self._read_call(payload) + + def _maybe_delete_trans(self, th): + for mode in ('read', 'read_write'): + if th == self._trans.get(mode, None): + del self._trans[mode] + + +class ValueBuilder(object): + PATH_RE = re.compile('{[^}]*}') + PATH_RE_50 = re.compile('{[^}]*}$') + + class Value(object): + __slots__ = ['path', 'tag_path', 'state', 'value', 'deps'] + + def __init__(self, path, state, value, deps): + self.path = path + self.tag_path = ValueBuilder.PATH_RE.sub('', path) + self.state = state + self.value = value + self.deps = deps + + # nodes can depend on themselves + if self.tag_path in self.deps: + self.deps.remove(self.tag_path) + + def __lt__(self, rhs): + l_len = len(self.path.split('/')) + r_len = len(rhs.path.split('/')) + if l_len == r_len: + return self.path.__lt__(rhs.path) + return l_len < r_len + + def __str__(self): + return 'Value'.format( + self.path, self.state, self.value) + + class ValueIterator(object): + def __init__(self, client, values, delayed_values): + self._client = client + self._values = values + self._delayed_values = delayed_values + self._pos = 0 + + def __iter__(self): + return self + + def __next__(self): + return self.next() + + def next(self): + if self._pos >= len(self._values): + if len(self._delayed_values) == 0: + raise StopIteration() + + builder = ValueBuilder(self._client, delay=False) + for (parent, maybe_qname, value) in self._delayed_values: + builder.build(parent, maybe_qname, value) + del self._delayed_values[:] + self._values.extend(builder.values) + + return self.next() + + value = self._values[self._pos] + self._pos += 1 + return value + + def __init__(self, client, mode='config', delay=None): + self._client = client + self._mode = mode + self._schema_cache = {} + self._module_prefix_map_cache = {} + self._values = [] + self._values_dirty = False + self._delay = delay is None and mode == 'config' and is_version(self._client, [(5, 0)]) + self._delayed_values = [] + + def build(self, parent, maybe_qname, value, schema=None): + qname, name = self.get_prefix_name(parent, maybe_qname) + if name is None: + path = parent + else: + path = '{0}/{1}'.format(parent, qname) + + if schema is None: + schema = self._get_schema(path) + + if self._delay and schema.get('is_mount_point', False): + # delay conversion of mounted values, required to get + # shema information on 5.0 and later. + self._delayed_values.append((parent, maybe_qname, value)) + elif self._is_leaf_list(schema) and is_version(self._client, [(4, 5)]): + self._build_leaf_list(path, schema, value) + elif self._is_leaf(schema): + deps = schema.get('deps', []) + if self._is_empty_leaf(schema): + exists = self._client.exists(path) + if exists and value != [None]: + self._add_value(path, State.ABSENT, None, deps) + elif not exists and value == [None]: + self._add_value(path, State.PRESENT, None, deps) + else: + if maybe_qname is None: + 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(parent, v) for v in value]] + else: + value, t_value = self.get_prefix_name(parent, value) + self._add_value(path, State.SET, value, deps) + elif isinstance(value, dict): + self._build_dict(path, schema, value) + elif isinstance(value, list): + self._build_list(path, schema, value) + else: + raise ModuleFailException( + 'unsupported schema {0} at {1}'.format( + schema['kind'], path)) + + @property + def values(self): + if self._values_dirty: + self._values = ValueBuilder.sort_values(self._values) + self._values_dirty = False + + return ValueBuilder.ValueIterator(self._client, self._values, self._delayed_values) + + @staticmethod + def sort_values(values): + class N(object): + def __init__(self, v): + self.tmp_mark = False + self.mark = False + self.v = v + + sorted_values = [] + nodes = [N(v) for v in sorted(values)] + + def get_node(tag_path): + return next((m for m in nodes + if m.v.tag_path == tag_path), None) + + def is_cycle(n, dep, visited): + visited.add(n.v.tag_path) + if dep in visited: + return True + + dep_n = get_node(dep) + if dep_n is not None: + for sub_dep in dep_n.v.deps: + if is_cycle(dep_n, sub_dep, visited): + return True + + return False + + # check for dependency cycles, remove if detected. sort will + # not be 100% but allows for a best-effort to work around + # issue in NSO. + for n in nodes: + for dep in n.v.deps: + if is_cycle(n, dep, set()): + n.v.deps.remove(dep) + + def visit(n): + if n.tmp_mark: + return False + if not n.mark: + n.tmp_mark = True + for m in nodes: + if m.v.tag_path in n.v.deps: + if not visit(m): + return False + + n.tmp_mark = False + n.mark = True + + sorted_values.insert(0, n.v) + + return True + + n = next((n for n in nodes if not n.mark), None) + while n is not None: + visit(n) + n = next((n for n in nodes if not n.mark), None) + + return sorted_values[::-1] + + 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(path, dict_key) + if dict_key in ('__state', ) or name in keys: + continue + + child_schema = self._find_child(path, schema, qname) + self.build(path, dict_key, dict_value, child_schema) + + def _build_leaf_list(self, path, schema, value): + deps = schema.get('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(path, 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(path, 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', []) + for entry in value: + entry_key = self._build_key(path, entry, schema['key']) + entry_path = '{0}{{{1}}}'.format(path, entry_key) + entry_state = entry.get('__state', 'present') + entry_exists = self._client.exists(entry_path) + + if entry_state == 'absent': + if entry_exists: + self._add_value(entry_path, State.ABSENT, None, deps) + else: + if not entry_exists: + self._add_value(entry_path, State.PRESENT, None, deps) + if entry_state in State.SYNC_STATES: + self._add_value(entry_path, entry_state, None, deps) + + self.build(entry_path, None, entry) + + def _build_key(self, path, entry, schema_keys): + key_parts = [] + for key in schema_keys: + value = entry.get(key, None) + if value is None: + raise ModuleFailException( + 'required leaf {0} in {1} not set in data'.format( + key, path)) + + value_type = self._get_child_type(path, key) + if 'identityref' in value_type: + value, t_value = self.get_prefix_name(path, value) + key_parts.append(self._quote_key(value)) + return ' '.join(key_parts) + + def _quote_key(self, key): + if isinstance(key, bool): + return key and 'true' or 'false' + + q_key = [] + for c in str(key): + if c in ('{', '}', "'", '\\'): + q_key.append('\\') + q_key.append(c) + q_key = ''.join(q_key) + if ' ' in q_key: + return '"{0}"'.format(q_key) + return q_key + + def _find_child(self, path, schema, qname): + if 'children' not in schema: + schema = self._get_schema(path) + + # look for the qualified name if : is in the name + child_schema = self._get_child(schema, qname) + if child_schema is not None: + return child_schema + + # no child was found, look for a choice with a child matching + for child_schema in schema['children']: + if child_schema['kind'] != 'choice': + continue + choice_child_schema = self._get_choice_child(child_schema, qname) + if choice_child_schema is not None: + return choice_child_schema + + raise ModuleFailException( + 'no child in {0} with name {1}. children {2}'.format( + path, qname, ','.join((c.get('qname', c.get('name', None)) for c in schema['children'])))) + + def _add_value(self, path, state, value, deps): + self._values.append(ValueBuilder.Value(path, state, value, deps)) + self._values_dirty = True + + def get_prefix_name(self, path, qname): + if not isinstance(qname, (str, unicode)): + return qname, None + if ':' not in qname: + return qname, qname + + module_prefix_map = self._get_module_prefix_map(path) + module, name = qname.split(':', 1) + if module not in module_prefix_map: + raise ModuleFailException( + 'no module mapping for module {0}. loaded modules {1}'.format( + module, ','.join(sorted(module_prefix_map.keys())))) + + return '{0}:{1}'.format(module_prefix_map[module], name), name + + def _get_schema(self, path): + return self._ensure_schema_cached(path)['data'] + + def _get_child_type(self, parent_path, key): + all_schema = self._ensure_schema_cached(parent_path) + 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) + + 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'] + meta = all_schema['meta'] + + if self._is_leaf(schema): + def get_type(meta, curr_type): + if curr_type.get('primitive', False): + return [curr_type['name']] + if 'namespace' in curr_type: + curr_type_key = '{0}:{1}'.format( + curr_type['namespace'], curr_type['name']) + type_info = meta['types'][curr_type_key][-1] + return get_type(meta, type_info) + if 'leaf_type' in curr_type: + return get_type(meta, curr_type['leaf_type'][-1]) + if 'union' in curr_type: + union_types = [] + for union_type in curr_type['union']: + union_types.extend(get_type(meta, union_type[-1])) + return union_types + return [curr_type.get('name', 'unknown')] + + return get_type(meta, schema['type']) + return None + + def _ensure_schema_cached(self, path): + if not self._delay and is_version(self._client, [(5, 0)]): + # newer versions of NSO support multiple different schemas + # for different devices, thus the device is required to + # look up the schema. Remove the key entry to get schema + # logic working ok. + path = ValueBuilder.PATH_RE_50.sub('', path) + else: + path = ValueBuilder.PATH_RE.sub('', path) + + if path not in self._schema_cache: + schema = self._client.get_schema(path=path, levels=1) + self._schema_cache[path] = schema + return self._schema_cache[path] + + def _get_module_prefix_map(self, path): + # newer versions of NSO support multiple mappings from module + # to prefix depending on which device is used. + if path != '' and is_version(self._client, [(5, 0)]): + if path not in self._module_prefix_map_cache: + self._module_prefix_map_cache[path] = self._client.get_module_prefix_map(path) + return self._module_prefix_map_cache[path] + + if '' not in self._module_prefix_map_cache: + self._module_prefix_map_cache[''] = self._client.get_module_prefix_map() + return self._module_prefix_map_cache[''] + + def _get_child(self, schema, qname): + # no child specified, return parent + if qname is None: + return schema + + name_key = ':' in qname and 'qname' or 'name' + return next((c for c in schema['children'] + if c.get(name_key, None) == qname), None) + + def _get_choice_child(self, schema, qname): + name_key = ':' in qname and 'qname' or 'name' + for child_case in schema['cases']: + # look for direct child + choice_child_schema = next( + (c for c in child_case['children'] + if c.get(name_key, None) == qname), None) + if choice_child_schema is not None: + return choice_child_schema + + # look for nested choice + for child_schema in child_case['children']: + if child_schema['kind'] != 'choice': + continue + choice_child_schema = self._get_choice_child(child_schema, qname) + if choice_child_schema is not None: + return choice_child_schema + return None + + def _is_leaf_list(self, schema): + return schema.get('kind', None) == 'leaf-list' + + def _is_leaf(self, schema): + # still checking for leaf-list here to be compatible with pre + # 4.5 versions of NSO. + return schema.get('kind', None) in ('key', 'leaf', 'leaf-list') + + def _is_empty_leaf(self, schema): + return (schema.get('kind', None) == 'leaf' and + schema['type'].get('primitive', False) and + schema['type'].get('name', '') == 'empty') + + +def connect(params): + client = JsonRpc(params['url'], + params['timeout'], + params['validate_certs']) + client.login(params['username'], params['password']) + return client + + +def verify_version(client, required_versions): + version_str = client.get_system_setting('version') + if not verify_version_str(version_str, required_versions): + supported_versions = ', '.join( + ['.'.join([str(p) for p in required_version]) + for required_version in required_versions]) + raise ModuleFailException( + 'unsupported NSO version {0}. {1} or later supported'.format( + version_str, supported_versions)) + + +def is_version(client, required_versions): + version_str = client.get_system_setting('version') + return verify_version_str(version_str, required_versions) + + +def verify_version_str(version_str, required_versions): + version_str = re.sub('_.*', '', version_str) + + version = [int(p) for p in version_str.split('.')] + if len(version) < 2: + raise ModuleFailException( + 'unsupported NSO version format {0}'.format(version_str)) + + def check_version(required_version, version): + for pos in range(len(required_version)): + if pos >= len(version): + return False + if version[pos] > required_version[pos]: + return True + if version[pos] < required_version[pos]: + return False + return True + + for required_version in required_versions: + if check_version(required_version, version): + return True + return False + + +def normalize_value(expected_value, value, key): + if value is None: + return None + if (isinstance(expected_value, bool) and + isinstance(value, (str, unicode))): + return value == 'true' + if isinstance(expected_value, int): + try: + return int(value) + except TypeError: + raise ModuleFailException( + 'returned value {0} for {1} is not a valid integer'.format( + key, value)) + if isinstance(expected_value, float): + try: + return float(value) + except TypeError: + raise ModuleFailException( + 'returned value {0} for {1} is not a valid float'.format( + key, value)) + if isinstance(expected_value, (list, tuple)): + if not isinstance(value, (list, tuple)): + raise ModuleFailException( + 'returned value {0} for {1} is not a list'.format(value, key)) + if len(expected_value) != len(value): + raise ModuleFailException( + 'list length mismatch for {0}'.format(key)) + + normalized_value = [] + for i in range(len(expected_value)): + normalized_value.append( + normalize_value(expected_value[i], value[i], '{0}[{1}]'.format(key, i))) + return normalized_value + + if isinstance(expected_value, dict): + if not isinstance(value, dict): + raise ModuleFailException( + 'returned value {0} for {1} is not a dict'.format(value, key)) + if len(expected_value) != len(value): + raise ModuleFailException( + 'dict length mismatch for {0}'.format(key)) + + normalized_value = {} + for k in expected_value.keys(): + n_k = normalize_value(k, k, '{0}[{1}]'.format(key, k)) + if n_k not in value: + raise ModuleFailException('missing {0} in value'.format(n_k)) + normalized_value[n_k] = normalize_value(expected_value[k], value[k], '{0}[{1}]'.format(key, k)) + return normalized_value + + if HAVE_UNICODE: + if isinstance(expected_value, unicode) and isinstance(value, str): + return value.decode('utf-8') + if isinstance(expected_value, str) and isinstance(value, unicode): + return value.encode('utf-8') + else: + if hasattr(expected_value, 'encode') and hasattr(value, 'decode'): + return value.decode('utf-8') + if hasattr(expected_value, 'decode') and hasattr(value, 'encode'): + return value.encode('utf-8') + + return value diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/ordnance.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/ordnance.py new file mode 100644 index 00000000..84194c1c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/ordnance/ordnance.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +_DEVICE_CONFIGS = {} + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = 'show running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + rc, out, err = module.exec_command(cmd) + if rc != 0: + module.fail_json(msg='unable to retrieve current config', stderr=err) + cfg = str(out).strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/panos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/panos.py new file mode 100644 index 00000000..1cc9ec72 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/panos/panos.py @@ -0,0 +1,418 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2018 Palo Alto Networks techbizdev, +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +_MIN_VERSION_ERROR = '{0} version ({1}) < minimum version ({2})' +HAS_PANDEVICE = True +try: + import pandevice + from pandevice.base import PanDevice + from pandevice.firewall import Firewall + from pandevice.panorama import DeviceGroup, Template, TemplateStack + from pandevice.policies import PreRulebase, PostRulebase, Rulebase + from pandevice.device import Vsys + from pandevice.errors import PanDeviceError +except ImportError: + HAS_PANDEVICE = False + + +def _vstr(val): + return '{0}.{1}.{2}'.format(*val) + + +class ConnectionHelper(object): + def __init__(self, min_pandevice_version, min_panos_version, + panorama_error, firewall_error): + """Performs connection initialization and determines params.""" + # Params for AnsibleModule. + self.argument_spec = {} + self.required_one_of = [] + + # Params for pandevice tree construction. + self.vsys = None + self.device_group = None + self.vsys_dg = None + self.rulebase = None + self.template = None + self.template_stack = None + self.vsys_importable = None + self.min_pandevice_version = min_pandevice_version + self.min_panos_version = min_panos_version + self.panorama_error = panorama_error + self.firewall_error = firewall_error + + # The PAN-OS device. + self.device = None + + def get_pandevice_parent(self, module): + """Builds the pandevice object tree, returning the parent object. + + If pandevice is not installed, then module.fail_json() will be + invoked. + + Arguments: + * module(AnsibleModule): the ansible module. + + Returns: + * The parent pandevice object based on the spec given to + get_connection(). + """ + # Sanity check. + if not HAS_PANDEVICE: + module.fail_json(msg='Missing required library "pandevice".') + + # Verify pandevice minimum version. + if self.min_pandevice_version is not None: + pdv = tuple(int(x) for x in pandevice.__version__.split('.')) + if pdv < self.min_pandevice_version: + module.fail_json(msg=_MIN_VERSION_ERROR.format( + 'pandevice', pandevice.__version__, + _vstr(self.min_pandevice_version))) + + pan_device_auth, serial_number = None, None + if module.params['provider'] and module.params['provider']['ip_address']: + pan_device_auth = ( + module.params['provider']['ip_address'], + module.params['provider']['username'], + module.params['provider']['password'], + module.params['provider']['api_key'], + module.params['provider']['port'], + ) + serial_number = module.params['provider']['serial_number'] + elif module.params.get('ip_address', None) is not None: + pan_device_auth = ( + module.params['ip_address'], + module.params['username'], + module.params['password'], + module.params['api_key'], + module.params['port'], + ) + msg = 'Classic provider params are deprecated; use "provider" instead' + module.deprecate(msg, version='2.0.0', collection_name='community.network') # was Ansible 2.12 + else: + module.fail_json(msg='Provider params are required.') + + # Create the connection object. + try: + self.device = PanDevice.create_from_device(*pan_device_auth) + except PanDeviceError as e: + module.fail_json(msg='Failed connection: {0}'.format(e)) + + # Verify PAN-OS minimum version. + if self.min_panos_version is not None: + if self.device._version_info < self.min_panos_version: + module.fail_json(msg=_MIN_VERSION_ERROR.format( + 'PAN-OS', _vstr(self.device._version_info), + _vstr(self.min_panos_version))) + + # Optional: Firewall via Panorama connectivity specified. + if hasattr(self.device, 'refresh_devices') and serial_number: + fw = Firewall(serial=serial_number) + self.device.add(fw) + self.device = fw + + parent = self.device + not_found = '{0} "{1}" is not present.' + pano_mia_param = 'Param "{0}" is required for Panorama but not specified.' + ts_error = 'Specify either the template or the template stack{0}.' + if hasattr(self.device, 'refresh_devices'): + # Panorama connection. + # Error if Panorama is not supported. + if self.panorama_error is not None: + module.fail_json(msg=self.panorama_error) + + # Spec: template stack. + tmpl_required = False + added_template = False + if self.template_stack is not None: + name = module.params[self.template_stack] + if name is not None: + stacks = TemplateStack.refreshall(parent, name_only=True) + for ts in stacks: + if ts.name == name: + parent = ts + added_template = True + break + else: + module.fail_json(msg=not_found.format( + 'Template stack', name, + )) + elif self.template is not None: + tmpl_required = True + else: + module.fail_json(msg=pano_mia_param.format(self.template_stack)) + + # Spec: template. + if self.template is not None: + name = module.params[self.template] + if name is not None: + if added_template: + module.fail_json(msg=ts_error.format(', not both')) + templates = Template.refreshall(parent, name_only=True) + for t in templates: + if t.name == name: + parent = t + break + else: + module.fail_json(msg=not_found.format( + 'Template', name, + )) + elif tmpl_required: + module.fail_json(msg=ts_error.format('')) + else: + module.fail_json(msg=pano_mia_param.format(self.template)) + + # Spec: vsys importable. + vsys_name = self.vsys_importable or self.vsys + if vsys_name is not None: + name = module.params[vsys_name] + if name not in (None, 'shared'): + vo = Vsys(name) + parent.add(vo) + parent = vo + + # Spec: vsys_dg or device_group. + dg_name = self.vsys_dg or self.device_group + if dg_name is not None: + name = module.params[dg_name] + if name not in (None, 'shared'): + groups = DeviceGroup.refreshall(parent, name_only=True) + for dg in groups: + if dg.name == name: + parent = dg + break + else: + module.fail_json(msg=not_found.format( + 'Device group', name, + )) + + # Spec: rulebase. + if self.rulebase is not None: + if module.params[self.rulebase] in (None, 'pre-rulebase'): + rb = PreRulebase() + parent.add(rb) + parent = rb + elif module.params[self.rulebase] == 'rulebase': + rb = Rulebase() + parent.add(rb) + parent = rb + elif module.params[self.rulebase] == 'post-rulebase': + rb = PostRulebase() + parent.add(rb) + parent = rb + else: + module.fail_json(msg=not_found.format( + 'Rulebase', module.params[self.rulebase])) + else: + # Firewall connection. + # Error if firewalls are not supported. + if self.firewall_error is not None: + module.fail_json(msg=self.firewall_error) + + # Spec: vsys or vsys_dg or vsys_importable. + vsys_name = self.vsys_dg or self.vsys or self.vsys_importable + if vsys_name is not None: + parent.vsys = module.params[vsys_name] + + # Spec: rulebase. + if self.rulebase is not None: + rb = Rulebase() + parent.add(rb) + parent = rb + + # Done. + return parent + + +def get_connection(vsys=None, device_group=None, + vsys_dg=None, vsys_importable=None, + rulebase=None, template=None, template_stack=None, + with_classic_provider_spec=False, with_state=True, + argument_spec=None, required_one_of=None, + min_pandevice_version=None, min_panos_version=None, + panorama_error=None, firewall_error=None): + """Returns a helper object that handles pandevice object tree init. + + The `vsys`, `device_group`, `vsys_dg`, `vsys_importable`, `rulebase`, + `template`, and `template_stack` params can be any of the following types: + + * None - do not include this in the spec + * True - use the default param name + * string - use this string for the param name + + The `min_pandevice_version` and `min_panos_version` args expect a 3 element + tuple of ints. For example, `(0, 6, 0)` or `(8, 1, 0)`. + + If you are including template support (by defining either `template` and/or + `template_stack`), and the thing the module is enabling the management of is + an "importable", you should define either `vsys_importable` (whose default + value is None) or `vsys` (whose default value is 'vsys1'). + + Arguments: + vsys: The vsys (default: 'vsys1'). + device_group: Panorama only - The device group (default: 'shared'). + vsys_dg: The param name if vsys and device_group are a shared param. + vsys_importable: Either this or `vsys` should be specified. For: + - Interfaces + - VLANs + - Virtual Wires + - Virtual Routers + rulebase: This is a policy of some sort. + template: Panorama - The template name. + template_stack: Panorama - The template stack name. + with_classic_provider_spec(bool): Include the ip_address, username, + password, api_key, and port params in the base spec, and make the + "provider" param optional. + with_state(bool): Include the standard 'state' param. + argument_spec(dict): The argument spec to mixin with the + generated spec based on the given parameters. + required_one_of(list): List of lists to extend into required_one_of. + min_pandevice_version(tuple): Minimum pandevice version allowed. + min_panos_version(tuple): Minimum PAN-OS version allowed. + panorama_error(str): The error message if the device is Panorama. + firewall_error(str): The error message if the device is a firewall. + + Returns: + ConnectionHelper + """ + helper = ConnectionHelper( + min_pandevice_version, min_panos_version, + panorama_error, firewall_error) + req = [] + spec = { + 'provider': { + 'required': True, + 'type': 'dict', + 'required_one_of': [['password', 'api_key'], ], + 'options': { + 'ip_address': {'required': True}, + 'username': {'default': 'admin'}, + 'password': {'no_log': True}, + 'api_key': {'no_log': True}, + 'port': {'default': 443, 'type': 'int'}, + 'serial_number': {'no_log': True}, + }, + }, + } + + if with_classic_provider_spec: + spec['provider']['required'] = False + spec['provider']['options']['ip_address']['required'] = False + del(spec['provider']['required_one_of']) + spec.update({ + 'ip_address': {'required': False}, + 'username': {'default': 'admin'}, + 'password': {'no_log': True}, + 'api_key': {'no_log': True}, + 'port': {'default': 443, 'type': 'int'}, + }) + req.extend([ + ['provider', 'ip_address'], + ['provider', 'password', 'api_key'], + ]) + + if with_state: + spec['state'] = { + 'default': 'present', + 'choices': ['present', 'absent'], + } + + if vsys_dg is not None: + if isinstance(vsys_dg, bool): + param = 'vsys_dg' + else: + param = vsys_dg + spec[param] = {} + helper.vsys_dg = param + else: + if vsys is not None: + if isinstance(vsys, bool): + param = 'vsys' + else: + param = vsys + spec[param] = {'default': 'vsys1'} + helper.vsys = param + if device_group is not None: + if isinstance(device_group, bool): + param = 'device_group' + else: + param = device_group + spec[param] = {'default': 'shared'} + helper.device_group = param + if vsys_importable is not None: + if vsys is not None: + raise KeyError('Define "vsys" or "vsys_importable", not both.') + if isinstance(vsys_importable, bool): + param = 'vsys' + else: + param = vsys_importable + spec[param] = {} + helper.vsys_importable = param + + if rulebase is not None: + if isinstance(rulebase, bool): + param = 'rulebase' + else: + param = rulebase + spec[param] = { + 'default': None, + 'choices': ['pre-rulebase', 'rulebase', 'post-rulebase'], + } + helper.rulebase = param + + if template is not None: + if isinstance(template, bool): + param = 'template' + else: + param = template + spec[param] = {} + helper.template = param + + if template_stack is not None: + if isinstance(template_stack, bool): + param = 'template_stack' + else: + param = template_stack + spec[param] = {} + helper.template_stack = param + + if argument_spec is not None: + for k in argument_spec.keys(): + if k in spec: + raise KeyError('{0}: key used by connection helper.'.format(k)) + spec[k] = argument_spec[k] + + if required_one_of is not None: + req.extend(required_one_of) + + # Done. + helper.argument_spec = spec + helper.required_one_of = req + return helper diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/routeros.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/routeros.py new file mode 100644 index 00000000..0d9a7c18 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/routeros/routeros.py @@ -0,0 +1,163 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONFIGS = {} + +routeros_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'timeout': dict(type='int') +} +routeros_argument_spec = {} + + +def get_provider_argspec(): + return routeros_provider_spec + + +def get_connection(module): + if hasattr(module, '_routeros_connection'): + return module._routeros_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._routeros_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._routeros_connection + + +def get_capabilities(module): + if hasattr(module, '_routeros_capabilities'): + return module._routeros_capabilities + + try: + capabilities = Connection(module._socket_path).get_capabilities() + module._routeros_capabilities = json.loads(capabilities) + return module._routeros_capabilities + except ConnectionError as exc: + module.fail_json(msg=to_native(exc, errors='surrogate_then_replace')) + + +def get_defaults_flag(module): + connection = get_connection(module) + + try: + out = connection.get('/system default-configuration print') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + out = to_text(out, errors='surrogate_then_replace') + + commands = set() + for line in out.splitlines(): + if line.strip(): + commands.add(line.strip().split()[0]) + + if 'all' in commands: + return ['all'] + else: + return ['full'] + + +def get_config(module, flags=None): + flag_str = ' '.join(to_list(flags)) + + try: + return _DEVICE_CONFIGS[flag_str] + except KeyError: + connection = get_connection(module) + + try: + out = connection.get_config(flags=flags) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[flag_str] = cfg + return cfg + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + responses = list() + connection = get_connection(module) + + for cmd in to_list(commands): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + else: + command = cmd + prompt = None + answer = None + + try: + out = connection.get(command, prompt, answer) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + try: + out = to_text(out, errors='surrogate_or_strict') + except UnicodeError: + module.fail_json( + msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) + + responses.append(out) + + return responses + + +def load_config(module, commands): + connection = get_connection(module) + + out = connection.edit_config(commands) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/slxos.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/slxos.py new file mode 100644 index 00000000..bac2a635 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/slxos/slxos.py @@ -0,0 +1,152 @@ +# +# (c) 2018 Extreme Networks 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection + + +def get_connection(module): + """Get switch connection + + Creates reusable SSH connection to the switch described in a given module. + + Args: + module: A valid AnsibleModule instance. + + Returns: + An instance of `ansible.module_utils.connection.Connection` with a + connection to the switch described in the provided module. + + Raises: + AnsibleConnectionFailure: An error occurred connecting to the device + """ + if hasattr(module, 'slxos_connection'): + return module.slxos_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module.slxos_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module.slxos_connection + + +def get_capabilities(module): + """Get switch capabilities + + Collects and returns a python object with the switch capabilities. + + Args: + module: A valid AnsibleModule instance. + + Returns: + A dictionary containing the switch capabilities. + """ + if hasattr(module, 'slxos_capabilities'): + return module.slxos_capabilities + + capabilities = Connection(module._socket_path).get_capabilities() + module.slxos_capabilities = json.loads(capabilities) + return module.slxos_capabilities + + +def run_commands(module, commands): + """Run command list against connection. + + Get new or previously used connection and send commands to it one at a time, + collecting response. + + Args: + module: A valid AnsibleModule instance. + commands: Iterable of command strings. + + Returns: + A list of output strings. + """ + responses = list() + connection = get_connection(module) + + for cmd in to_list(commands): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + else: + command = cmd + prompt = None + answer = None + + out = connection.get(command, prompt, answer) + + try: + out = to_text(out, errors='surrogate_or_strict') + except UnicodeError: + module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) + + responses.append(out) + + return responses + + +def get_config(module): + """Get switch configuration + + Gets the described device's current configuration. If a configuration has + already been retrieved it will return the previously obtained configuration. + + Args: + module: A valid AnsibleModule instance. + + Returns: + A string containing the configuration. + """ + if not hasattr(module, 'device_configs'): + module.device_configs = {} + elif module.device_configs != {}: + return module.device_configs + + connection = get_connection(module) + out = connection.get_config() + cfg = to_text(out, errors='surrogate_then_replace').strip() + module.device_configs = cfg + return cfg + + +def load_config(module, commands): + """Apply a list of commands to a device. + + Given a list of commands apply them to the device to modify the + configuration in bulk. + + Args: + module: A valid AnsibleModule instance. + commands: Iterable of command strings. + + Returns: + None + """ + connection = get_connection(module) + connection.edit_config(commands) diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/sros.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/sros.py new file mode 100644 index 00000000..bdc3a960 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/sros/sros.py @@ -0,0 +1,122 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2016 Peter Sprygada, +# +# Redistribution and use in source and binary forms, with or without +# modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, +# this list of conditions and the following disclaimer in the +# documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import exec_command + +_DEVICE_CONFIGS = {} + +sros_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'timeout': dict(type='int'), +} +sros_argument_spec = { + 'provider': dict(type='dict', options=sros_provider_spec, removed_in_version='4.0.0', + removed_from_collection='community.network'), +} +sros_top_spec = { + 'host': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'port': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 + 'username': dict(removed_in_version='0.2.0', + removed_from_collection='community.network'), # was Ansible 2.9 + 'password': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', no_log=True), # was Ansible 2.9 + 'ssh_keyfile': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='path'), # was Ansible 2.9 + 'timeout': dict(removed_in_version='0.2.0', + removed_from_collection='community.network', type='int'), # was Ansible 2.9 +} +sros_argument_spec.update(sros_top_spec) + + +def check_args(module, warnings): + pass + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = 'admin display-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict')) + cfg = to_text(out, errors='surrogate_or_strict').strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + responses = list() + commands = to_commands(module, to_list(commands)) + for cmd in commands: + cmd = module.jsonify(cmd) + rc, out, err = exec_command(module, cmd) + if check_rc and rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc) + responses.append(to_text(out, errors='surrogate_or_strict')) + return responses + + +def load_config(module, commands): + for command in to_list(commands): + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc) + exec_command(module, 'exit all') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/voss.py b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/voss.py new file mode 100644 index 00000000..2ce48add --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/module_utils/network/voss/voss.py @@ -0,0 +1,223 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Extreme Networks Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +import re + +from ansible.module_utils._text import to_native, to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, ConfigLine + +_DEVICE_CONFIGS = {} + +DEFAULT_COMMENT_TOKENS = ['#', '!', '/*', '*/', 'echo'] + +DEFAULT_IGNORE_LINES_RE = set([ + re.compile(r"Preparing to Display Configuration\.\.\.") +]) + + +def get_connection(module): + if hasattr(module, '_voss_connection'): + return module._voss_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._voss_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._voss_connection + + +def get_capabilities(module): + if hasattr(module, '_voss_capabilities'): + return module._voss_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._voss_capabilities = json.loads(capabilities) + return module._voss_capabilities + + +def get_defaults_flag(module): + connection = get_connection(module) + try: + out = connection.get_defaults_flag() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return to_text(out, errors='surrogate_then_replace').strip() + + +def get_config(module, source='running', flags=None): + flag_str = ' '.join(to_list(flags)) + + try: + return _DEVICE_CONFIGS[flag_str] + except KeyError: + connection = get_connection(module) + try: + out = connection.get_config(source=source, flags=flags) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[flag_str] = cfg + return cfg + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + out = connection.run_commands(commands=commands, check_rc=check_rc) + return out + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def load_config(module, commands): + connection = get_connection(module) + + try: + resp = connection.edit_config(commands) + return resp.get('response') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_sublevel_config(running_config, module): + contents = list() + current_config_contents = list() + sublevel_config = VossNetworkConfig(indent=0) + obj = running_config.get_object(module.params['parents']) + if obj: + contents = obj._children + for c in contents: + if isinstance(c, ConfigLine): + current_config_contents.append(c.raw) + sublevel_config.add(current_config_contents, module.params['parents']) + return sublevel_config + + +def ignore_line(text, tokens=None): + for item in (tokens or DEFAULT_COMMENT_TOKENS): + if text.startswith(item): + return True + for regex in DEFAULT_IGNORE_LINES_RE: + if regex.match(text): + return True + + +def voss_parse(lines, indent=None, comment_tokens=None): + toplevel = re.compile(r'(^interface.*$)|(^router \w+$)|(^router vrf \w+$)') + exitline = re.compile(r'^exit$') + entry_reg = re.compile(r'([{};])') + + ancestors = list() + config = list() + dup_parent_index = None + + for line in to_native(lines, errors='surrogate_or_strict').split('\n'): + text = entry_reg.sub('', line).strip() + + cfg = ConfigLine(text) + + if not text or ignore_line(text, comment_tokens): + continue + + # Handle top level commands + if toplevel.match(text): + # Looking to see if we have existing parent + for index, item in enumerate(config): + if item.text == text: + # This means we have an existing parent with same label + dup_parent_index = index + break + ancestors = [cfg] + config.append(cfg) + + # Handle 'exit' line + elif exitline.match(text): + ancestors = list() + + if dup_parent_index is not None: + # We're working with a duplicate parent + # Don't need to store exit, just go to next line in config + dup_parent_index = None + else: + cfg._parents = ancestors[:1] + config.append(cfg) + + # Handle sub-level commands. Only have single sub-level + elif ancestors: + cfg._parents = ancestors[:1] + if dup_parent_index is not None: + # Update existing entry, since this already exists in config + config[int(dup_parent_index)].add_child(cfg) + new_index = dup_parent_index + 1 + config.insert(new_index, cfg) + else: + ancestors[0].add_child(cfg) + config.append(cfg) + + else: + # Global command, no further special handling needed + config.append(cfg) + return config + + +class VossNetworkConfig(NetworkConfig): + + def load(self, s): + self._config_text = s + self._items = voss_parse(s, self._indent) + + def _diff_line(self, other): + updates = list() + for item in self.items: + if str(item) == "exit": + if updates and updates[-1]._parents: + updates.append(item) + elif item not in other: + updates.append(item) + return updates diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server.py new file mode 100644 index 00000000..d9bdcc80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# (c) 2016, Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_server +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' server object. +description: + - Manage SLB (Server Load Balancer) server objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + partition: + description: + - set active-partition + server_name: + description: + - The SLB (Server Load Balancer) server name. + required: true + aliases: ['server'] + server_ip: + description: + - The SLB server IPv4 address. + aliases: ['ip', 'address'] + server_status: + description: + - The SLB virtual server status. + default: enabled + aliases: ['status'] + choices: ['enabled', 'disabled'] + server_ports: + description: + - A list of ports to create for the server. Each list item should be a + dictionary which specifies the C(port:) and C(protocol:), but can also optionally + specify the C(status:). See the examples below for details. This parameter is + required when C(state) is C(present). + aliases: ['port'] + state: + description: + - This is to specify the operation to create, update or remove SLB server. + default: present + choices: ['present', 'absent'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +EXAMPLES = ''' +- name: Create a new server + community.network.a10_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + server: test + server_ip: 1.1.1.100 + server_ports: + - port_num: 8080 + protocol: tcp + - port_num: 8443 + protocol: TCP +''' + +RETURN = ''' +content: + description: the full info regarding the slb_server + returned: success + type: str + sample: "mynewserver" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, axapi_failure, + axapi_get_port_protocol, axapi_enabled_disabled, AXAPI_PORT_PROTOCOLS) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port_num', 'protocol', 'status'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port_num' in item: + try: + item['port_num'] = int(item['port_num']) + except Exception: + module.fail_json(msg="port_num entries in the port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port_num field") + + # validate the port protocol is present, and convert it to + # the internal API integer value (and validate it) + if 'protocol' in item: + protocol = axapi_get_port_protocol(item['protocol']) + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS)) + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + server_name=dict(type='str', aliases=['server'], required=True), + server_ip=dict(type='str', aliases=['ip', 'address']), + server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']), + server_ports=dict(type='list', aliases=['port'], default=[]), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + partition = module.params['partition'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_server = module.params['server_name'] + slb_server_ip = module.params['server_ip'] + slb_server_status = module.params['server_status'] + slb_server_ports = module.params['server_ports'] + + if slb_server is None: + module.fail_json(msg='server_name is required') + + axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host + session_url = axapi_authenticate(module, axapi_base_url, username, password) + + # validate the ports data structure + validate_ports(module, slb_server_ports) + + json_post = { + 'server': { + 'name': slb_server, + } + } + + # add optional module parameters + if slb_server_ip: + json_post['server']['host'] = slb_server_ip + + if slb_server_ports: + json_post['server']['port_list'] = slb_server_ports + + if slb_server_status: + json_post['server']['status'] = axapi_enabled_disabled(slb_server_status) + + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + + slb_server_data = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server})) + slb_server_exists = not axapi_failure(slb_server_data) + + changed = False + if state == 'present': + if not slb_server_exists: + if not slb_server_ip: + module.fail_json(msg='you must specify an IP address when creating a server') + + result = axapi_call(module, session_url + '&method=slb.server.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg']) + changed = True + else: + def port_needs_update(src_ports, dst_ports): + ''' + Checks to determine if the port definitions of the src_ports + array are in or different from those in dst_ports. If there is + a difference, this function returns true, otherwise false. + ''' + for src_port in src_ports: + found = False + different = False + for dst_port in dst_ports: + if src_port['port_num'] == dst_port['port_num']: + found = True + for valid_field in VALID_PORT_FIELDS: + if src_port[valid_field] != dst_port[valid_field]: + different = True + break + if found or different: + break + if not found or different: + return True + # every port from the src exists in the dst, and none of them were different + return False + + def status_needs_update(current_status, new_status): + ''' + Check to determine if we want to change the status of a server. + If there is a difference between the current status of the server and + the desired status, return true, otherwise false. + ''' + if current_status != new_status: + return True + return False + + defined_ports = slb_server_data.get('server', {}).get('port_list', []) + current_status = slb_server_data.get('server', {}).get('status') + + # we check for a needed update several ways + # - in case ports are missing from the ones specified by the user + # - in case ports are missing from those on the device + # - in case we are change the status of a server + if (port_needs_update(defined_ports, slb_server_ports) or + port_needs_update(slb_server_ports, defined_ports) or + status_needs_update(current_status, axapi_enabled_disabled(slb_server_status))): + result = axapi_call(module, session_url + '&method=slb.server.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to update the server: %s" % result['response']['err']['msg']) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server})) + else: + result = slb_server_data + elif state == 'absent': + if slb_server_exists: + result = axapi_call(module, session_url + '&method=slb.server.delete', json.dumps({'name': slb_server})) + changed = True + else: + result = dict(msg="the server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server_axapi3.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server_axapi3.py new file mode 100644 index 00000000..b5ba9a5f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_server_axapi3.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2014, Mischa Peters +# Copyright: (c) 2016, Eric Chou +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_server_axapi3 +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices +description: + - Manage SLB (Server Load Balancer) server objects on A10 Networks devices via aXAPIv3. +author: + - Eric Chou (@ericchou1) +extends_documentation_fragment: +- community.network.a10 +- url + +options: + server_name: + description: + - The SLB (Server Load Balancer) server name. + required: true + aliases: ['server'] + server_ip: + description: + - The SLB (Server Load Balancer) server IPv4 address. + required: true + aliases: ['ip', 'address'] + server_status: + description: + - The SLB (Server Load Balancer) virtual server status. + default: enable + aliases: ['action'] + choices: ['enable', 'disable'] + server_ports: + description: + - A list of ports to create for the server. Each list item should be a dictionary which specifies the C(port:) + and C(protocol:). + aliases: ['port'] + operation: + description: + - Create, Update or Remove SLB server. For create and update operation, we use the IP address and server + name specified in the POST message. For delete operation, we use the server name in the request URI. + default: create + choices: ['create', 'update', 'remove'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +RETURN = ''' +# +''' + +EXAMPLES = ''' +- name: Create a new server + a10_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + server: test + server_ip: 1.1.1.100 + validate_certs: false + server_status: enable + write_config: yes + operation: create + server_ports: + - port-number: 8080 + protocol: tcp + action: enable + - port-number: 8443 + protocol: TCP +''' + +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import axapi_call_v3, a10_argument_spec, axapi_authenticate_v3, axapi_failure +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import AXAPI_PORT_PROTOCOLS +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port-number', 'protocol', 'action'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port-number' in item: + try: + item['port-number'] = int(item['port-number']) + except Exception: + module.fail_json(msg="port-number entries in the port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port-number field") + + # validate the port protocol is present, no need to convert to the internal API integer value in v3 + if 'protocol' in item: + protocol = item['protocol'] + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS)) + + # 'status' is 'action' in AXAPIv3 + # no need to convert the status, a.k.a action, to the internal API integer value in v3 + # action is either enabled or disabled + if 'action' in item: + action = item['action'] + if action not in ['enable', 'disable']: + module.fail_json(msg="server action must be enable or disable") + else: + item['action'] = 'enable' + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + operation=dict(type='str', default='create', choices=['create', 'update', 'delete']), + server_name=dict(type='str', aliases=['server'], required=True), + server_ip=dict(type='str', aliases=['ip', 'address'], required=True), + server_status=dict(type='str', default='enable', aliases=['action'], choices=['enable', 'disable']), + server_ports=dict(type='list', aliases=['port'], default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + operation = module.params['operation'] + write_config = module.params['write_config'] + slb_server = module.params['server_name'] + slb_server_ip = module.params['server_ip'] + slb_server_status = module.params['server_status'] + slb_server_ports = module.params['server_ports'] + + axapi_base_url = 'https://{0}/axapi/v3/'.format(host) + axapi_auth_url = axapi_base_url + 'auth/' + signature = axapi_authenticate_v3(module, axapi_auth_url, username, password) + + # validate the ports data structure + validate_ports(module, slb_server_ports) + + json_post = { + "server-list": [ + { + "name": slb_server, + "host": slb_server_ip + } + ] + } + + # add optional module parameters + if slb_server_ports: + json_post['server-list'][0]['port-list'] = slb_server_ports + + if slb_server_status: + json_post['server-list'][0]['action'] = slb_server_status + + slb_server_data = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='GET', body='', signature=signature) + + # for empty slb server list + if axapi_failure(slb_server_data): + slb_server_exists = False + else: + slb_server_list = [server['name'] for server in slb_server_data['server-list']] + if slb_server in slb_server_list: + slb_server_exists = True + else: + slb_server_exists = False + + changed = False + if operation == 'create': + if slb_server_exists is False: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='POST', body=json.dumps(json_post), signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg']) + changed = True + else: + module.fail_json(msg="server already exists, use state='update' instead") + changed = False + # if we changed things, get the full info regarding result + if changed: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/' + slb_server, method='GET', body='', signature=signature) + else: + result = slb_server_data + elif operation == 'delete': + if slb_server_exists: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/' + slb_server, method='DELETE', body='', signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to delete server: %s" % result['response']['err']['msg']) + changed = True + else: + result = dict(msg="the server was not present") + elif operation == 'update': + if slb_server_exists: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='PUT', body=json.dumps(json_post), signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to update server: %s" % result['response']['err']['msg']) + changed = True + else: + result = dict(msg="the server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call_v3(module, axapi_base_url + 'write/memory/', method='POST', body='', signature=signature) + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out gracefully and exit + axapi_call_v3(module, axapi_base_url + 'logoff/', method='POST', body='', signature=signature) + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_service_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_service_group.py new file mode 100644 index 00000000..2a85b487 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_service_group.py @@ -0,0 +1,332 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_service_group +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' service groups. +description: + - Manage SLB (Server Load Balancing) service-group objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. + - When a server doesn't exist and is added to the service-group the server will be created. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + state: + description: + - If the specified service group should exists. + default: present + choices: ['present', 'absent'] + partition: + description: + - set active-partition + service_group: + description: + - The SLB (Server Load Balancing) service-group name + required: true + aliases: ['service', 'pool', 'group'] + service_group_protocol: + description: + - The SLB service-group protocol of TCP or UDP. + default: tcp + aliases: ['proto', 'protocol'] + choices: ['tcp', 'udp'] + service_group_method: + description: + - The SLB service-group load balancing method, such as round-robin or weighted-rr. + default: round-robin + aliases: ['method'] + choices: + - 'round-robin' + - 'weighted-rr' + - 'least-connection' + - 'weighted-least-connection' + - 'service-least-connection' + - 'service-weighted-least-connection' + - 'fastest-response' + - 'least-request' + - 'round-robin-strict' + - 'src-ip-only-hash' + - 'src-ip-hash' + servers: + description: + - A list of servers to add to the service group. Each list item should be a + dictionary which specifies the C(server:) and C(port:), but can also optionally + specify the C(status:). See the examples below for details. + aliases: ['server', 'member'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +EXAMPLES = ''' +- name: Create a new service-group + community.network.a10_service_group: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + service_group: sg-80-tcp + servers: + - server: foo1.mydomain.com + port: 8080 + - server: foo2.mydomain.com + port: 8080 + - server: foo3.mydomain.com + port: 8080 + - server: foo4.mydomain.com + port: 8080 + status: disabled +''' + +RETURN = ''' +content: + description: the full info regarding the slb_service_group + returned: success + type: str + sample: "mynewservicegroup" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, + axapi_failure, axapi_enabled_disabled) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_SERVICE_GROUP_FIELDS = ['name', 'protocol', 'lb_method'] +VALID_SERVER_FIELDS = ['server', 'port', 'status'] + + +def validate_servers(module, servers): + for item in servers: + for key in item: + if key not in VALID_SERVER_FIELDS: + module.fail_json(msg="invalid server field (%s), must be one of: %s" % (key, ','.join(VALID_SERVER_FIELDS))) + + # validate the server name is present + if 'server' not in item: + module.fail_json(msg="server definitions must define the server field") + + # validate the port number is present and an integer + if 'port' in item: + try: + item['port'] = int(item['port']) + except Exception: + module.fail_json(msg="server port definitions must be integers") + else: + module.fail_json(msg="server definitions must define the port field") + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + service_group=dict(type='str', aliases=['service', 'pool', 'group'], required=True), + service_group_protocol=dict(type='str', default='tcp', aliases=['proto', 'protocol'], choices=['tcp', 'udp']), + service_group_method=dict(type='str', default='round-robin', + aliases=['method'], + choices=['round-robin', + 'weighted-rr', + 'least-connection', + 'weighted-least-connection', + 'service-least-connection', + 'service-weighted-least-connection', + 'fastest-response', + 'least-request', + 'round-robin-strict', + 'src-ip-only-hash', + 'src-ip-hash']), + servers=dict(type='list', aliases=['server', 'member'], default=[]), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + partition = module.params['partition'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_service_group = module.params['service_group'] + slb_service_group_proto = module.params['service_group_protocol'] + slb_service_group_method = module.params['service_group_method'] + slb_servers = module.params['servers'] + + if slb_service_group is None: + module.fail_json(msg='service_group is required') + + axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json' + load_balancing_methods = {'round-robin': 0, + 'weighted-rr': 1, + 'least-connection': 2, + 'weighted-least-connection': 3, + 'service-least-connection': 4, + 'service-weighted-least-connection': 5, + 'fastest-response': 6, + 'least-request': 7, + 'round-robin-strict': 8, + 'src-ip-only-hash': 14, + 'src-ip-hash': 15} + + if not slb_service_group_proto or slb_service_group_proto.lower() == 'tcp': + protocol = 2 + else: + protocol = 3 + + # validate the server data list structure + validate_servers(module, slb_servers) + + json_post = { + 'service_group': { + 'name': slb_service_group, + 'protocol': protocol, + 'lb_method': load_balancing_methods[slb_service_group_method], + } + } + + # first we authenticate to get a session id + session_url = axapi_authenticate(module, axapi_base_url, username, password) + # then we select the active-partition + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + # then we check to see if the specified group exists + slb_result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group})) + slb_service_group_exist = not axapi_failure(slb_result) + + changed = False + if state == 'present': + # before creating/updating we need to validate that servers + # defined in the servers list exist to prevent errors + checked_servers = [] + for server in slb_servers: + result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': server['server']})) + if axapi_failure(result): + module.fail_json(msg="the server %s specified in the servers list does not exist" % server['server']) + checked_servers.append(server['server']) + + if not slb_service_group_exist: + result = axapi_call(module, session_url + '&method=slb.service_group.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg=result['response']['err']['msg']) + changed = True + else: + # check to see if the service group definition without the + # server members is different, and update that individually + # if it needs it + do_update = False + for field in VALID_SERVICE_GROUP_FIELDS: + if json_post['service_group'][field] != slb_result['service_group'][field]: + do_update = True + break + + if do_update: + result = axapi_call(module, session_url + '&method=slb.service_group.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg=result['response']['err']['msg']) + changed = True + + # next we pull the defined list of servers out of the returned + # results to make it a bit easier to iterate over + defined_servers = slb_result.get('service_group', {}).get('member_list', []) + + # next we add/update new member servers from the user-specified + # list if they're different or not on the target device + for server in slb_servers: + found = False + different = False + for def_server in defined_servers: + if server['server'] == def_server['server']: + found = True + for valid_field in VALID_SERVER_FIELDS: + if server[valid_field] != def_server[valid_field]: + different = True + break + if found or different: + break + # add or update as required + server_data = { + "name": slb_service_group, + "member": server, + } + if not found: + result = axapi_call(module, session_url + '&method=slb.service_group.member.create', json.dumps(server_data)) + changed = True + elif different: + result = axapi_call(module, session_url + '&method=slb.service_group.member.update', json.dumps(server_data)) + changed = True + + # finally, remove any servers that are on the target + # device but were not specified in the list given + for server in defined_servers: + found = False + for slb_server in slb_servers: + if server['server'] == slb_server['server']: + found = True + break + # remove if not found + server_data = { + "name": slb_service_group, + "member": server, + } + if not found: + result = axapi_call(module, session_url + '&method=slb.service_group.member.delete', json.dumps(server_data)) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group})) + else: + result = slb_result + elif state == 'absent': + if slb_service_group_exist: + result = axapi_call(module, session_url + '&method=slb.service_group.delete', json.dumps({'name': slb_service_group})) + changed = True + else: + result = dict(msg="the service group was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_virtual_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_virtual_server.py new file mode 100644 index 00000000..77343f80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/a10_virtual_server.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_virtual_server +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' virtual servers. +description: + - Manage SLB (Server Load Balancing) virtual server objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + state: + description: + - If the specified virtual server should exist. + choices: ['present', 'absent'] + default: present + partition: + description: + - set active-partition + virtual_server: + description: + - The SLB (Server Load Balancing) virtual server name. + required: true + aliases: ['vip', 'virtual'] + virtual_server_ip: + description: + - The SLB virtual server IPv4 address. + aliases: ['ip', 'address'] + virtual_server_status: + description: + - The SLB virtual server status, such as enabled or disabled. + default: enable + aliases: ['status'] + choices: ['enabled', 'disabled'] + virtual_server_ports: + description: + - A list of ports to create for the virtual server. Each list item should be a + dictionary which specifies the C(port:) and C(type:), but can also optionally + specify the C(service_group:) as well as the C(status:). See the examples + below for details. This parameter is required when C(state) is C(present). + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + + +EXAMPLES = ''' +- name: Create a new virtual server + community.network.a10_virtual_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + virtual_server: vserver1 + virtual_server_ip: 1.1.1.1 + virtual_server_ports: + - port: 80 + protocol: TCP + service_group: sg-80-tcp + - port: 443 + protocol: HTTPS + service_group: sg-443-https + - port: 8080 + protocol: http + status: disabled +''' + +RETURN = ''' +content: + description: the full info regarding the slb_virtual + returned: success + type: str + sample: "mynewvirtualserver" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, axapi_failure, + axapi_enabled_disabled, axapi_get_vport_protocol, + AXAPI_VPORT_PROTOCOLS) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port', 'protocol', 'service_group', 'status'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port' in item: + try: + item['port'] = int(item['port']) + except Exception: + module.fail_json(msg="port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port field") + + # validate the port protocol is present, and convert it to + # the internal API integer value (and validate it) + if 'protocol' in item: + protocol = axapi_get_vport_protocol(item['protocol']) + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_VPORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_VPORT_PROTOCOLS)) + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + # ensure the service_group field is at least present + if 'service_group' not in item: + item['service_group'] = '' + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + virtual_server=dict(type='str', aliases=['vip', 'virtual'], required=True), + virtual_server_ip=dict(type='str', aliases=['ip', 'address'], required=True), + virtual_server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']), + virtual_server_ports=dict(type='list', required=True), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + partition = module.params['partition'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_virtual = module.params['virtual_server'] + slb_virtual_ip = module.params['virtual_server_ip'] + slb_virtual_status = module.params['virtual_server_status'] + slb_virtual_ports = module.params['virtual_server_ports'] + + if slb_virtual is None: + module.fail_json(msg='virtual_server is required') + + validate_ports(module, slb_virtual_ports) + + axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host + session_url = axapi_authenticate(module, axapi_base_url, username, password) + + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + slb_virtual_data = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual})) + slb_virtual_exists = not axapi_failure(slb_virtual_data) + + changed = False + if state == 'present': + json_post = { + 'virtual_server': { + 'name': slb_virtual, + 'address': slb_virtual_ip, + 'status': axapi_enabled_disabled(slb_virtual_status), + 'vport_list': slb_virtual_ports, + } + } + + # before creating/updating we need to validate that any + # service groups defined in the ports list exist since + # since the API will still create port definitions for + # them while indicating a failure occurred + checked_service_groups = [] + for port in slb_virtual_ports: + if 'service_group' in port and port['service_group'] not in checked_service_groups: + # skip blank service group entries + if port['service_group'] == '': + continue + result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': port['service_group']})) + if axapi_failure(result): + module.fail_json(msg="the service group %s specified in the ports list does not exist" % port['service_group']) + checked_service_groups.append(port['service_group']) + + if not slb_virtual_exists: + result = axapi_call(module, session_url + '&method=slb.virtual_server.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg']) + changed = True + else: + def needs_update(src_ports, dst_ports): + ''' + Checks to determine if the port definitions of the src_ports + array are in or different from those in dst_ports. If there is + a difference, this function returns true, otherwise false. + ''' + for src_port in src_ports: + found = False + different = False + for dst_port in dst_ports: + if src_port['port'] == dst_port['port']: + found = True + for valid_field in VALID_PORT_FIELDS: + if src_port[valid_field] != dst_port[valid_field]: + different = True + break + if found or different: + break + if not found or different: + return True + # every port from the src exists in the dst, and none of them were different + return False + + defined_ports = slb_virtual_data.get('virtual_server', {}).get('vport_list', []) + + # we check for a needed update both ways, in case ports + # are missing from either the ones specified by the user + # or from those on the device + if needs_update(defined_ports, slb_virtual_ports) or needs_update(slb_virtual_ports, defined_ports): + result = axapi_call(module, session_url + '&method=slb.virtual_server.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg']) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual})) + else: + result = slb_virtual_data + elif state == 'absent': + if slb_virtual_exists: + result = axapi_call(module, session_url + '&method=slb.virtual_server.delete', json.dumps({'name': slb_virtual})) + changed = True + else: + result = dict(msg="the virtual server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_command.py new file mode 100644 index 00000000..58312516 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_command.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aireos_command +author: "James Mighion (@jmighion)" +short_description: Run commands on remote devices running Cisco WLC +description: + - Sends arbitrary commands to an aireos node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - Commands run in configuration mode with this module are not + idempotent. Please use M(community.network.aireos_config) to configure WLC devices. +extends_documentation_fragment: +- community.network.aireos + +options: + commands: + description: + - List of commands to send to the remote aireos device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show sysinfo on remote devices + community.network.aireos_command: + commands: show sysinfo + + - name: Run show sysinfo and check to see if output contains Cisco Controller + community.network.aireos_command: + commands: show sysinfo + wait_for: result[0] contains 'Cisco Controller' + + - name: Run multiple commands on remote nodes + community.network.aireos_command: + commands: + - show sysinfo + - show interface summary + + - name: Run multiple commands and evaluate the output + community.network.aireos_command: + commands: + - show sysinfo + - show interface summary + wait_for: + - result[0] contains Cisco Controller + - result[1] contains Loopback0 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import run_commands +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import aireos_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_text + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = to_text(item, errors='surrogate_then_replace').split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + warnings.append( + 'commands run in config mode with aireos_command are not ' + 'idempotent. Please use aireos_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(aireos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_config.py new file mode 100644 index 00000000..06fecec9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aireos_config.py @@ -0,0 +1,354 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aireos_config +author: "James Mighion (@jmighion)" +short_description: Manage Cisco WLC configurations +description: + - AireOS does not use a block indent file syntax, so there are no sections or parents. + This module provides an implementation for working with AireOS configurations in + a deterministic way. +extends_documentation_fragment: +- community.network.aireos + +options: + lines: + description: + - The ordered set of commands that should be configured. + The commands must be the exact same commands as found + in the device run-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. + If match is set to I(none), the module will not attempt to + compare the source configuration with the running + configuration on the remote device. + default: line + choices: ['line', 'none'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + save: + description: + - The C(save) argument instructs the module to save the + running-config to startup-config. This operation is performed + after any changes are made to the current running config. If + no changes are made, the configuration is still saved to the + startup config. This option will always cause the module to + return changed. This argument is mutually exclusive with I(save_when). + - This option is deprecated as of Ansible 2.7, use C(save_when) + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the module will always return as changed. + If the argument is set to I(never), the running-config will never + be copied to the startup-config. If the argument is set to I(changed), + then the running-config will only be copied to the startup-config if + the task has made a change. + default: never + choices: ['always', 'never', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['intended', 'running'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure configuration + community.network.aireos_config: + lines: sysname testDevice + +- name: Diff the running-config against a provided config + community.network.aireos_config: + diff_against: intended + intended: "{{ lookup('file', 'master.cfg') }}" + +- name: Load new acl into device + community.network.aireos_config: + lines: + - acl create testACL + - acl rule protocol testACL 1 any + - acl rule direction testACL 3 in + before: acl delete testACL + +- name: Configurable backup path + community.network.aireos_config: + backup: yes + lines: sysname testDevice + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/aireos_config.2016-07-16@22:28:34 +""" +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import run_commands, get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import aireos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import check_args as aireos_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_running_config(module, config=None): + contents = module.params['running_config'] + if not contents: + if config: + contents = config + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + candidate.add(module.params['lines']) + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "save config", "prompt": "Are you sure you want to save", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `save config` due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + # save is deprecated as of Ansible 2.7, use save_when instead + save=dict(type='bool', default=False, removed_in_version='2.0.0', + removed_from_collection='community.network'), # was Ansible 2.11 + save_when=dict(choices=['always', 'never', 'changed'], default='never'), + + diff_against=dict(choices=['running', 'intended']), + diff_ignore_lines=dict(type='list') + ) + + argument_spec.update(aireos_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('save', 'save_when')] + + required_if = [('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + aireos_check_args(module, warnings) + result = {'changed': False, 'warnings': warnings} + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['src'], module.params['lines'])): + match = module.params['match'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + configobjs = candidate.difference(config, match=match) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always' or module.params['save']: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + output = run_commands(module, 'show run-config commands') + contents = output[0] + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + result.update({ + 'changed': True, + 'diff': {'before': str(base_config), 'after': str(running_config)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/apconos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/apconos_command.py new file mode 100644 index 00000000..18156a4c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/apconos_command.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# +# Copyright (C) 2019 APCON. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute apconos Commands on Apcon Switches. +# Apcon Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: apconos_command +version_added: '0.2.0' +author: "David Lee (@davidlee-ap)" +short_description: Run arbitrary commands on APCON devices +description: + - Sends arbitrary commands to an apcon device and returns the results + read from the device. The module includes an argument that will + cause the module to wait for a specific condition before returning + or timing out if the condition is not met. +notes: + - Tested against apcon iis+ii +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + type: list + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + type: str + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + type: int + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 + type: int +''' + +EXAMPLES = """ +- name: Basic Configuration + community.network.apconos_command: + commands: + - show version + - enable ssh + register: result + +- name: Get output from single command + community.network.apconos_command: + commands: ['show version'] + register: result +""" + +RETURN = """ +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_lines +from ansible_collections.community.network.plugins.module_utils.network.apconos.apconos import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional + + +def parse_commands(module, warnings): + + commands = module.params['commands'] + + if module.check_mode: + for item in list(commands): + if not item.startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not ' + 'executing %s' % item + ) + commands.remove(item) + + return commands + + +def main(): + spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=False) + warnings = list() + result = {'changed': False, 'warnings': warnings} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = parse_commands(module, warnings) + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + for item in responses: + if len(item) == 0: + if module.check_mode: + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + else: + result.update({ + 'changed': True, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + elif 'ERROR' in item: + result.update({ + 'failed': True, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + else: + result.update({ + 'stdout': item, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_command.py new file mode 100644 index 00000000..33d9f2c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_command.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aruba_command +author: "James Mighion (@jmighion)" +short_description: Run commands on remote devices running Aruba Mobility Controller +description: + - Sends arbitrary commands to an aruba node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.aruba_config) to configure Aruba devices. +extends_documentation_fragment: +- community.network.aruba + +options: + commands: + description: + - List of commands to send to the remote aruba device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.aruba_command: + commands: show version + + - name: Run show version and check to see if output contains Aruba + community.network.aruba_command: + commands: show version + wait_for: result[0] contains Aruba + + - name: Run multiple commands on remote nodes + community.network.aruba_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.aruba_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains Aruba + - result[1] contains Loopback0 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import run_commands +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import aruba_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + module.fail_json( + msg='aruba_command does not support running config mode ' + 'commands. Please use aruba_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(aruba_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_config.py new file mode 100644 index 00000000..b21098b2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/aruba_config.py @@ -0,0 +1,420 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aruba_config +author: "James Mighion (@jmighion)" +short_description: Manage Aruba configuration sections +description: + - Aruba configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with Aruba configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.aruba + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup configuration and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup configuration if it has changed since + the last save to startup configuration. If the argument is set to + I(never), the running-config will never be copied to the + startup configuration. If the argument is set to I(changed), then the running-config + will only be copied to the startup configuration if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup configuration. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['startup', 'intended', 'running'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + encrypt: + description: + - This allows an Aruba controller's passwords and keys to be displayed in plain + text when set to I(false) or encrypted when set to I(true). + If set to I(false), the setting will re-encrypt at the end of the module run. + Backups are still encrypted even when set to I(false). + type: bool + default: 'yes' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.aruba_config: + lines: hostname {{ inventory_hostname }} + +- name: Diff the running-config against a provided config + community.network.aruba_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Configure interface settings + community.network.aruba_config: + lines: + - description test interface + - ip access-group 1 in + parents: interface gigabitethernet 0/0/0 + +- name: Load new acl into device + community.network.aruba_config: + lines: + - permit host 10.10.10.10 + - ipv6 permit host fda9:97d6:32a3:3e59::3333 + parents: ip access-list standard 1 + before: no ip access-list standard 1 + match: exact + +- name: Configurable backup path + community.network.aruba_config: + backup: yes + lines: hostname {{ inventory_hostname }} + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/aruba_config.2016-07-16@22:28:34 +""" + + +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import run_commands, get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import aruba_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import check_args as aruba_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_running_config(module, config=None): + contents = module.params['running_config'] + if not contents: + if config: + contents = config + else: + contents = get_config(module) + return NetworkConfig(contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig() + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'write memory') + else: + module.warn('Skipping command `write memory` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['running', 'startup', 'intended']), + diff_ignore_lines=dict(type='list'), + + encrypt=dict(type='bool', default=True), + ) + + argument_spec.update(aruba_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + aruba_check_args(module, warnings) + result = {'changed': False, 'warnings': warnings} + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if not module.params['encrypt']: + run_commands(module, 'encrypt disable') + + if any((module.params['src'], module.params['lines'])): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + path = module.params['parents'] + configobjs = candidate.difference(config, match=match, replace=replace, path=path) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show configuration']) + + running_config = NetworkConfig(contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed': + if result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show configuration') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + result.update({ + 'changed': True, + 'diff': {'before': str(base_config), 'after': str(running_config)} + }) + + # make sure 'encrypt enable' is applied if it was ever disabled + if not module.params['encrypt']: + run_commands(module, 'encrypt enable') + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_actiongroupconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_actiongroupconfig.py new file mode 100644 index 00000000..bfd07703 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_actiongroupconfig.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_actiongroupconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ActionGroupConfig Avi RESTful Object +description: + - This module is used to configure ActionGroupConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_script_config_ref: + description: + - Reference of the action script configuration to be used. + - It is a reference to an object of type alertscriptconfig. + autoscale_trigger_notification: + description: + - Trigger notification to autoscale manager. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + email_config_ref: + description: + - Select the email notification configuration to use when sending alerts via email. + - It is a reference to an object of type alertemailconfig. + external_only: + description: + - Generate alert only to external destinations. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + required: true + type: bool + level: + description: + - When an alert is generated, mark its priority via the alert level. + - Enum options - ALERT_LOW, ALERT_MEDIUM, ALERT_HIGH. + - Default value when not specified in API or module is interpreted by Avi Controller as ALERT_LOW. + required: true + name: + description: + - Name of the object. + required: true + snmp_trap_profile_ref: + description: + - Select the snmp trap notification to use when sending alerts via snmp trap. + - It is a reference to an object of type snmptrapprofile. + syslog_config_ref: + description: + - Select the syslog notification configuration to use when sending alerts via syslog. + - It is a reference to an object of type alertsyslogconfig. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ActionGroupConfig object + community.network.avi_actiongroupconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_actiongroupconfig +""" + +RETURN = ''' +obj: + description: ActionGroupConfig (api/actiongroupconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_script_config_ref=dict(type='str',), + autoscale_trigger_notification=dict(type='bool',), + description=dict(type='str',), + email_config_ref=dict(type='str',), + external_only=dict(type='bool', required=True), + level=dict(type='str', required=True), + name=dict(type='str', required=True), + snmp_trap_profile_ref=dict(type='str',), + syslog_config_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'actiongroupconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertconfig.py new file mode 100644 index 00000000..80a5f443 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertconfig.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertConfig Avi RESTful Object +description: + - This module is used to configure AlertConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_group_ref: + description: + - The alert config will trigger the selected alert action, which can send notifications and execute a controlscript. + - It is a reference to an object of type actiongroupconfig. + alert_rule: + description: + - List of filters matching on events or client logs used for triggering alerts. + required: true + autoscale_alert: + description: + - This alert config applies to auto scale alerts. + type: bool + category: + description: + - Determines whether an alert is raised immediately when event occurs (realtime) or after specified number of events occurs within rolling time + - window. + - Enum options - REALTIME, ROLLINGWINDOW, WATERMARK. + - Default value when not specified in API or module is interpreted by Avi Controller as REALTIME. + required: true + description: + description: + - A custom description field. + enabled: + description: + - Enable or disable this alert config from generating new alerts. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + expiry_time: + description: + - An alert is expired and deleted after the expiry time has elapsed. + - The original event triggering the alert remains in the event's log. + - Allowed values are 1-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 86400. + name: + description: + - Name of the alert configuration. + required: true + obj_uuid: + description: + - Uuid of the resource for which alert was raised. + object_type: + description: + - The object type to which the alert config is associated with. + - Valid object types are - virtual service, pool, service engine. + - Enum options - VIRTUALSERVICE, POOL, HEALTHMONITOR, NETWORKPROFILE, APPLICATIONPROFILE, HTTPPOLICYSET, DNSPOLICY, SECURITYPOLICY, IPADDRGROUP, + - STRINGGROUP, SSLPROFILE, SSLKEYANDCERTIFICATE, NETWORKSECURITYPOLICY, APPLICATIONPERSISTENCEPROFILE, ANALYTICSPROFILE, VSDATASCRIPTSET, TENANT, + - PKIPROFILE, AUTHPROFILE, CLOUD, SERVERAUTOSCALEPOLICY, AUTOSCALELAUNCHCONFIG, MICROSERVICEGROUP, IPAMPROFILE, HARDWARESECURITYMODULEGROUP, + - POOLGROUP, PRIORITYLABELS, POOLGROUPDEPLOYMENTPOLICY, GSLBSERVICE, GSLBSERVICERUNTIME, SCHEDULER, GSLBGEODBPROFILE, + - GSLBAPPLICATIONPERSISTENCEPROFILE, TRAFFICCLONEPROFILE, VSVIP, WAFPOLICY, WAFPROFILE, ERRORPAGEPROFILE, ERRORPAGEBODY, L4POLICYSET, + - GSLBSERVICERUNTIMEBATCH, WAFPOLICYPSMGROUP, PINGACCESSAGENT, SERVICEENGINEPOLICY, NATPOLICY, SSOPOLICY, PROTOCOLPARSER, SERVICEENGINE, + - DEBUGSERVICEENGINE, DEBUGCONTROLLER, DEBUGVIRTUALSERVICE, SERVICEENGINEGROUP, SEPROPERTIES, NETWORK, CONTROLLERNODE, CONTROLLERPROPERTIES, + - SYSTEMCONFIGURATION, VRFCONTEXT, USER, ALERTCONFIG, ALERTSYSLOGCONFIG, ALERTEMAILCONFIG, ALERTTYPECONFIG, APPLICATION, ROLE, CLOUDPROPERTIES, + - SNMPTRAPPROFILE, ACTIONGROUPPROFILE, MICROSERVICE, ALERTPARAMS, ACTIONGROUPCONFIG, CLOUDCONNECTORUSER, GSLB, GSLBDNSUPDATE, GSLBSITEOPS, + - GLBMGRWARMSTART, IPAMDNSRECORD, GSLBDNSGSSTATUS, GSLBDNSGEOFILEOPS, GSLBDNSGEOUPDATE, GSLBDNSGEOCLUSTEROPS, GSLBDNSCLEANUP, GSLBSITEOPSRESYNC, + - IPAMDNSPROVIDERPROFILE, TCPSTATRUNTIME, UDPSTATRUNTIME, IPSTATRUNTIME, ARPSTATRUNTIME, MBSTATRUNTIME, IPSTKQSTATSRUNTIME, MALLOCSTATRUNTIME, + - SHMALLOCSTATRUNTIME, CPUUSAGERUNTIME, L7GLOBALSTATSRUNTIME, L7VIRTUALSERVICESTATSRUNTIME, SEAGENTVNICDBRUNTIME, SEAGENTGRAPHDBRUNTIME, + - SEAGENTSTATERUNTIME, INTERFACERUNTIME, ARPTABLERUNTIME, DISPATCHERSTATRUNTIME, DISPATCHERSTATCLEARRUNTIME, DISPATCHERTABLEDUMPRUNTIME, + - DISPATCHERREMOTETIMERLISTDUMPRUNTIME, METRICSAGENTMESSAGE, HEALTHMONITORSTATRUNTIME, METRICSENTITYRUNTIME, PERSISTENCEINTERNAL, + - HTTPPOLICYSETINTERNAL, DNSPOLICYINTERNAL, CONNECTIONDUMPRUNTIME, SHAREDDBSTATS, SHAREDDBSTATSCLEAR, ICMPSTATRUNTIME, ROUTETABLERUNTIME, + - VIRTUALMACHINE, POOLSERVER, SEVSLIST, MEMINFORUNTIME, RTERINGSTATRUNTIME, ALGOSTATRUNTIME, HEALTHMONITORRUNTIME, CPUSTATRUNTIME, SEVM, HOST, + - PORTGROUP, CLUSTER, DATACENTER, VCENTER, HTTPPOLICYSETSTATS, DNSPOLICYSTATS, METRICSSESTATS, RATELIMITERSTATRUNTIME, NETWORKSECURITYPOLICYSTATS, + - TCPCONNRUNTIME, POOLSTATS, CONNPOOLINTERNAL, CONNPOOLSTATS, VSHASHSHOWRUNTIME, SELOGSTATSRUNTIME, NETWORKSECURITYPOLICYDETAIL, LICENSERUNTIME, + - SERVERRUNTIME, METRICSRUNTIMESUMMARY, METRICSRUNTIMEDETAIL, DISPATCHERSEHMPROBETEMPDISABLERUNTIME, POOLDEBUG, VSLOGMGRMAP, SERUMINSERTIONSTATS, + - HTTPCACHE, HTTPCACHESTATS, SEDOSSTATRUNTIME, VSDOSSTATRUNTIME, SERVERUPDATEREQ, VSSCALEOUTLIST, SEMEMDISTRUNTIME, TCPCONNRUNTIMEDETAIL, + - SEUPGRADESTATUS, SEUPGRADEPREVIEW, SEFAULTINJECTEXHAUSTM, SEFAULTINJECTEXHAUSTMCL, SEFAULTINJECTEXHAUSTMCLSMALL, SEFAULTINJECTEXHAUSTCONN, + - SEHEADLESSONLINEREQ, SEUPGRADE, SEUPGRADESTATUSDETAIL, SERESERVEDVS, SERESERVEDVSCLEAR, VSCANDIDATESEHOSTLIST, SEGROUPUPGRADE, REBALANCE, + - SEGROUPREBALANCE, SEAUTHSTATSRUNTIME, AUTOSCALESTATE, VIRTUALSERVICEAUTHSTATS, NETWORKSECURITYPOLICYDOS, KEYVALINTERNAL, KEYVALSUMMARYINTERNAL, + - SERVERSTATEUPDATEINFO, CLTRACKINTERNAL, CLTRACKSUMMARYINTERNAL, MICROSERVICERUNTIME, SEMICROSERVICE, VIRTUALSERVICEANALYSIS, CLIENTINTERNAL, + - CLIENTSUMMARYINTERNAL, MICROSERVICEGROUPRUNTIME, BGPRUNTIME, REQUESTQUEUERUNTIME, MIGRATEALL, MIGRATEALLSTATUSSUMMARY, MIGRATEALLSTATUSDETAIL, + - INTERFACESUMMARYRUNTIME, INTERFACELACPRUNTIME, DNSTABLE, GSLBSERVICEDETAIL, GSLBSERVICEINTERNAL, GSLBSERVICEHMONSTAT, SETROLESREQUEST, + - TRAFFICCLONERUNTIME, GEOLOCATIONINFO, SEVSHBSTATRUNTIME, GEODBINTERNAL, GSLBSITEINTERNAL, WAFSTATS, USERDEFINEDDATASCRIPTCOUNTERS, LLDPRUNTIME, + - VSESSHARINGPOOL, NDTABLERUNTIME, IP6STATRUNTIME, ICMP6STATRUNTIME, SEVSSPLACEMENT, L4POLICYSETSTATS, L4POLICYSETINTERNAL, BGPDEBUGINFO, SHARD, + - CPUSTATRUNTIMEDETAIL, SEASSERTSTATRUNTIME, SEFAULTINJECTINFRA, SEAGENTASSERTSTATRUNTIME, SEDATASTORESTATUS, DIFFQUEUESTATUS, IP6ROUTETABLERUNTIME, + - SECURITYMGRSTATE, VIRTUALSERVICESESCALEOUTSTATUS, SHARDSERVERSTATUS, SEAGENTSHARDCLIENTRESOURCEMAP, SEAGENTCONSISTENTHASH, SEAGENTVNICDBHISTORY, + - SEAGENTSHARDCLIENTAPPMAP, SEAGENTSHARDCLIENTEVENTHISTORY, SENATSTATRUNTIME, SENATFLOWRUNTIME, SERESOURCEPROTO, SECONSUMERPROTO, + - SECREATEPENDINGPROTO, PLACEMENTSTATS, SEVIPPROTO, RMVRFPROTO, VCENTERMAP, VIMGRVCENTERRUNTIME, INTERESTEDVMS, INTERESTEDHOSTS, + - VCENTERSUPPORTEDCOUNTERS, ENTITYCOUNTERS, TRANSACTIONSTATS, SEVMCREATEPROGRESS, PLACEMENTSTATUS, VISUBFOLDERS, VIDATASTORE, VIHOSTRESOURCES, + - CLOUDCONNECTOR, VINETWORKSUBNETVMS, VIDATASTORECONTENTS, VIMGRVCENTERCLOUDRUNTIME, VIVCENTERPORTGROUPS, VIVCENTERDATACENTERS, VIMGRHOSTRUNTIME, + - PLACEMENTGLOBALS, APICCONFIGURATION, CIFTABLE, APICTRANSACTION, VIRTUALSERVICESTATEDBCACHESUMMARY, POOLSTATEDBCACHESUMMARY, + - SERVERSTATEDBCACHESUMMARY, APICAGENTINTERNAL, APICTRANSACTIONFLAP, APICGRAPHINSTANCES, APICEPGS, APICEPGEPS, APICDEVICEPKGVER, APICTENANTS, + - APICVMMDOMAINS, NSXCONFIGURATION, NSXSGTABLE, NSXAGENTINTERNAL, NSXSGINFO, NSXSGIPS, NSXAGENTINTERNALCLI, MAXOBJECTS. + recommendation: + description: + - Recommendation of alertconfig. + rolling_window: + description: + - Only if the number of events is reached or exceeded within the time window will an alert be generated. + - Allowed values are 1-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + source: + description: + - Signifies system events or the type of client logsused in this alert configuration. + - Enum options - CONN_LOGS, APP_LOGS, EVENT_LOGS, METRICS. + required: true + summary: + description: + - Summary of reason why alert is generated. + tenant_ref: + description: + - It is a reference to an object of type tenant. + threshold: + description: + - An alert is created only when the number of events meets or exceeds this number within the chosen time frame. + - Allowed values are 1-65536. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + throttle: + description: + - Alerts are suppressed (throttled) for this duration of time since the last alert was raised for this alert config. + - Allowed values are 0-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create AlertConfig object + community.network.avi_alertconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_alertconfig +""" + +RETURN = ''' +obj: + description: AlertConfig (api/alertconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_group_ref=dict(type='str',), + alert_rule=dict(type='dict', required=True), + autoscale_alert=dict(type='bool',), + category=dict(type='str', required=True), + description=dict(type='str',), + enabled=dict(type='bool',), + expiry_time=dict(type='int',), + name=dict(type='str', required=True), + obj_uuid=dict(type='str',), + object_type=dict(type='str',), + recommendation=dict(type='str',), + rolling_window=dict(type='int',), + source=dict(type='str', required=True), + summary=dict(type='str',), + tenant_ref=dict(type='str',), + threshold=dict(type='int',), + throttle=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertemailconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertemailconfig.py new file mode 100644 index 00000000..b460a76e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertemailconfig.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertemailconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertEmailConfig Avi RESTful Object +description: + - This module is used to configure AlertEmailConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cc_emails: + description: + - Alerts are copied to the comma separated list of email recipients. + description: + description: + - User defined description for the object. + name: + description: + - A user-friendly name of the email notification service. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + to_emails: + description: + - Alerts are sent to the comma separated list of email recipients. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create AlertEmailConfig object + community.network.avi_alertemailconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_alertemailconfig +""" + +RETURN = ''' +obj: + description: AlertEmailConfig (api/alertemailconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cc_emails=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + to_emails=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertemailconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertscriptconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertscriptconfig.py new file mode 100644 index 00000000..95868c89 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertscriptconfig.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertscriptconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertScriptConfig Avi RESTful Object +description: + - This module is used to configure AlertScriptConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_script: + description: + - User defined alert action script. + - Please refer to kb.avinetworks.com for more information. + name: + description: + - A user-friendly name of the script. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Alert Script to perform AWS server autoscaling + community.network.avi_alertscriptconfig: + username: '{{ username }}' + controller: '{{ controller }}' + password: '{{ password }}' + action_script: "echo Hello" + name: AWS-Launch-Script + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: AlertScriptConfig (api/alertscriptconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_script=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertscriptconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertsyslogconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertsyslogconfig.py new file mode 100644 index 00000000..d6bd4a0a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_alertsyslogconfig.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertsyslogconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertSyslogConfig Avi RESTful Object +description: + - This module is used to configure AlertSyslogConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for alert syslog config. + name: + description: + - A user-friendly name of the syslog notification. + required: true + syslog_servers: + description: + - The list of syslog servers. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Alert Syslog object to forward all events to external syslog server + community.network.avi_alertsyslogconfig: + controller: '{{ controller }}' + name: Roberts-syslog + password: '{{ password }}' + syslog_servers: + - syslog_server: 10.10.0.100 + syslog_server_port: 514 + udp: true + tenant_ref: admin + username: '{{ username }}' +""" + +RETURN = ''' +obj: + description: AlertSyslogConfig (api/alertsyslogconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + name=dict(type='str', required=True), + syslog_servers=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertsyslogconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_analyticsprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_analyticsprofile.py new file mode 100644 index 00000000..079ec4c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_analyticsprofile.py @@ -0,0 +1,610 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_analyticsprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AnalyticsProfile Avi RESTful Object +description: + - This module is used to configure AnalyticsProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + apdex_response_threshold: + description: + - If a client receives an http response in less than the satisfactory latency threshold, the request is considered satisfied. + - It is considered tolerated if it is not satisfied and less than tolerated latency factor multiplied by the satisfactory latency threshold. + - Greater than this number and the client's request is considered frustrated. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 500. + apdex_response_tolerated_factor: + description: + - Client tolerated response latency factor. + - Client must receive a response within this factor times the satisfactory threshold (apdex_response_threshold) to be considered tolerated. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_rtt_threshold: + description: + - Satisfactory client to avi round trip time(rtt). + - Allowed values are 1-2000. + - Default value when not specified in API or module is interpreted by Avi Controller as 250. + apdex_rtt_tolerated_factor: + description: + - Tolerated client to avi round trip time(rtt) factor. + - It is a multiple of apdex_rtt_tolerated_factor. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_rum_threshold: + description: + - If a client is able to load a page in less than the satisfactory latency threshold, the pageload is considered satisfied. + - It is considered tolerated if it is greater than satisfied but less than the tolerated latency multiplied by satisfied latency. + - Greater than this number and the client's request is considered frustrated. + - A pageload includes the time for dns lookup, download of all http objects, and page render time. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 5000. + apdex_rum_tolerated_factor: + description: + - Virtual service threshold factor for tolerated page load time (plt) as multiple of apdex_rum_threshold. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_server_response_threshold: + description: + - A server http response is considered satisfied if latency is less than the satisfactory latency threshold. + - The response is considered tolerated when it is greater than satisfied but less than the tolerated latency factor * s_latency. + - Greater than this number and the server response is considered frustrated. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 400. + apdex_server_response_tolerated_factor: + description: + - Server tolerated response latency factor. + - Servermust response within this factor times the satisfactory threshold (apdex_server_response_threshold) to be considered tolerated. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_server_rtt_threshold: + description: + - Satisfactory client to avi round trip time(rtt). + - Allowed values are 1-2000. + - Default value when not specified in API or module is interpreted by Avi Controller as 125. + apdex_server_rtt_tolerated_factor: + description: + - Tolerated client to avi round trip time(rtt) factor. + - It is a multiple of apdex_rtt_tolerated_factor. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + client_log_config: + description: + - Configure which logs are sent to the avi controller from ses and how they are processed. + client_log_streaming_config: + description: + - Configure to stream logs to an external server. + - Field introduced in 17.1.1. + conn_lossy_ooo_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of out of order packets are received. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_lossy_timeo_rexmt_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of packets are retransmitted due to timeout. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + conn_lossy_total_rexmt_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of packets are retransmitted. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_lossy_zero_win_size_event_threshold: + description: + - A client connection is considered lossy when percentage of times a packet could not be transmitted due to tcp zero window is above this threshold. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + conn_server_lossy_ooo_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of out of order packets are received. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_server_lossy_timeo_rexmt_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of packets are retransmitted due to timeout. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + conn_server_lossy_total_rexmt_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of packets are retransmitted. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_server_lossy_zero_win_size_event_threshold: + description: + - A server connection is considered lossy when percentage of times a packet could not be transmitted due to tcp zero window is above this threshold. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + description: + description: + - User defined description for the object. + disable_ondemand_metrics: + description: + - Virtual service (vs) metrics are processed only when there is live data traffic on the vs. + - In case, vs is idle for a period of time as specified by ondemand_metrics_idle_timeout then metrics processing is suspended for that vs. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_se_analytics: + description: + - Disable node (service engine) level analytics forvs metrics. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_server_analytics: + description: + - Disable analytics on backend servers. + - This may be desired in container environment when there are large number of ephemeral servers. + - Additionally, no healthscore of servers is computed when server analytics is disabled. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_vs_analytics: + description: + - Disable virtualservice (frontend) analytics. + - This flag disables metrics and healthscore for virtualservice. + - Field introduced in 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_advanced_analytics: + description: + - Enables advanced analytics features like anomaly detection. + - If set to false, anomaly computation (and associated rules/events) for vs, pool and server metrics will be disabled. + - However, setting it to false reduces cpu and memory requirements for analytics subsystem. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + exclude_client_close_before_request_as_error: + description: + - Exclude client closed connection before an http request could be completed from being classified as an error. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_dns_policy_drop_as_significant: + description: + - Exclude dns policy drops from the list of errors. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_gs_down_as_error: + description: + - Exclude queries to gslb services that are operationally down from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_http_error_codes: + description: + - List of http status codes to be excluded from being classified as an error. + - Error connections or responses impacts health score, are included as significant logs, and may be classified as part of a dos attack. + exclude_invalid_dns_domain_as_error: + description: + - Exclude dns queries to domains outside the domains configured in the dns application profile from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_invalid_dns_query_as_error: + description: + - Exclude invalid dns queries from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_no_dns_record_as_error: + description: + - Exclude queries to domains that did not have configured services/records from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_no_valid_gs_member_as_error: + description: + - Exclude queries to gslb services that have no available members from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_persistence_change_as_error: + description: + - Exclude persistence server changed while load balancing' from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_server_dns_error_as_error: + description: + - Exclude server dns error response from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_server_tcp_reset_as_error: + description: + - Exclude server tcp reset from errors. + - It is common for applications like ms exchange. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_sip_error_codes: + description: + - List of sip status codes to be excluded from being classified as an error. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + exclude_syn_retransmit_as_error: + description: + - Exclude 'server unanswered syns' from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_tcp_reset_as_error: + description: + - Exclude tcp resets by client from the list of potential errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_unsupported_dns_query_as_error: + description: + - Exclude unsupported dns queries from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + healthscore_max_server_limit: + description: + - Skips health score computation of pool servers when number of servers in a pool is more than this setting. + - Allowed values are 0-5000. + - Special values are 0- 'server health score is disabled'. + - Field introduced in 17.2.13, 18.1.4. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + hs_event_throttle_window: + description: + - Time window (in secs) within which only unique health change events should occur. + - Default value when not specified in API or module is interpreted by Avi Controller as 1209600. + hs_max_anomaly_penalty: + description: + - Maximum penalty that may be deducted from health score for anomalies. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + hs_max_resources_penalty: + description: + - Maximum penalty that may be deducted from health score for high resource utilization. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 25. + hs_max_security_penalty: + description: + - Maximum penalty that may be deducted from health score based on security assessment. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + hs_min_dos_rate: + description: + - Dos connection rate below which the dos security assessment will not kick in. + - Default value when not specified in API or module is interpreted by Avi Controller as 1000. + hs_performance_boost: + description: + - Adds free performance score credits to health score. + - It can be used for compensating health score for known slow applications. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + hs_pscore_traffic_threshold_l4_client: + description: + - Threshold number of connections in 5min, below which apdexr, apdexc, rum_apdex, and other network quality metrics are not computed. + - Default value when not specified in API or module is interpreted by Avi Controller as 10.0. + hs_pscore_traffic_threshold_l4_server: + description: + - Threshold number of connections in 5min, below which apdexr, apdexc, rum_apdex, and other network quality metrics are not computed. + - Default value when not specified in API or module is interpreted by Avi Controller as 10.0. + hs_security_certscore_expired: + description: + - Score assigned when the certificate has expired. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_certscore_gt30d: + description: + - Score assigned when the certificate expires in more than 30 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_certscore_le07d: + description: + - Score assigned when the certificate expires in less than or equal to 7 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 2.0. + hs_security_certscore_le30d: + description: + - Score assigned when the certificate expires in less than or equal to 30 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + hs_security_chain_invalidity_penalty: + description: + - Penalty for allowing certificates with invalid chain. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_cipherscore_eq000b: + description: + - Score assigned when the minimum cipher strength is 0 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_cipherscore_ge128b: + description: + - Score assigned when the minimum cipher strength is greater than equal to 128 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_cipherscore_lt128b: + description: + - Score assigned when the minimum cipher strength is less than 128 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 3.5. + hs_security_encalgo_score_none: + description: + - Score assigned when no algorithm is used for encryption. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_encalgo_score_rc4: + description: + - Score assigned when rc4 algorithm is used for encryption. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 2.5. + hs_security_hsts_penalty: + description: + - Penalty for not enabling hsts. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_nonpfs_penalty: + description: + - Penalty for allowing non-pfs handshakes. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_selfsignedcert_penalty: + description: + - Deprecated. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_ssl30_score: + description: + - Score assigned when supporting ssl3.0 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 3.5. + hs_security_tls10_score: + description: + - Score assigned when supporting tls1.0 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_tls11_score: + description: + - Score assigned when supporting tls1.1 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_tls12_score: + description: + - Score assigned when supporting tls1.2 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_weak_signature_algo_penalty: + description: + - Penalty for allowing weak signature algorithm(s). + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + name: + description: + - The name of the analytics profile. + required: true + ondemand_metrics_idle_timeout: + description: + - This flag sets the time duration of no live data traffic after which virtual service metrics processing is suspended. + - It is applicable only when disable_ondemand_metrics is set to false. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1800. + ranges: + description: + - List of http status code ranges to be excluded from being classified as an error. + resp_code_block: + description: + - Block of http response codes to be excluded from being classified as an error. + - Enum options - AP_HTTP_RSP_4XX, AP_HTTP_RSP_5XX. + sensitive_log_profile: + description: + - Rules applied to the http application log for filtering sensitive information. + - Field introduced in 17.2.10, 18.1.2. + sip_log_depth: + description: + - Maximum number of sip messages added in logs for a sip transaction. + - By default, this value is 20. + - Allowed values are 1-1000. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the analytics profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a custom Analytics profile object + community.network.avi_analyticsprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + apdex_response_threshold: 500 + apdex_response_tolerated_factor: 4.0 + apdex_rtt_threshold: 250 + apdex_rtt_tolerated_factor: 4.0 + apdex_rum_threshold: 5000 + apdex_rum_tolerated_factor: 4.0 + apdex_server_response_threshold: 400 + apdex_server_response_tolerated_factor: 4.0 + apdex_server_rtt_threshold: 125 + apdex_server_rtt_tolerated_factor: 4.0 + conn_lossy_ooo_threshold: 50 + conn_lossy_timeo_rexmt_threshold: 20 + conn_lossy_total_rexmt_threshold: 50 + conn_lossy_zero_win_size_event_threshold: 2 + conn_server_lossy_ooo_threshold: 50 + conn_server_lossy_timeo_rexmt_threshold: 20 + conn_server_lossy_total_rexmt_threshold: 50 + conn_server_lossy_zero_win_size_event_threshold: 2 + disable_se_analytics: false + disable_server_analytics: false + exclude_client_close_before_request_as_error: false + exclude_persistence_change_as_error: false + exclude_server_tcp_reset_as_error: false + exclude_syn_retransmit_as_error: false + exclude_tcp_reset_as_error: false + hs_event_throttle_window: 1209600 + hs_max_anomaly_penalty: 10 + hs_max_resources_penalty: 25 + hs_max_security_penalty: 100 + hs_min_dos_rate: 1000 + hs_performance_boost: 20 + hs_pscore_traffic_threshold_l4_client: 10.0 + hs_pscore_traffic_threshold_l4_server: 10.0 + hs_security_certscore_expired: 0.0 + hs_security_certscore_gt30d: 5.0 + hs_security_certscore_le07d: 2.0 + hs_security_certscore_le30d: 4.0 + hs_security_chain_invalidity_penalty: 1.0 + hs_security_cipherscore_eq000b: 0.0 + hs_security_cipherscore_ge128b: 5.0 + hs_security_cipherscore_lt128b: 3.5 + hs_security_encalgo_score_none: 0.0 + hs_security_encalgo_score_rc4: 2.5 + hs_security_hsts_penalty: 0.0 + hs_security_nonpfs_penalty: 1.0 + hs_security_selfsignedcert_penalty: 1.0 + hs_security_ssl30_score: 3.5 + hs_security_tls10_score: 5.0 + hs_security_tls11_score: 5.0 + hs_security_tls12_score: 5.0 + hs_security_weak_signature_algo_penalty: 1.0 + name: jason-analytics-profile + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: AnalyticsProfile (api/analyticsprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + apdex_response_threshold=dict(type='int',), + apdex_response_tolerated_factor=dict(type='float',), + apdex_rtt_threshold=dict(type='int',), + apdex_rtt_tolerated_factor=dict(type='float',), + apdex_rum_threshold=dict(type='int',), + apdex_rum_tolerated_factor=dict(type='float',), + apdex_server_response_threshold=dict(type='int',), + apdex_server_response_tolerated_factor=dict(type='float',), + apdex_server_rtt_threshold=dict(type='int',), + apdex_server_rtt_tolerated_factor=dict(type='float',), + client_log_config=dict(type='dict',), + client_log_streaming_config=dict(type='dict',), + conn_lossy_ooo_threshold=dict(type='int',), + conn_lossy_timeo_rexmt_threshold=dict(type='int',), + conn_lossy_total_rexmt_threshold=dict(type='int',), + conn_lossy_zero_win_size_event_threshold=dict(type='int',), + conn_server_lossy_ooo_threshold=dict(type='int',), + conn_server_lossy_timeo_rexmt_threshold=dict(type='int',), + conn_server_lossy_total_rexmt_threshold=dict(type='int',), + conn_server_lossy_zero_win_size_event_threshold=dict(type='int',), + description=dict(type='str',), + disable_ondemand_metrics=dict(type='bool',), + disable_se_analytics=dict(type='bool',), + disable_server_analytics=dict(type='bool',), + disable_vs_analytics=dict(type='bool',), + enable_advanced_analytics=dict(type='bool',), + exclude_client_close_before_request_as_error=dict(type='bool',), + exclude_dns_policy_drop_as_significant=dict(type='bool',), + exclude_gs_down_as_error=dict(type='bool',), + exclude_http_error_codes=dict(type='list',), + exclude_invalid_dns_domain_as_error=dict(type='bool',), + exclude_invalid_dns_query_as_error=dict(type='bool',), + exclude_no_dns_record_as_error=dict(type='bool',), + exclude_no_valid_gs_member_as_error=dict(type='bool',), + exclude_persistence_change_as_error=dict(type='bool',), + exclude_server_dns_error_as_error=dict(type='bool',), + exclude_server_tcp_reset_as_error=dict(type='bool',), + exclude_sip_error_codes=dict(type='list',), + exclude_syn_retransmit_as_error=dict(type='bool',), + exclude_tcp_reset_as_error=dict(type='bool',), + exclude_unsupported_dns_query_as_error=dict(type='bool',), + healthscore_max_server_limit=dict(type='int',), + hs_event_throttle_window=dict(type='int',), + hs_max_anomaly_penalty=dict(type='int',), + hs_max_resources_penalty=dict(type='int',), + hs_max_security_penalty=dict(type='int',), + hs_min_dos_rate=dict(type='int',), + hs_performance_boost=dict(type='int',), + hs_pscore_traffic_threshold_l4_client=dict(type='float',), + hs_pscore_traffic_threshold_l4_server=dict(type='float',), + hs_security_certscore_expired=dict(type='float',), + hs_security_certscore_gt30d=dict(type='float',), + hs_security_certscore_le07d=dict(type='float',), + hs_security_certscore_le30d=dict(type='float',), + hs_security_chain_invalidity_penalty=dict(type='float',), + hs_security_cipherscore_eq000b=dict(type='float',), + hs_security_cipherscore_ge128b=dict(type='float',), + hs_security_cipherscore_lt128b=dict(type='float',), + hs_security_encalgo_score_none=dict(type='float',), + hs_security_encalgo_score_rc4=dict(type='float',), + hs_security_hsts_penalty=dict(type='float',), + hs_security_nonpfs_penalty=dict(type='float',), + hs_security_selfsignedcert_penalty=dict(type='float',), + hs_security_ssl30_score=dict(type='float',), + hs_security_tls10_score=dict(type='float',), + hs_security_tls11_score=dict(type='float',), + hs_security_tls12_score=dict(type='float',), + hs_security_weak_signature_algo_penalty=dict(type='float',), + name=dict(type='str', required=True), + ondemand_metrics_idle_timeout=dict(type='int',), + ranges=dict(type='list',), + resp_code_block=dict(type='list',), + sensitive_log_profile=dict(type='dict',), + sip_log_depth=dict(type='int',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'analyticsprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_session.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_session.py new file mode 100644 index 00000000..147e7c07 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_session.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: not supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_api_session +author: Gaurav Rastogi (@grastogi23) + +short_description: Avi API Module +description: + - This module can be used for calling any resources defined in Avi REST API. U(https://avinetworks.com/) + - This module is useful for invoking HTTP Patch methods and accessing resources that do not have an REST object associated with them. +requirements: [ avisdk ] +options: + http_method: + description: + - Allowed HTTP methods for RESTful services and are supported by Avi Controller. + choices: ["get", "put", "post", "patch", "delete"] + required: true + data: + description: + - HTTP body in YAML or JSON format. + params: + description: + - Query parameters passed to the HTTP API. + path: + description: + - 'Path for Avi API resource. For example, C(path: virtualservice) will translate to C(api/virtualserivce).' + timeout: + description: + - Timeout (in seconds) for Avi API calls. + default: 60 +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + + - name: Get Pool Information using avi_api_session + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: get + path: pool + params: + name: "{{ pool_name }}" + api_version: 16.4 + register: pool_results + + - name: Patch Pool with list of servers + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: patch + path: "{{ pool_path }}" + api_version: 16.4 + data: + add: + servers: + - ip: + addr: 10.10.10.10 + type: V4 + - ip: + addr: 20.20.20.20 + type: V4 + register: updated_pool + + - name: Fetch Pool metrics bandwidth and connections rate + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: get + path: analytics/metrics/pool + api_version: 16.4 + params: + name: "{{ pool_name }}" + metric_id: l4_server.avg_bandwidth,l4_server.avg_complete_conns + step: 300 + limit: 10 + register: pool_metrics + +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, avi_obj_cmp, + cleanup_absent_fields, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + http_method=dict(required=True, + choices=['get', 'put', 'post', 'patch', + 'delete']), + path=dict(type='str', required=True), + params=dict(type='dict'), + data=dict(type='jsonarg'), + timeout=dict(type='int', default=60) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + tenant_uuid = api_creds.tenant_uuid + tenant = api_creds.tenant + timeout = int(module.params.get('timeout')) + # path is a required argument + path = module.params.get('path', '') + params = module.params.get('params', None) + data = module.params.get('data', None) + # Get the api_version from module. + api_version = api_creds.api_version + if data is not None: + data = json.loads(data) + method = module.params['http_method'] + + existing_obj = None + changed = method != 'get' + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + + # API methods not allowed + api_get_not_allowed = ["cluster", "gslbsiteops"] + api_post_not_allowed = ["alert", "fileservice"] + api_put_not_allowed = ["backup"] + + if method == 'post' and not any(path.startswith(uri) for uri in api_post_not_allowed): + # TODO: Above condition should be updated after AV-38981 is fixed + # need to check if object already exists. In that case + # change the method to be put + try: + using_collection = False + if not any(path.startswith(uri) for uri in api_get_not_allowed): + if 'name' in data: + gparams['name'] = data['name'] + using_collection = True + if not any(path.startswith(uri) for uri in api_get_not_allowed): + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + existing_obj = rsp.json() + if using_collection: + existing_obj = existing_obj['results'][0] + except (IndexError, KeyError): + # object is not found + pass + else: + if not any(path.startswith(uri) for uri in api_get_not_allowed): + # object is present + method = 'put' + path += '/' + existing_obj['uuid'] + + if method == 'put' and not any(path.startswith(uri) for uri in api_put_not_allowed): + # put can happen with when full path is specified or it is put + post + if existing_obj is None: + using_collection = False + if ((len(path.split('/')) == 1) and ('name' in data) and + (not any(path.startswith(uri) for uri in api_get_not_allowed))): + gparams['name'] = data['name'] + using_collection = True + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + rsp_data = rsp.json() + if using_collection: + if rsp_data['results']: + existing_obj = rsp_data['results'][0] + path += '/' + existing_obj['uuid'] + else: + method = 'post' + else: + if rsp.status_code == 404: + method = 'post' + else: + existing_obj = rsp_data + if existing_obj: + changed = not avi_obj_cmp(data, existing_obj) + cleanup_absent_fields(data) + if method == 'patch': + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + existing_obj = rsp.json() + + if (method == 'put' and changed) or (method != 'put'): + fn = getattr(api, method) + rsp = fn(path, tenant=tenant, tenant_uuid=tenant, timeout=timeout, + params=params, data=data, api_version=api_version) + else: + rsp = None + if method == 'delete' and rsp.status_code == 404: + changed = False + rsp.status_code = 200 + if method == 'patch' and existing_obj and rsp.status_code < 299: + # Ideally the comparison should happen with the return values + # from the patch API call. However, currently Avi API are + # returning different hostname when GET is used vs Patch. + # tracked as AV-12561 + if path.startswith('pool'): + time.sleep(1) + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + new_obj = rsp.json() + changed = not avi_obj_cmp(new_obj, existing_obj) + if rsp is None: + return module.exit_json(changed=changed, obj=existing_obj) + return ansible_return(module, rsp, changed, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_version.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_version.py new file mode 100644 index 00000000..5b5ea5f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_api_version.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +""" +# Created on July 24, 2017 +# +# @author: Vilian Atmadzhov (vilian.atmadzhov@paddypowerbetfair.com) GitHub ID: vivobg +# +# module_check: not supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# Vilian Atmadzhov, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_api_version +author: Vilian Atmadzhov (@vivobg) + +short_description: Avi API Version Module +description: + - This module can be used to obtain the version of the Avi REST API. U(https://avinetworks.com/) +requirements: [ avisdk ] +options: {} +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Get AVI API version + community.network.avi_api_version: + controller: "" + username: "" + password: "" + tenant: "" + register: avi_controller_version +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict() + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + try: + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, + password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + remote_api_version = api.remote_api_version + remote = {} + for key in remote_api_version.keys(): + remote[key.lower()] = remote_api_version[key] + api.close() + module.exit_json(changed=False, obj=remote) + except Exception as e: + module.fail_json(msg=("Unable to get an AVI session. %s" % e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationpersistenceprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationpersistenceprofile.py new file mode 100644 index 00000000..e43967d9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationpersistenceprofile.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_applicationpersistenceprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ApplicationPersistenceProfile Avi RESTful Object +description: + - This module is used to configure ApplicationPersistenceProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + app_cookie_persistence_profile: + description: + - Specifies the application cookie persistence profile parameters. + description: + description: + - User defined description for the object. + hdr_persistence_profile: + description: + - Specifies the custom http header persistence profile parameters. + http_cookie_persistence_profile: + description: + - Specifies the http cookie persistence profile parameters. + ip_persistence_profile: + description: + - Specifies the client ip persistence profile parameters. + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - A user-friendly name for the persistence profile. + required: true + persistence_type: + description: + - Method used to persist clients to the same server for a duration of time or a session. + - Enum options - PERSISTENCE_TYPE_CLIENT_IP_ADDRESS, PERSISTENCE_TYPE_HTTP_COOKIE, PERSISTENCE_TYPE_TLS, PERSISTENCE_TYPE_CLIENT_IPV6_ADDRESS, + - PERSISTENCE_TYPE_CUSTOM_HTTP_HEADER, PERSISTENCE_TYPE_APP_COOKIE, PERSISTENCE_TYPE_GSLB_SITE. + - Default value when not specified in API or module is interpreted by Avi Controller as PERSISTENCE_TYPE_CLIENT_IP_ADDRESS. + required: true + server_hm_down_recovery: + description: + - Specifies behavior when a persistent server has been marked down by a health monitor. + - Enum options - HM_DOWN_PICK_NEW_SERVER, HM_DOWN_ABORT_CONNECTION, HM_DOWN_CONTINUE_PERSISTENT_SERVER. + - Default value when not specified in API or module is interpreted by Avi Controller as HM_DOWN_PICK_NEW_SERVER. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the persistence profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Application Persistence setting using http cookie. + community.network.avi_applicationpersistenceprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + http_cookie_persistence_profile: + always_send_cookie: false + cookie_name: My-HTTP + key: + - aes_key: ShYGZdMks8j6Bpvm2sCvaXWzvXms2Z9ob+TTjRy46lQ= + name: c1276819-550c-4adf-912d-59efa5fd7269 + - aes_key: OGsyVk84VCtyMENFOW0rMnRXVnNrb0RzdG5mT29oamJRb0dlbHZVSjR1az0= + name: a080de57-77c3-4580-a3ea-e7a6493c14fd + - aes_key: UVN0cU9HWmFUM2xOUzBVcmVXaHFXbnBLVUUxMU1VSktSVU5HWjJOWmVFMTBUMUV4UmxsNk4xQmFZejA9 + name: 60478846-33c6-484d-868d-bbc324fce4a5 + timeout: 15 + name: My-HTTP-Cookie + persistence_type: PERSISTENCE_TYPE_HTTP_COOKIE + server_hm_down_recovery: HM_DOWN_PICK_NEW_SERVER + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: ApplicationPersistenceProfile (api/applicationpersistenceprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + app_cookie_persistence_profile=dict(type='dict',), + description=dict(type='str',), + hdr_persistence_profile=dict(type='dict',), + http_cookie_persistence_profile=dict(type='dict',), + ip_persistence_profile=dict(type='dict',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + persistence_type=dict(type='str', required=True), + server_hm_down_recovery=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'applicationpersistenceprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationprofile.py new file mode 100644 index 00000000..456c66a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_applicationprofile.py @@ -0,0 +1,217 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_applicationprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ApplicationProfile Avi RESTful Object +description: + - This module is used to configure ApplicationProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of application profiles. + - Internally set by cloud connector. + - Field introduced in 17.2.14, 18.1.5, 18.2.1. + created_by: + description: + - Name of the application profile creator. + - Field introduced in 17.2.14, 18.1.5, 18.2.1. + description: + description: + - User defined description for the object. + dns_service_profile: + description: + - Specifies various dns service related controls for virtual service. + dos_rl_profile: + description: + - Specifies various security related controls for virtual service. + http_profile: + description: + - Specifies the http application proxy profile parameters. + name: + description: + - The name of the application profile. + required: true + preserve_client_ip: + description: + - Specifies if client ip needs to be preserved for backend connection. + - Not compatible with connection multiplexing. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + preserve_client_port: + description: + - Specifies if we need to preserve client port while preserving client ip for backend connections. + - Field introduced in 17.2.7. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + sip_service_profile: + description: + - Specifies various sip service related controls for virtual service. + - Field introduced in 17.2.8, 18.1.3, 18.2.1. + tcp_app_profile: + description: + - Specifies the tcp application proxy profile parameters. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Specifies which application layer proxy is enabled for the virtual service. + - Enum options - APPLICATION_PROFILE_TYPE_L4, APPLICATION_PROFILE_TYPE_HTTP, APPLICATION_PROFILE_TYPE_SYSLOG, APPLICATION_PROFILE_TYPE_DNS, + - APPLICATION_PROFILE_TYPE_SSL, APPLICATION_PROFILE_TYPE_SIP. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the application profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Application Profile for HTTP application enabled for SSL traffic + community.network.avi_applicationprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + http_profile: + cache_config: + age_header: true + aggressive: false + date_header: true + default_expire: 600 + enabled: false + heuristic_expire: false + max_cache_size: 0 + max_object_size: 4194304 + mime_types_group_refs: + - admin:System-Cacheable-Resource-Types + min_object_size: 100 + query_cacheable: false + xcache_header: true + client_body_timeout: 0 + client_header_timeout: 10000 + client_max_body_size: 0 + client_max_header_size: 12 + client_max_request_size: 48 + compression_profile: + compressible_content_ref: admin:System-Compressible-Content-Types + compression: false + remove_accept_encoding_header: true + type: AUTO_COMPRESSION + connection_multiplexing_enabled: true + hsts_enabled: false + hsts_max_age: 365 + http_to_https: false + httponly_enabled: false + keepalive_header: false + keepalive_timeout: 30000 + max_bad_rps_cip: 0 + max_bad_rps_cip_uri: 0 + max_bad_rps_uri: 0 + max_rps_cip: 0 + max_rps_cip_uri: 0 + max_rps_unknown_cip: 0 + max_rps_unknown_uri: 0 + max_rps_uri: 0 + post_accept_timeout: 30000 + secure_cookie_enabled: false + server_side_redirect_to_https: false + spdy_enabled: false + spdy_fwd_proxy_mode: false + ssl_client_certificate_mode: SSL_CLIENT_CERTIFICATE_NONE + ssl_everywhere_enabled: false + websockets_enabled: true + x_forwarded_proto_enabled: false + xff_alternate_name: X-Forwarded-For + xff_enabled: true + name: System-HTTP + tenant_ref: admin + type: APPLICATION_PROFILE_TYPE_HTTP +""" + +RETURN = ''' +obj: + description: ApplicationProfile (api/applicationprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + dns_service_profile=dict(type='dict',), + dos_rl_profile=dict(type='dict',), + http_profile=dict(type='dict',), + name=dict(type='str', required=True), + preserve_client_ip=dict(type='bool',), + preserve_client_port=dict(type='bool',), + sip_service_profile=dict(type='dict',), + tcp_app_profile=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'applicationprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_authprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_authprofile.py new file mode 100644 index 00000000..88667db1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_authprofile.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_authprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AuthProfile Avi RESTful Object +description: + - This module is used to configure AuthProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + http: + description: + - Http user authentication params. + ldap: + description: + - Ldap server and directory settings. + name: + description: + - Name of the auth profile. + required: true + pa_agent_ref: + description: + - Pingaccessagent uuid. + - It is a reference to an object of type pingaccessagent. + - Field introduced in 18.2.3. + saml: + description: + - Saml settings. + - Field introduced in 17.2.3. + tacacs_plus: + description: + - Tacacs+ settings. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of the auth profile. + - Enum options - AUTH_PROFILE_LDAP, AUTH_PROFILE_TACACS_PLUS, AUTH_PROFILE_SAML, AUTH_PROFILE_PINGACCESS. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the auth profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create user authorization profile based on the LDAP + community.network.avi_authprofile: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + http: + cache_expiration_time: 5 + group_member_is_full_dn: false + ldap: + base_dn: dc=avi,dc=local + bind_as_administrator: true + port: 389 + security_mode: AUTH_LDAP_SECURE_NONE + server: + - 10.10.0.100 + settings: + admin_bind_dn: user@avi.local + group_filter: (objectClass=*) + group_member_attribute: member + group_member_is_full_dn: true + group_search_dn: dc=avi,dc=local + group_search_scope: AUTH_LDAP_SCOPE_SUBTREE + ignore_referrals: true + password: password + user_id_attribute: samAccountname + user_search_dn: dc=avi,dc=local + user_search_scope: AUTH_LDAP_SCOPE_ONE + name: ProdAuth + tenant_ref: admin + type: AUTH_PROFILE_LDAP +""" + +RETURN = ''' +obj: + description: AuthProfile (api/authprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + http=dict(type='dict',), + ldap=dict(type='dict',), + name=dict(type='str', required=True), + pa_agent_ref=dict(type='str',), + saml=dict(type='dict',), + tacacs_plus=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'authprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_autoscalelaunchconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_autoscalelaunchconfig.py new file mode 100644 index 00000000..39e93f83 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_autoscalelaunchconfig.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_autoscalelaunchconfig +author: Chaitanya Deshpande (@chaitanyaavi) + +short_description: Module for setup of AutoScaleLaunchConfig Avi RESTful Object +description: + - This module is used to configure AutoScaleLaunchConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + image_id: + description: + - Unique id of the amazon machine image (ami) or openstack vm id. + mesos: + description: + - Autoscalemesossettings settings for autoscalelaunchconfig. + name: + description: + - Name of the object. + required: true + openstack: + description: + - Autoscaleopenstacksettings settings for autoscalelaunchconfig. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_external_asg: + description: + - If set to true, serverautoscalepolicy will use the autoscaling group (external_autoscaling_groups) from pool to perform scale up and scale down. + - Pool should have single autoscaling group configured. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Autoscale Launch configuration. + community.network.avi_autoscalelaunchconfig: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + image_id: default + name: default-autoscalelaunchconfig + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: AutoScaleLaunchConfig (api/autoscalelaunchconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + image_id=dict(type='str',), + mesos=dict(type='dict',), + name=dict(type='str', required=True), + openstack=dict(type='dict',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_external_asg=dict(type='bool',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'autoscalelaunchconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backup.py new file mode 100644 index 00000000..f052d0a4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backup.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_backup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Backup Avi RESTful Object +description: + - This module is used to configure Backup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + backup_config_ref: + description: + - Backupconfiguration information. + - It is a reference to an object of type backupconfiguration. + file_name: + description: + - The file name of backup. + required: true + local_file_url: + description: + - Url to download the backup file. + remote_file_url: + description: + - Url to download the backup file. + scheduler_ref: + description: + - Scheduler information. + - It is a reference to an object of type scheduler. + tenant_ref: + description: + - It is a reference to an object of type tenant. + timestamp: + description: + - Unix timestamp of when the backup file is created. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Backup object + community.network.avi_backup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_backup +""" + +RETURN = ''' +obj: + description: Backup (api/backup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + backup_config_ref=dict(type='str',), + file_name=dict(type='str', required=True), + local_file_url=dict(type='str',), + remote_file_url=dict(type='str',), + scheduler_ref=dict(type='str',), + tenant_ref=dict(type='str',), + timestamp=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'backup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backupconfiguration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backupconfiguration.py new file mode 100644 index 00000000..7ba546a0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_backupconfiguration.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_backupconfiguration +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of BackupConfiguration Avi RESTful Object +description: + - This module is used to configure BackupConfiguration object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + aws_access_key: + description: + - Aws access key id. + - Field introduced in 18.2.3. + aws_bucket_id: + description: + - Aws bucket. + - Field introduced in 18.2.3. + aws_secret_access: + description: + - Aws secret access key. + - Field introduced in 18.2.3. + backup_file_prefix: + description: + - Prefix of the exported configuration file. + - Field introduced in 17.1.1. + backup_passphrase: + description: + - Passphrase of backup configuration. + maximum_backups_stored: + description: + - Rotate the backup files based on this count. + - Allowed values are 1-20. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + name: + description: + - Name of backup configuration. + required: true + remote_directory: + description: + - Directory at remote destination with write permission for ssh user. + remote_hostname: + description: + - Remote destination. + save_local: + description: + - Local backup. + type: bool + ssh_user_ref: + description: + - Access credentials for remote destination. + - It is a reference to an object of type cloudconnectoruser. + tenant_ref: + description: + - It is a reference to an object of type tenant. + upload_to_remote_host: + description: + - Remote backup. + type: bool + upload_to_s3: + description: + - Cloud backup. + - Field introduced in 18.2.3. + type: bool + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create BackupConfiguration object + community.network.avi_backupconfiguration: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_backupconfiguration +""" + +RETURN = ''' +obj: + description: BackupConfiguration (api/backupconfiguration) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + aws_access_key=dict(type='str', no_log=True,), + aws_bucket_id=dict(type='str',), + aws_secret_access=dict(type='str', no_log=True,), + backup_file_prefix=dict(type='str',), + backup_passphrase=dict(type='str', no_log=True,), + maximum_backups_stored=dict(type='int',), + name=dict(type='str', required=True), + remote_directory=dict(type='str',), + remote_hostname=dict(type='str',), + save_local=dict(type='bool',), + ssh_user_ref=dict(type='str',), + tenant_ref=dict(type='str',), + upload_to_remote_host=dict(type='bool',), + upload_to_s3=dict(type='bool',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'backupconfiguration', + set(['backup_passphrase', 'aws_access_key', 'aws_secret_access'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_certificatemanagementprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_certificatemanagementprofile.py new file mode 100644 index 00000000..aaac46e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_certificatemanagementprofile.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_certificatemanagementprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CertificateManagementProfile Avi RESTful Object +description: + - This module is used to configure CertificateManagementProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the pki profile. + required: true + script_params: + description: + - List of customparams. + script_path: + description: + - Script_path of certificatemanagementprofile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CertificateManagementProfile object + community.network.avi_certificatemanagementprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_certificatemanagementprofile +""" + +RETURN = ''' +obj: + description: CertificateManagementProfile (api/certificatemanagementprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + script_params=dict(type='list',), + script_path=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'certificatemanagementprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloud.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloud.py new file mode 100644 index 00000000..568d68ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloud.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloud +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Cloud Avi RESTful Object +description: + - This module is used to configure Cloud object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + apic_configuration: + description: + - Apicconfiguration settings for cloud. + apic_mode: + description: + - Boolean flag to set apic_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + autoscale_polling_interval: + description: + - Cloudconnector polling interval for external autoscale groups. + - Field introduced in 18.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + aws_configuration: + description: + - Awsconfiguration settings for cloud. + azure_configuration: + description: + - Field introduced in 17.2.1. + cloudstack_configuration: + description: + - Cloudstackconfiguration settings for cloud. + custom_tags: + description: + - Custom tags for all avi created resources in the cloud infrastructure. + - Field introduced in 17.1.5. + dhcp_enabled: + description: + - Select the ip address management scheme. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + dns_provider_ref: + description: + - Dns profile for the cloud. + - It is a reference to an object of type ipamdnsproviderprofile. + docker_configuration: + description: + - Dockerconfiguration settings for cloud. + east_west_dns_provider_ref: + description: + - Dns profile for east-west services. + - It is a reference to an object of type ipamdnsproviderprofile. + east_west_ipam_provider_ref: + description: + - Ipam profile for east-west services. + - Warning - please use virtual subnets in this ipam profile that do not conflict with the underlay networks or any overlay networks in the cluster. + - For example in aws and gcp, 169.254.0.0/16 is used for storing instance metadata. + - Hence, it should not be used in this profile. + - It is a reference to an object of type ipamdnsproviderprofile. + enable_vip_static_routes: + description: + - Use static routes for vip side network resolution during virtualservice placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + gcp_configuration: + description: + - Google cloud platform configuration. + - Field introduced in 18.2.1. + ip6_autocfg_enabled: + description: + - Enable ipv6 auto configuration. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ipam_provider_ref: + description: + - Ipam profile for the cloud. + - It is a reference to an object of type ipamdnsproviderprofile. + license_tier: + description: + - Specifies the default license tier which would be used by new se groups. + - This field by default inherits the value from system configuration. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + license_type: + description: + - If no license type is specified then default license enforcement for the cloud type is chosen. + - The default mappings are container cloud is max ses, openstack and vmware is cores and linux it is sockets. + - Enum options - LIC_BACKEND_SERVERS, LIC_SOCKETS, LIC_CORES, LIC_HOSTS, LIC_SE_BANDWIDTH, LIC_METERED_SE_BANDWIDTH. + linuxserver_configuration: + description: + - Linuxserverconfiguration settings for cloud. + mesos_configuration: + description: + - Field deprecated in 18.2.2. + mtu: + description: + - Mtu setting for the cloud. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + name: + description: + - Name of the object. + required: true + nsx_configuration: + description: + - Configuration parameters for nsx manager. + - Field introduced in 17.1.1. + obj_name_prefix: + description: + - Default prefix for all automatically created objects in this cloud. + - This prefix can be overridden by the se-group template. + openstack_configuration: + description: + - Openstackconfiguration settings for cloud. + oshiftk8s_configuration: + description: + - Oshiftk8sconfiguration settings for cloud. + prefer_static_routes: + description: + - Prefer static routes over interface routes during virtualservice placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + proxy_configuration: + description: + - Proxyconfiguration settings for cloud. + rancher_configuration: + description: + - Rancherconfiguration settings for cloud. + state_based_dns_registration: + description: + - Dns records for vips are added/deleted based on the operational state of the vips. + - Field introduced in 17.1.12. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vca_configuration: + description: + - Vcloudairconfiguration settings for cloud. + vcenter_configuration: + description: + - Vcenterconfiguration settings for cloud. + vtype: + description: + - Cloud type. + - Enum options - CLOUD_NONE, CLOUD_VCENTER, CLOUD_OPENSTACK, CLOUD_AWS, CLOUD_VCA, CLOUD_APIC, CLOUD_MESOS, CLOUD_LINUXSERVER, CLOUD_DOCKER_UCP, + - CLOUD_RANCHER, CLOUD_OSHIFT_K8S, CLOUD_AZURE, CLOUD_GCP. + - Default value when not specified in API or module is interpreted by Avi Controller as CLOUD_NONE. + required: true +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a VMware cloud with write access mode + community.network.avi_cloud: + username: '{{ username }}' + controller: '{{ controller }}' + password: '{{ password }}' + apic_mode: false + dhcp_enabled: true + enable_vip_static_routes: false + license_type: LIC_CORES + mtu: 1500 + name: vCenter Cloud + prefer_static_routes: false + tenant_ref: admin + vcenter_configuration: + datacenter_ref: /api/vimgrdcruntime/datacenter-2-10.10.20.100 + management_network: /api/vimgrnwruntime/dvportgroup-103-10.10.20.100 + password: password + privilege: WRITE_ACCESS + username: user + vcenter_url: 10.10.20.100 + vtype: CLOUD_VCENTER +""" + +RETURN = ''' +obj: + description: Cloud (api/cloud) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + apic_configuration=dict(type='dict',), + apic_mode=dict(type='bool',), + autoscale_polling_interval=dict(type='int',), + aws_configuration=dict(type='dict',), + azure_configuration=dict(type='dict',), + cloudstack_configuration=dict(type='dict',), + custom_tags=dict(type='list',), + dhcp_enabled=dict(type='bool',), + dns_provider_ref=dict(type='str',), + docker_configuration=dict(type='dict',), + east_west_dns_provider_ref=dict(type='str',), + east_west_ipam_provider_ref=dict(type='str',), + enable_vip_static_routes=dict(type='bool',), + gcp_configuration=dict(type='dict',), + ip6_autocfg_enabled=dict(type='bool',), + ipam_provider_ref=dict(type='str',), + license_tier=dict(type='str',), + license_type=dict(type='str',), + linuxserver_configuration=dict(type='dict',), + mesos_configuration=dict(type='dict',), + mtu=dict(type='int',), + name=dict(type='str', required=True), + nsx_configuration=dict(type='dict',), + obj_name_prefix=dict(type='str',), + openstack_configuration=dict(type='dict',), + oshiftk8s_configuration=dict(type='dict',), + prefer_static_routes=dict(type='bool',), + proxy_configuration=dict(type='dict',), + rancher_configuration=dict(type='dict',), + state_based_dns_registration=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + vca_configuration=dict(type='dict',), + vcenter_configuration=dict(type='dict',), + vtype=dict(type='str', required=True), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloud', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudconnectoruser.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudconnectoruser.py new file mode 100644 index 00000000..8e40269c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudconnectoruser.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloudconnectoruser +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CloudConnectorUser Avi RESTful Object +description: + - This module is used to configure CloudConnectorUser object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + azure_serviceprincipal: + description: + - Field introduced in 17.2.1. + azure_userpass: + description: + - Field introduced in 17.2.1. + gcp_credentials: + description: + - Credentials for google cloud platform. + - Field introduced in 18.2.1. + name: + description: + - Name of the object. + required: true + oci_credentials: + description: + - Credentials for oracle cloud infrastructure. + - Field introduced in 18.2.1,18.1.3. + private_key: + description: + - Private_key of cloudconnectoruser. + public_key: + description: + - Public_key of cloudconnectoruser. + tenant_ref: + description: + - It is a reference to an object of type tenant. + tencent_credentials: + description: + - Credentials for tencent cloud. + - Field introduced in 18.2.3. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a Cloud connector user that is used for integration into cloud platforms + community.network.avi_cloudconnectoruser: + controller: '{{ controller }}' + name: root + password: '{{ password }}' + private_key: | + -----BEGIN RSA PRIVATE KEY----- + -----END RSA PRIVATE KEY-----' + public_key: 'ssh-rsa ...' + tenant_ref: admin + username: '{{ username }}' +""" + +RETURN = ''' +obj: + description: CloudConnectorUser (api/cloudconnectoruser) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + azure_serviceprincipal=dict(type='dict',), + azure_userpass=dict(type='dict',), + gcp_credentials=dict(type='dict',), + name=dict(type='str', required=True), + oci_credentials=dict(type='dict',), + private_key=dict(type='str', no_log=True,), + public_key=dict(type='str',), + tenant_ref=dict(type='str',), + tencent_credentials=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloudconnectoruser', + set(['private_key'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudproperties.py new file mode 100644 index 00000000..d054a9c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cloudproperties.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloudproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CloudProperties Avi RESTful Object +description: + - This module is used to configure CloudProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cc_props: + description: + - Cloudconnector properties. + cc_vtypes: + description: + - Cloud types supported by cloudconnector. + - Enum options - CLOUD_NONE, CLOUD_VCENTER, CLOUD_OPENSTACK, CLOUD_AWS, CLOUD_VCA, CLOUD_APIC, CLOUD_MESOS, CLOUD_LINUXSERVER, CLOUD_DOCKER_UCP, + - CLOUD_RANCHER, CLOUD_OSHIFT_K8S, CLOUD_AZURE, CLOUD_GCP. + hyp_props: + description: + - Hypervisor properties. + info: + description: + - Properties specific to a cloud type. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CloudProperties object + community.network.avi_cloudproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_cloudproperties +""" + +RETURN = ''' +obj: + description: CloudProperties (api/cloudproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cc_props=dict(type='dict',), + cc_vtypes=dict(type='list',), + hyp_props=dict(type='list',), + info=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloudproperties', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cluster.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cluster.py new file mode 100644 index 00000000..e31feae3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_cluster.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cluster +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Cluster Avi RESTful Object +description: + - This module is used to configure Cluster object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the object. + required: true + nodes: + description: + - List of clusternode. + rejoin_nodes_automatically: + description: + - Re-join cluster nodes automatically in the event one of the node is reset to factory. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + virtual_ip: + description: + - A virtual ip address. + - This ip address will be dynamically reconfigured so that it always is the ip of the cluster leader. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Cluster object + community.network.avi_cluster: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_cluster +""" + +RETURN = ''' +obj: + description: Cluster (api/cluster) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + nodes=dict(type='list',), + rejoin_nodes_automatically=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + virtual_ip=dict(type='dict',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cluster', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_clusterclouddetails.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_clusterclouddetails.py new file mode 100644 index 00000000..938f3515 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_clusterclouddetails.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_clusterclouddetails +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ClusterCloudDetails Avi RESTful Object +description: + - This module is used to configure ClusterCloudDetails object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + azure_info: + description: + - Azure info to configure cluster_vip on the controller. + - Field introduced in 17.2.5. + name: + description: + - Field introduced in 17.2.5. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.5. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.5. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ClusterCloudDetails object + community.network.avi_clusterclouddetails: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_clusterclouddetails +""" + +RETURN = ''' +obj: + description: ClusterCloudDetails (api/clusterclouddetails) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + azure_info=dict(type='dict',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'clusterclouddetails', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_controllerproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_controllerproperties.py new file mode 100644 index 00000000..4b68d379 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_controllerproperties.py @@ -0,0 +1,420 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_controllerproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ControllerProperties Avi RESTful Object +description: + - This module is used to configure ControllerProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + allow_ip_forwarding: + description: + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + allow_unauthenticated_apis: + description: + - Allow unauthenticated access for special apis. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + allow_unauthenticated_nodes: + description: + - Boolean flag to set allow_unauthenticated_nodes. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + api_idle_timeout: + description: + - Allowed values are 0-1440. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + api_perf_logging_threshold: + description: + - Threshold to log request timing in portal_performance.log and server-timing response header. + - Any stage taking longer than 1% of the threshold will be included in the server-timing header. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 10000. + appviewx_compat_mode: + description: + - Export configuration in appviewx compatibility mode. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + attach_ip_retry_interval: + description: + - Number of attach_ip_retry_interval. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + attach_ip_retry_limit: + description: + - Number of attach_ip_retry_limit. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + bm_use_ansible: + description: + - Use ansible for se creation in baremetal. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + cleanup_expired_authtoken_timeout_period: + description: + - Period for auth token cleanup job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + cleanup_sessions_timeout_period: + description: + - Period for sessions cleanup job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + cloud_reconcile: + description: + - Enable/disable periodic reconcile for all the clouds. + - Field introduced in 17.2.14,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + cluster_ip_gratuitous_arp_period: + description: + - Period for cluster ip gratuitous arp job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + consistency_check_timeout_period: + description: + - Period for consistency check job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + crashed_se_reboot: + description: + - Number of crashed_se_reboot. + - Default value when not specified in API or module is interpreted by Avi Controller as 900. + dead_se_detection_timer: + description: + - Number of dead_se_detection_timer. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + dns_refresh_period: + description: + - Period for refresh pool and gslb dns job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + dummy: + description: + - Number of dummy. + enable_api_sharding: + description: + - This setting enables the controller leader to shard api requests to the followers (if any). + - Field introduced in 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_memory_balancer: + description: + - Enable/disable memory balancer. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + fatal_error_lease_time: + description: + - Number of fatal_error_lease_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + max_dead_se_in_grp: + description: + - Number of max_dead_se_in_grp. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_pcap_per_tenant: + description: + - Maximum number of pcap files stored per tenant. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + max_seq_attach_ip_failures: + description: + - Maximum number of consecutive attach ip failures that halts vs placement. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + max_seq_vnic_failures: + description: + - Number of max_seq_vnic_failures. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + persistence_key_rotate_period: + description: + - Period for rotate app persistence keys job. + - Allowed values are 1-1051200. + - Special values are 0 - 'disabled'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + portal_token: + description: + - Token used for uploading tech-support to portal. + - Field introduced in 16.4.6,17.1.2. + process_locked_useraccounts_timeout_period: + description: + - Period for process locked user accounts job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + process_pki_profile_timeout_period: + description: + - Period for process pki profile job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1440. + query_host_fail: + description: + - Number of query_host_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + safenet_hsm_version: + description: + - Version of the safenet package installed on the controller. + - Field introduced in 16.5.2,17.2.3. + se_create_timeout: + description: + - Number of se_create_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 900. + se_failover_attempt_interval: + description: + - Interval between attempting failovers to an se. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + se_from_marketplace: + description: + - This setting decides whether se is to be deployed from the cloud marketplace or to be created by the controller. + - The setting is applicable only when byol license is selected. + - Enum options - MARKETPLACE, IMAGE. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as IMAGE. + se_offline_del: + description: + - Number of se_offline_del. + - Default value when not specified in API or module is interpreted by Avi Controller as 172000. + se_vnic_cooldown: + description: + - Number of se_vnic_cooldown. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + secure_channel_cleanup_timeout: + description: + - Period for secure channel cleanup job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + secure_channel_controller_token_timeout: + description: + - Number of secure_channel_controller_token_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + secure_channel_se_token_timeout: + description: + - Number of secure_channel_se_token_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + seupgrade_fabric_pool_size: + description: + - Pool size used for all fabric commands during se upgrade. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + seupgrade_segroup_min_dead_timeout: + description: + - Time to wait before marking segroup upgrade as stuck. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + ssl_certificate_expiry_warning_days: + description: + - Number of days for ssl certificate expiry warning. + unresponsive_se_reboot: + description: + - Number of unresponsive_se_reboot. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + upgrade_dns_ttl: + description: + - Time to account for dns ttl during upgrade. + - This is in addition to vs_scalein_timeout_for_upgrade in se_group. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 5. + upgrade_lease_time: + description: + - Number of upgrade_lease_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vnic_op_fail_time: + description: + - Number of vnic_op_fail_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + vs_apic_scaleout_timeout: + description: + - Time to wait for the scaled out se to become ready before marking the scaleout done, applies to apic configuration only. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + vs_awaiting_se_timeout: + description: + - Number of vs_awaiting_se_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_key_rotate_period: + description: + - Period for rotate vs keys job. + - Allowed values are 1-1051200. + - Special values are 0 - 'disabled'. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + vs_scaleout_ready_check_interval: + description: + - Interval for checking scaleout_ready status while controller is waiting for scaleoutready rpc from the service engine. + - Field introduced in 18.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_se_attach_ip_fail: + description: + - Time to wait before marking attach ip operation on an se as failed. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + vs_se_bootup_fail: + description: + - Number of vs_se_bootup_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 480. + vs_se_create_fail: + description: + - Number of vs_se_create_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + vs_se_ping_fail: + description: + - Number of vs_se_ping_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_se_vnic_fail: + description: + - Number of vs_se_vnic_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + vs_se_vnic_ip_fail: + description: + - Number of vs_se_vnic_ip_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + warmstart_se_reconnect_wait_time: + description: + - Number of warmstart_se_reconnect_wait_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 480. + warmstart_vs_resync_wait_time: + description: + - Timeout for warmstart vs resync. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ControllerProperties object + community.network.avi_controllerproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_controllerproperties +""" + +RETURN = ''' +obj: + description: ControllerProperties (api/controllerproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + allow_ip_forwarding=dict(type='bool',), + allow_unauthenticated_apis=dict(type='bool',), + allow_unauthenticated_nodes=dict(type='bool',), + api_idle_timeout=dict(type='int',), + api_perf_logging_threshold=dict(type='int',), + appviewx_compat_mode=dict(type='bool',), + attach_ip_retry_interval=dict(type='int',), + attach_ip_retry_limit=dict(type='int',), + bm_use_ansible=dict(type='bool',), + cleanup_expired_authtoken_timeout_period=dict(type='int',), + cleanup_sessions_timeout_period=dict(type='int',), + cloud_reconcile=dict(type='bool',), + cluster_ip_gratuitous_arp_period=dict(type='int',), + consistency_check_timeout_period=dict(type='int',), + crashed_se_reboot=dict(type='int',), + dead_se_detection_timer=dict(type='int',), + dns_refresh_period=dict(type='int',), + dummy=dict(type='int',), + enable_api_sharding=dict(type='bool',), + enable_memory_balancer=dict(type='bool',), + fatal_error_lease_time=dict(type='int',), + max_dead_se_in_grp=dict(type='int',), + max_pcap_per_tenant=dict(type='int',), + max_seq_attach_ip_failures=dict(type='int',), + max_seq_vnic_failures=dict(type='int',), + persistence_key_rotate_period=dict(type='int',), + portal_token=dict(type='str', no_log=True,), + process_locked_useraccounts_timeout_period=dict(type='int',), + process_pki_profile_timeout_period=dict(type='int',), + query_host_fail=dict(type='int',), + safenet_hsm_version=dict(type='str',), + se_create_timeout=dict(type='int',), + se_failover_attempt_interval=dict(type='int',), + se_from_marketplace=dict(type='str',), + se_offline_del=dict(type='int',), + se_vnic_cooldown=dict(type='int',), + secure_channel_cleanup_timeout=dict(type='int',), + secure_channel_controller_token_timeout=dict(type='int',), + secure_channel_se_token_timeout=dict(type='int',), + seupgrade_fabric_pool_size=dict(type='int',), + seupgrade_segroup_min_dead_timeout=dict(type='int',), + ssl_certificate_expiry_warning_days=dict(type='list',), + unresponsive_se_reboot=dict(type='int',), + upgrade_dns_ttl=dict(type='int',), + upgrade_lease_time=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + vnic_op_fail_time=dict(type='int',), + vs_apic_scaleout_timeout=dict(type='int',), + vs_awaiting_se_timeout=dict(type='int',), + vs_key_rotate_period=dict(type='int',), + vs_scaleout_ready_check_interval=dict(type='int',), + vs_se_attach_ip_fail=dict(type='int',), + vs_se_bootup_fail=dict(type='int',), + vs_se_create_fail=dict(type='int',), + vs_se_ping_fail=dict(type='int',), + vs_se_vnic_fail=dict(type='int',), + vs_se_vnic_ip_fail=dict(type='int',), + warmstart_se_reconnect_wait_time=dict(type='int',), + warmstart_vs_resync_wait_time=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'controllerproperties', + set(['portal_token'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_customipamdnsprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_customipamdnsprofile.py new file mode 100644 index 00000000..074bda7b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_customipamdnsprofile.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_customipamdnsprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CustomIpamDnsProfile Avi RESTful Object +description: + - This module is used to configure CustomIpamDnsProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the custom ipam dns profile. + - Field introduced in 17.1.1. + required: true + script_params: + description: + - Parameters that are always passed to the ipam/dns script. + - Field introduced in 17.1.1. + script_uri: + description: + - Script uri of form controller //ipamdnsscripts/. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CustomIpamDnsProfile object + community.network.avi_customipamdnsprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_customipamdnsprofile +""" + +RETURN = ''' +obj: + description: CustomIpamDnsProfile (api/customipamdnsprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + script_params=dict(type='list',), + script_uri=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'customipamdnsprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_dnspolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_dnspolicy.py new file mode 100644 index 00000000..be23d5ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_dnspolicy.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_dnspolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of DnsPolicy Avi RESTful Object +description: + - This module is used to configure DnsPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.1.1. + description: + description: + - Field introduced in 17.1.1. + name: + description: + - Name of the dns policy. + - Field introduced in 17.1.1. + required: true + rule: + description: + - Dns rules. + - Field introduced in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the dns policy. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create DnsPolicy object + community.network.avi_dnspolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_dnspolicy +""" + +RETURN = ''' +obj: + description: DnsPolicy (api/dnspolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + rule=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'dnspolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpagebody.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpagebody.py new file mode 100644 index 00000000..709835a9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpagebody.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_errorpagebody +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ErrorPageBody Avi RESTful Object +description: + - This module is used to configure ErrorPageBody object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + error_page_body: + description: + - Error page body sent to client when match. + - Field introduced in 17.2.4. + format: + description: + - Format of an error page body html or json. + - Enum options - ERROR_PAGE_FORMAT_HTML, ERROR_PAGE_FORMAT_JSON. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as ERROR_PAGE_FORMAT_HTML. + name: + description: + - Field introduced in 17.2.4. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.4. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.4. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ErrorPageBody object + community.network.avi_errorpagebody: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_errorpagebody +""" + +RETURN = ''' +obj: + description: ErrorPageBody (api/errorpagebody) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + error_page_body=dict(type='str',), + format=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'errorpagebody', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpageprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpageprofile.py new file mode 100644 index 00000000..e594d15f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_errorpageprofile.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_errorpageprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ErrorPageProfile Avi RESTful Object +description: + - This module is used to configure ErrorPageProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + app_name: + description: + - Name of the virtual service which generated the error page. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as VS Name. + company_name: + description: + - Name of the company to show in error page. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as Avi Networks. + error_pages: + description: + - Defined error pages for http status codes. + - Field introduced in 17.2.4. + host_name: + description: + - Fully qualified domain name for which the error page is generated. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as Host Header. + name: + description: + - Field introduced in 17.2.4. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.4. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.4. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ErrorPageProfile object + community.network.avi_errorpageprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_errorpageprofile +""" + +RETURN = ''' +obj: + description: ErrorPageProfile (api/errorpageprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + app_name=dict(type='str',), + company_name=dict(type='str',), + error_pages=dict(type='list',), + host_name=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'errorpageprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslb.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslb.py new file mode 100644 index 00000000..ac6de5a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslb.py @@ -0,0 +1,353 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslb +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Gslb Avi RESTful Object +description: + - This module is used to configure Gslb object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + async_interval: + description: + - Frequency with which messages are propagated to vs mgr. + - Value of 0 disables async behavior and rpc are sent inline. + - Allowed values are 0-5. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + clear_on_max_retries: + description: + - Max retries after which the remote site is treated as a fresh start. + - In fresh start all the configs are downloaded. + - Allowed values are 1-1024. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + client_ip_addr_group: + description: + - Group to specify if the client ip addresses are public or private. + - Field introduced in 17.1.2. + description: + description: + - User defined description for the object. + dns_configs: + description: + - Sub domain configuration for the gslb. + - Gslb service's fqdn must be a match one of these subdomains. + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + leader_cluster_uuid: + description: + - Mark this site as leader of gslb configuration. + - This site is the one among the avi sites. + required: true + maintenance_mode: + description: + - This field disables the configuration operations on the leader for all federated objects. + - Cud operations on gslb, gslbservice, gslbgeodbprofile and other federated objects will be rejected. + - The rest-api disabling helps in upgrade scenarios where we don't want configuration sync operations to the gslb member when the member is being + - upgraded. + - This configuration programmatically blocks the leader from accepting new gslb configuration when member sites are undergoing upgrade. + - Field introduced in 17.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name for the gslb object. + required: true + send_interval: + description: + - Frequency with which group members communicate. + - Allowed values are 1-3600. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + send_interval_prior_to_maintenance_mode: + description: + - The user can specify a send-interval while entering maintenance mode. + - The validity of this 'maintenance send-interval' is only during maintenance mode. + - When the user leaves maintenance mode, the original send-interval is reinstated. + - This internal variable is used to store the original send-interval. + - Field introduced in 18.2.3. + sites: + description: + - Select avi site member belonging to this gslb. + tenant_ref: + description: + - It is a reference to an object of type tenant. + third_party_sites: + description: + - Third party site member belonging to this gslb. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the gslb object. + view_id: + description: + - The view-id is used in change-leader mode to differentiate partitioned groups while they have the same gslb namespace. + - Each partitioned group will be able to operate independently by using the view-id. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Gslb object + community.network.avi_gslb: + name: "test-gslb" + avi_credentials: + username: '{{ username }}' + password: '{{ password }}' + controller: '{{ controller }}' + sites: + - name: "test-site1" + username: "gslb_username" + password: "gslb_password" + ip_addresses: + - type: "V4" + addr: "10.10.28.83" + enabled: True + member_type: "GSLB_ACTIVE_MEMBER" + port: 443 + cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + - name: "test-site2" + username: "gslb_username" + password: "gslb_password" + ip_addresses: + - type: "V4" + addr: "10.10.28.86" + enabled: True + member_type: "GSLB_ACTIVE_MEMBER" + port: 443 + cluster_uuid: "cluster-0c37ae8d-ab62-410c-ad3e-06fa831950b1" + dns_configs: + - domain_name: "test1.com" + - domain_name: "test2.com" + leader_cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + +- name: Update Gslb site's configurations (Patch Add Operation) + community.network.avi_gslb: + avi_credentials: + username: '{{ username }}' + password: '{{ password }}' + controller: '{{ controller }}' + avi_api_update_method: patch + avi_api_patch_op: add + leader_cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + name: "test-gslb" + dns_configs: + - domain_name: "temp1.com" + - domain_name: "temp2.com" + gslb_sites_config: + - ip_addr: "10.10.28.83" + dns_vses: + - dns_vs_uuid: "virtualservice-f2a711cd-5e78-473f-8f47-d12de660fd62" + domain_names: + - "test1.com" + - "test2.com" + - ip_addr: "10.10.28.86" + dns_vses: + - dns_vs_uuid: "virtualservice-c1a63a16-f2a1-4f41-aab4-1e90f92a5e49" + domain_names: + - "temp1.com" + - "temp2.com" + +- name: Update Gslb site's configurations (Patch Replace Operation) + community.network.avi_gslb: + avi_credentials: + username: "{{ username }}" + password: "{{ password }}" + controller: "{{ controller }}" + # On basis of cluster leader uuid dns_configs is set for that particular leader cluster + leader_cluster_uuid: "cluster-84aa795f-8f09-42bb-97a4-5103f4a53da9" + name: "test-gslb" + avi_api_update_method: patch + avi_api_patch_op: replace + dns_configs: + - domain_name: "test3.com" + - domain_name: "temp3.com" + gslb_sites_config: + # Ip address is mapping key for dns_vses field update. For the given IP address, + # dns_vses is updated. + - ip_addr: "10.10.28.83" + dns_vses: + - dns_vs_uuid: "virtualservice-7c947ed4-77f3-4a52-909c-4f12afaf5bb0" + domain_names: + - "test3.com" + - ip_addr: "10.10.28.86" + dns_vses: + - dns_vs_uuid: "virtualservice-799b2c6d-7f2d-4c3f-94c6-6e813b20b674" + domain_names: + - "temp3.com" + +- name: Update Gslb site's configurations (Patch Delete Operation) + community.network.avi_gslb: + avi_credentials: + username: "{{ username }}" + password: "{{ password }}" + controller: "{{ controller }}" + # On basis of cluster leader uuid dns_configs is set for that particular leader cluster + leader_cluster_uuid: "cluster-84aa795f-8f09-42bb-97a4-5103f4a53da9" + name: "test-gslb" + avi_api_update_method: patch + avi_api_patch_op: delete + dns_configs: + gslb_sites_config: + - ip_addr: "10.10.28.83" + - ip_addr: "10.10.28.86" +""" + +RETURN = ''' +obj: + description: Gslb (api/gslb) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ApiSession, AviCredentials +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + async_interval=dict(type='int',), + clear_on_max_retries=dict(type='int',), + client_ip_addr_group=dict(type='dict',), + description=dict(type='str',), + dns_configs=dict(type='list',), + is_federated=dict(type='bool',), + leader_cluster_uuid=dict(type='str', required=True), + maintenance_mode=dict(type='bool',), + name=dict(type='str', required=True), + send_interval=dict(type='int',), + send_interval_prior_to_maintenance_mode=dict(type='int',), + sites=dict(type='list',), + tenant_ref=dict(type='str',), + third_party_sites=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + view_id=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_method = module.params['avi_api_update_method'] + if str(api_method).lower() == 'patch': + patch_op = module.params['avi_api_patch_op'] + # Create controller session + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + # Get existing gslb objects + rsp = api.get('gslb', api_version=api_creds.api_version) + existing_gslb = rsp.json() + gslb = existing_gslb['results'] + sites = module.params['gslb_sites_config'] + for gslb_obj in gslb: + # Update/Delete domain names in dns_configs fields in gslb object. + if 'dns_configs' in module.params: + if gslb_obj['leader_cluster_uuid'] == module.params['leader_cluster_uuid']: + if str(patch_op).lower() == 'delete': + gslb_obj['dns_configs'] = [] + elif str(patch_op).lower() == 'add': + if module.params['dns_configs'] not in gslb_obj['dns_configs']: + gslb_obj['dns_configs'].extend(module.params['dns_configs']) + else: + gslb_obj['dns_configs'] = module.params['dns_configs'] + # Update/Delete sites configuration + if sites: + for site_obj in gslb_obj['sites']: + dns_vses = site_obj.get('dns_vses', []) + for obj in sites: + config_for = obj.get('ip_addr', None) + if not config_for: + return module.fail_json(msg=( + "ip_addr of site in a configuration is mandatory. " + "Please provide ip_addr i.e. gslb site's ip.")) + if config_for == site_obj['ip_addresses'][0]['addr']: + if str(patch_op).lower() == 'delete': + site_obj['dns_vses'] = [] + else: + # Modify existing gslb sites object + for key, val in obj.items(): + if key == 'dns_vses' and str(patch_op).lower() == 'add': + found = False + # Check dns_vses field already exists on the controller + for v in dns_vses: + if val[0]['dns_vs_uuid'] != v['dns_vs_uuid']: + found = True + break + if not found: + dns_vses.extend(val) + else: + site_obj[key] = val + if str(patch_op).lower() == 'add': + site_obj['dns_vses'] = dns_vses + uni_dns_configs = [dict(tupleized) for tupleized in set(tuple(item.items()) + for item in gslb_obj['dns_configs'])] + gslb_obj['dns_configs'] = uni_dns_configs + module.params.update(gslb_obj) + module.params.update( + { + 'avi_api_update_method': 'put', + 'state': 'present' + } + ) + return avi_ansible_api(module, 'gslb', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbgeodbprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbgeodbprofile.py new file mode 100644 index 00000000..4a1ddf13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbgeodbprofile.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbgeodbprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of GslbGeoDbProfile Avi RESTful Object +description: + - This module is used to configure GslbGeoDbProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - Field introduced in 17.1.1. + entries: + description: + - List of geodb entries. + - An entry can either be a geodb file or an ip address group with geo properties. + - Field introduced in 17.1.1. + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - A user-friendly name for the geodb profile. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the geodb profile. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create GslbGeoDbProfile object + community.network.avi_gslbgeodbprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_gslbgeodbprofile +""" + +RETURN = ''' +obj: + description: GslbGeoDbProfile (api/gslbgeodbprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + entries=dict(type='list',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'gslbgeodbprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice.py new file mode 100644 index 00000000..4d63ea87 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbservice +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of GslbService Avi RESTful Object +description: + - This module is used to configure GslbService object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + application_persistence_profile_ref: + description: + - The federated application persistence associated with gslbservice site persistence functionality. + - It is a reference to an object of type applicationpersistenceprofile. + - Field introduced in 17.2.1. + controller_health_status_enabled: + description: + - Gs member's overall health status is derived based on a combination of controller and datapath health-status inputs. + - Note that the datapath status is determined by the association of health monitor profiles. + - Only the controller provided status is determined through this configuration. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + created_by: + description: + - Creator name. + - Field introduced in 17.1.2. + description: + description: + - User defined description for the object. + domain_names: + description: + - Fully qualified domain name of the gslb service. + down_response: + description: + - Response to the client query when the gslb service is down. + enabled: + description: + - Enable or disable the gslb service. + - If the gslb service is enabled, then the vips are sent in the dns responses based on reachability and configured algorithm. + - If the gslb service is disabled, then the vips are no longer available in the dns response. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + groups: + description: + - Select list of pools belonging to this gslb service. + health_monitor_refs: + description: + - Verify vs health by applying one or more health monitors. + - Active monitors generate synthetic traffic from dns service engine and to mark a vs up or down based on the response. + - It is a reference to an object of type healthmonitor. + health_monitor_scope: + description: + - Health monitor probe can be executed for all the members or it can be executed only for third-party members. + - This operational mode is useful to reduce the number of health monitor probes in case of a hybrid scenario. + - In such a case, avi members can have controller derived status while non-avi members can be probed by via health monitor probes in dataplane. + - Enum options - GSLB_SERVICE_HEALTH_MONITOR_ALL_MEMBERS, GSLB_SERVICE_HEALTH_MONITOR_ONLY_NON_AVI_MEMBERS. + - Default value when not specified in API or module is interpreted by Avi Controller as GSLB_SERVICE_HEALTH_MONITOR_ALL_MEMBERS. + hm_off: + description: + - This field is an internal field and is used in se. + - Field introduced in 18.2.2. + type: bool + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + min_members: + description: + - The minimum number of members to distribute traffic to. + - Allowed values are 1-65535. + - Special values are 0 - 'disable'. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + name: + description: + - Name for the gslb service. + required: true + num_dns_ip: + description: + - Number of ip addresses of this gslb service to be returned by the dns service. + - Enter 0 to return all ip addresses. + - Allowed values are 1-20. + - Special values are 0- 'return all ip addresses'. + pool_algorithm: + description: + - The load balancing algorithm will pick a gslb pool within the gslb service list of available pools. + - Enum options - GSLB_SERVICE_ALGORITHM_PRIORITY, GSLB_SERVICE_ALGORITHM_GEO. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as GSLB_SERVICE_ALGORITHM_PRIORITY. + site_persistence_enabled: + description: + - Enable site-persistence for the gslbservice. + - Field introduced in 17.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + ttl: + description: + - Ttl value (in seconds) for records served for this gslb service by the dns service. + - Allowed values are 0-86400. + url: + description: + - Avi controller URL of the object. + use_edns_client_subnet: + description: + - Use the client ip subnet from the edns option as source ipaddress for client geo-location and consistent hash algorithm. + - Default is true. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + uuid: + description: + - Uuid of the gslb service. + wildcard_match: + description: + - Enable wild-card match of fqdn if an exact match is not found in the dns table, the longest match is chosen by wild-carding the fqdn in the dns + - request. + - Default is false. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create GslbService object + community.network.avi_gslbservice: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_gslbservice +""" + +RETURN = ''' +obj: + description: GslbService (api/gslbservice) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + application_persistence_profile_ref=dict(type='str',), + controller_health_status_enabled=dict(type='bool',), + created_by=dict(type='str',), + description=dict(type='str',), + domain_names=dict(type='list',), + down_response=dict(type='dict',), + enabled=dict(type='bool',), + groups=dict(type='list',), + health_monitor_refs=dict(type='list',), + health_monitor_scope=dict(type='str',), + hm_off=dict(type='bool',), + is_federated=dict(type='bool',), + min_members=dict(type='int',), + name=dict(type='str', required=True), + num_dns_ip=dict(type='int',), + pool_algorithm=dict(type='str',), + site_persistence_enabled=dict(type='bool',), + tenant_ref=dict(type='str',), + ttl=dict(type='int',), + url=dict(type='str',), + use_edns_client_subnet=dict(type='bool',), + uuid=dict(type='str',), + wildcard_match=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'gslbservice', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice_patch_member.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice_patch_member.py new file mode 100644 index 00000000..62467d54 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_gslbservice_patch_member.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: supported +# +# Copyright: (c) 2016 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbservice_patch_member +author: Gaurav Rastogi (@grastogi23) + +short_description: Avi API Module +description: + - This module can be used for calling any resources defined in Avi REST API. U(https://avinetworks.com/) + - This module is useful for invoking HTTP Patch methods and accessing resources that do not have an REST object associated with them. +requirements: [ avisdk ] +options: + data: + description: + - HTTP body of GSLB Service Member in YAML or JSON format. + params: + description: + - Query parameters passed to the HTTP API. + name: + description: + - Name of the GSLB Service + required: true + state: + description: + - The state that should be applied to the member. Member is + - identified using field member.ip.addr. + default: present + choices: ["absent","present"] +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Patch GSLB Service to add a new member and group + community.network.avi_gslbservice_patch_member: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + name: gs-3 + api_version: 17.2.1 + data: + group: + name: newfoo + priority: 60 + members: + - enabled: true + ip: + addr: 10.30.10.66 + type: V4 + ratio: 3 + - name: Patch GSLB Service to delete an existing member + community.network.avi_gslbservice_patch_member: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + name: gs-3 + state: absent + api_version: 17.2.1 + data: + group: + name: newfoo + members: + - enabled: true + ip: + addr: 10.30.10.68 + type: V4 + ratio: 3 + - name: Update priority of GSLB Service Pool + community.network.avi_gslbservice_patch_member: + controller: "" + username: "" + password: "" + name: gs-3 + state: present + api_version: 17.2.1 + data: + group: + name: newfoo + priority: 42 +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_obj_cmp, cleanup_absent_fields, + ansible_return, AviCheckModeResponse, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def delete_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, api_version): + members = data.get('group', {}).get('members', []) + patched_member_ids = set([m['ip']['addr'] for m in members if 'fqdn' not in m]) + patched_member_fqdns = set([m['fqdn'] for m in members if 'fqdn' in m]) + + changed = False + rsp = None + + if existing_obj and (patched_member_ids or patched_member_fqdns): + groups = [group for group in existing_obj.get('groups', []) + if group['name'] == data['group']['name']] + if groups: + changed = any( + [(lambda g: g['ip']['addr'] in patched_member_ids)(m) + for m in groups[0].get('members', []) if 'fqdn' not in m]) + changed = changed or any( + [(lambda g: g['fqdn'] in patched_member_fqdns)(m) + for m in groups[0].get('members', []) if 'fqdn' in m]) + if check_mode or not changed: + return changed, rsp + # should not come here if not found + group = groups[0] + new_members = [] + for m in group.get('members', []): + if 'fqdn' in m: + if m['fqdn'] not in patched_member_fqdns: + new_members.append(m) + elif 'ip' in m: + if m['ip']['addr'] not in patched_member_ids: + new_members.append(m) + group['members'] = new_members + if not group['members']: + # Delete this group from the existing objects if it is empty. + # Controller also does not allow empty group. + existing_obj['groups'] = [ + grp for grp in existing_obj.get('groups', []) if + grp['name'] != data['group']['name']] + # remove the members that are part of the list + # update the object + # added api version for AVI api call. + rsp = api.put('gslbservice/%s' % existing_obj['uuid'], data=existing_obj, + tenant=tenant, tenant_uuid=tenant_uuid, api_version=api_version) + return changed, rsp + + +def add_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, name, api_version): + rsp = None + if not existing_obj: + # create the object + changed = True + if check_mode: + rsp = AviCheckModeResponse(obj=None) + else: + # creates group with single member + req = {'name': name, + 'groups': [data['group']] + } + # added api version for AVI api call. + rsp = api.post('gslbservice', data=req, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + else: + # found GSLB object + req = deepcopy(existing_obj) + if 'groups' not in req: + req['groups'] = [] + groups = [group for group in req['groups'] + if group['name'] == data['group']['name']] + if not groups: + # did not find the group + req['groups'].append(data['group']) + else: + # just update the existing group with members + group = groups[0] + group_info_wo_members = deepcopy(data['group']) + group_info_wo_members.pop('members', None) + group.update(group_info_wo_members) + if 'members' not in group: + group['members'] = [] + new_members = [] + for patch_member in data['group'].get('members', []): + found = False + for m in group['members']: + if 'fqdn' in patch_member and m.get('fqdn', '') == patch_member['fqdn']: + found = True + break + elif m['ip']['addr'] == patch_member['ip']['addr']: + found = True + break + if not found: + new_members.append(patch_member) + else: + m.update(patch_member) + # add any new members + group['members'].extend(new_members) + cleanup_absent_fields(req) + changed = not avi_obj_cmp(req, existing_obj) + if changed and not check_mode: + obj_path = '%s/%s' % ('gslbservice', existing_obj['uuid']) + # added api version for AVI api call. + rsp = api.put(obj_path, data=req, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + return changed, rsp + + +def main(): + argument_specs = dict( + params=dict(type='dict'), + data=dict(type='dict'), + name=dict(type='str', required=True), + state=dict(default='present', + choices=['absent', 'present']) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or ansible>=2.8 is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + tenant = api_creds.tenant + tenant_uuid = api_creds.tenant_uuid + params = module.params.get('params', None) + data = module.params.get('data', None) + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + name = module.params.get('name', '') + state = module.params['state'] + # Get the api version from module. + api_version = api_creds.api_version + """ + state: present + 1. Check if the GSLB service is present + 2. If not then create the GSLB service with the member + 3. Check if the group exists + 4. if not then create the group with the member + 5. Check if the member is present + if not then add the member + state: absent + 1. check if GSLB service is present if not then exit + 2. check if group is present. if not then exit + 3. check if member is present. if present then remove it. + """ + obj_type = 'gslbservice' + # Added api version to call + existing_obj = api.get_object_by_name( + obj_type, name, tenant=tenant, tenant_uuid=tenant_uuid, + params={'include_refs': '', 'include_name': ''}, api_version=api_version) + check_mode = module.check_mode + if state == 'absent': + # Added api version to call + changed, rsp = delete_member(module, check_mode, api, tenant, + tenant_uuid, existing_obj, data, api_version) + else: + # Added api version to call + changed, rsp = add_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, name, api_version) + if check_mode or not changed: + return module.exit_json(changed=changed, obj=existing_obj) + return ansible_return(module, rsp, changed, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_hardwaresecuritymodulegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_hardwaresecuritymodulegroup.py new file mode 100644 index 00000000..23ab69cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_hardwaresecuritymodulegroup.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_hardwaresecuritymodulegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HardwareSecurityModuleGroup Avi RESTful Object +description: + - This module is used to configure HardwareSecurityModuleGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + hsm: + description: + - Hardware security module configuration. + required: true + name: + description: + - Name of the hsm group configuration object. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the hsm group configuration object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create HardwareSecurityModuleGroup object + community.network.avi_hardwaresecuritymodulegroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_hardwaresecuritymodulegroup +""" + +RETURN = ''' +obj: + description: HardwareSecurityModuleGroup (api/hardwaresecuritymodulegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + hsm=dict(type='dict', required=True), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'hardwaresecuritymodulegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_healthmonitor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_healthmonitor.py new file mode 100644 index 00000000..a58d7b2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_healthmonitor.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_healthmonitor +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HealthMonitor Avi RESTful Object +description: + - This module is used to configure HealthMonitor object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + dns_monitor: + description: + - Healthmonitordns settings for healthmonitor. + external_monitor: + description: + - Healthmonitorexternal settings for healthmonitor. + failed_checks: + description: + - Number of continuous failed health checks before the server is marked down. + - Allowed values are 1-50. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + http_monitor: + description: + - Healthmonitorhttp settings for healthmonitor. + https_monitor: + description: + - Healthmonitorhttp settings for healthmonitor. + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + monitor_port: + description: + - Use this port instead of the port defined for the server in the pool. + - If the monitor succeeds to this port, the load balanced traffic will still be sent to the port of the server defined within the pool. + - Allowed values are 1-65535. + - Special values are 0 - 'use server port'. + name: + description: + - A user friendly name for this health monitor. + required: true + radius_monitor: + description: + - Health monitor for radius. + - Field introduced in 18.2.3. + receive_timeout: + description: + - A valid response from the server is expected within the receive timeout window. + - This timeout must be less than the send interval. + - If server status is regularly flapping up and down, consider increasing this value. + - Allowed values are 1-2400. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + send_interval: + description: + - Frequency, in seconds, that monitors are sent to a server. + - Allowed values are 1-3600. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + sip_monitor: + description: + - Health monitor for sip. + - Field introduced in 17.2.8, 18.1.3, 18.2.1. + successful_checks: + description: + - Number of continuous successful health checks before server is marked up. + - Allowed values are 1-50. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + tcp_monitor: + description: + - Healthmonitortcp settings for healthmonitor. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of the health monitor. + - Enum options - HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP, HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS, HEALTH_MONITOR_EXTERNAL, HEALTH_MONITOR_UDP, + - HEALTH_MONITOR_DNS, HEALTH_MONITOR_GSLB, HEALTH_MONITOR_SIP, HEALTH_MONITOR_RADIUS. + required: true + udp_monitor: + description: + - Healthmonitorudp settings for healthmonitor. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the health monitor. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a HTTPS health monitor + community.network.avi_healthmonitor: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + https_monitor: + http_request: HEAD / HTTP/1.0 + http_response_code: + - HTTP_2XX + - HTTP_3XX + receive_timeout: 4 + failed_checks: 3 + send_interval: 10 + successful_checks: 3 + type: HEALTH_MONITOR_HTTPS + name: MyWebsite-HTTPS +""" + +RETURN = ''' +obj: + description: HealthMonitor (api/healthmonitor) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + dns_monitor=dict(type='dict',), + external_monitor=dict(type='dict',), + failed_checks=dict(type='int',), + http_monitor=dict(type='dict',), + https_monitor=dict(type='dict',), + is_federated=dict(type='bool',), + monitor_port=dict(type='int',), + name=dict(type='str', required=True), + radius_monitor=dict(type='dict',), + receive_timeout=dict(type='int',), + send_interval=dict(type='int',), + sip_monitor=dict(type='dict',), + successful_checks=dict(type='int',), + tcp_monitor=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + udp_monitor=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'healthmonitor', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_httppolicyset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_httppolicyset.py new file mode 100644 index 00000000..ac04244c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_httppolicyset.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_httppolicyset +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HTTPPolicySet Avi RESTful Object +description: + - This module is used to configure HTTPPolicySet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for pool. + - Internally set by cloud connector. + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + http_request_policy: + description: + - Http request policy for the virtual service. + http_response_policy: + description: + - Http response policy for the virtual service. + http_security_policy: + description: + - Http security policy for the virtual service. + is_internal_policy: + description: + - Boolean flag to set is_internal_policy. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name of the http policy set. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the http policy set. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a HTTP Policy set two switch between testpool1 and testpool2 + community.network.avi_httppolicyset: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + name: test-HTTP-Policy-Set + tenant_ref: admin + http_request_policy: + rules: + - index: 1 + enable: true + name: test-test1 + match: + path: + match_case: INSENSITIVE + match_str: + - /test1 + match_criteria: EQUALS + switching_action: + action: HTTP_SWITCHING_SELECT_POOL + status_code: HTTP_LOCAL_RESPONSE_STATUS_CODE_200 + pool_ref: "/api/pool?name=testpool1" + - index: 2 + enable: true + name: test-test2 + match: + path: + match_case: INSENSITIVE + match_str: + - /test2 + match_criteria: CONTAINS + switching_action: + action: HTTP_SWITCHING_SELECT_POOL + status_code: HTTP_LOCAL_RESPONSE_STATUS_CODE_200 + pool_ref: "/api/pool?name=testpool2" + is_internal_policy: false +""" + +RETURN = ''' +obj: + description: HTTPPolicySet (api/httppolicyset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + http_request_policy=dict(type='dict',), + http_response_policy=dict(type='dict',), + http_security_policy=dict(type='dict',), + is_internal_policy=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'httppolicyset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipaddrgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipaddrgroup.py new file mode 100644 index 00000000..52ae9405 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipaddrgroup.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_ipaddrgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of IpAddrGroup Avi RESTful Object +description: + - This module is used to configure IpAddrGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + addrs: + description: + - Configure ip address(es). + apic_epg_name: + description: + - Populate ip addresses from members of this cisco apic epg. + country_codes: + description: + - Populate the ip address ranges from the geo database for this country. + description: + description: + - User defined description for the object. + ip_ports: + description: + - Configure (ip address, port) tuple(s). + marathon_app_name: + description: + - Populate ip addresses from tasks of this marathon app. + marathon_service_port: + description: + - Task port associated with marathon service port. + - If marathon app has multiple service ports, this is required. + - Else, the first task port is used. + name: + description: + - Name of the ip address group. + required: true + prefixes: + description: + - Configure ip address prefix(es). + ranges: + description: + - Configure ip address range(s). + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the ip address group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an IP Address Group configuration + community.network.avi_ipaddrgroup: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: Client-Source-Block + prefixes: + - ip_addr: + addr: 10.0.0.0 + type: V4 + mask: 8 + - ip_addr: + addr: 172.16.0.0 + type: V4 + mask: 12 + - ip_addr: + addr: 192.168.0.0 + type: V4 + mask: 16 +""" + +RETURN = ''' +obj: + description: IpAddrGroup (api/ipaddrgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + addrs=dict(type='list',), + apic_epg_name=dict(type='str',), + country_codes=dict(type='list',), + description=dict(type='str',), + ip_ports=dict(type='list',), + marathon_app_name=dict(type='str',), + marathon_service_port=dict(type='int',), + name=dict(type='str', required=True), + prefixes=dict(type='list',), + ranges=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'ipaddrgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipamdnsproviderprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipamdnsproviderprofile.py new file mode 100644 index 00000000..3e50bd8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_ipamdnsproviderprofile.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_ipamdnsproviderprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of IpamDnsProviderProfile Avi RESTful Object +description: + - This module is used to configure IpamDnsProviderProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + allocate_ip_in_vrf: + description: + - If this flag is set, only allocate ip from networks in the virtual service vrf. + - Applicable for avi vantage ipam only. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + aws_profile: + description: + - Provider details if type is aws. + azure_profile: + description: + - Provider details if type is microsoft azure. + - Field introduced in 17.2.1. + custom_profile: + description: + - Provider details if type is custom. + - Field introduced in 17.1.1. + gcp_profile: + description: + - Provider details if type is google cloud. + infoblox_profile: + description: + - Provider details if type is infoblox. + internal_profile: + description: + - Provider details if type is avi. + name: + description: + - Name for the ipam/dns provider profile. + required: true + oci_profile: + description: + - Provider details for oracle cloud. + - Field introduced in 18.2.1,18.1.3. + openstack_profile: + description: + - Provider details if type is openstack. + proxy_configuration: + description: + - Field introduced in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + tencent_profile: + description: + - Provider details for tencent cloud. + - Field introduced in 18.2.3. + type: + description: + - Provider type for the ipam/dns provider profile. + - Enum options - IPAMDNS_TYPE_INFOBLOX, IPAMDNS_TYPE_AWS, IPAMDNS_TYPE_OPENSTACK, IPAMDNS_TYPE_GCP, IPAMDNS_TYPE_INFOBLOX_DNS, IPAMDNS_TYPE_CUSTOM, + - IPAMDNS_TYPE_CUSTOM_DNS, IPAMDNS_TYPE_AZURE, IPAMDNS_TYPE_OCI, IPAMDNS_TYPE_TENCENT, IPAMDNS_TYPE_INTERNAL, IPAMDNS_TYPE_INTERNAL_DNS, + - IPAMDNS_TYPE_AWS_DNS, IPAMDNS_TYPE_AZURE_DNS. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the ipam/dns provider profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create IPAM DNS provider setting + community.network.avi_ipamdnsproviderprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + internal_profile: + dns_service_domain: + - domain_name: ashish.local + num_dns_ip: 1 + pass_through: true + record_ttl: 100 + - domain_name: guru.local + num_dns_ip: 1 + pass_through: true + record_ttl: 200 + ttl: 300 + name: Ashish-DNS + tenant_ref: Demo + type: IPAMDNS_TYPE_INTERNAL +""" + +RETURN = ''' +obj: + description: IpamDnsProviderProfile (api/ipamdnsproviderprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + allocate_ip_in_vrf=dict(type='bool',), + aws_profile=dict(type='dict',), + azure_profile=dict(type='dict',), + custom_profile=dict(type='dict',), + gcp_profile=dict(type='dict',), + infoblox_profile=dict(type='dict',), + internal_profile=dict(type='dict',), + name=dict(type='str', required=True), + oci_profile=dict(type='dict',), + openstack_profile=dict(type='dict',), + proxy_configuration=dict(type='dict',), + tenant_ref=dict(type='str',), + tencent_profile=dict(type='dict',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'ipamdnsproviderprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_l4policyset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_l4policyset.py new file mode 100644 index 00000000..d9ef7aa9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_l4policyset.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_l4policyset +author: Chaitanya Deshpande (@chaitanyaavi) + +short_description: Module for setup of L4PolicySet Avi RESTful Object +description: + - This module is used to configure L4PolicySet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.2.7. + description: + description: + - Field introduced in 17.2.7. + is_internal_policy: + description: + - Field introduced in 17.2.7. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + l4_connection_policy: + description: + - Policy to apply when a new transport connection is setup. + - Field introduced in 17.2.7. + name: + description: + - Name of the l4 policy set. + - Field introduced in 17.2.7. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.7. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Id of the l4 policy set. + - Field introduced in 17.2.7. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create L4PolicySet object + community.network.avi_l4policyset: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_l4policyset +""" + +RETURN = ''' +obj: + description: L4PolicySet (api/l4policyset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + is_internal_policy=dict(type='bool',), + l4_connection_policy=dict(type='dict',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'l4policyset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_microservicegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_microservicegroup.py new file mode 100644 index 00000000..d9bdf00a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_microservicegroup.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_microservicegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of MicroServiceGroup Avi RESTful Object +description: + - This module is used to configure MicroServiceGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + name: + description: + - Name of the microservice group. + required: true + service_refs: + description: + - Configure microservice(es). + - It is a reference to an object of type microservice. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the microservice group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a Microservice Group that can be used for setting up Network security policy + community.network.avi_microservicegroup: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + description: Group created by my Secure My App UI. + name: vs-msg-marketing + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: MicroServiceGroup (api/microservicegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + service_refs=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'microservicegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_network.py new file mode 100644 index 00000000..5704f264 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_network.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_network +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Network Avi RESTful Object +description: + - This module is used to configure Network object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + configured_subnets: + description: + - List of subnet. + dhcp_enabled: + description: + - Select the ip address management scheme for this network. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + exclude_discovered_subnets: + description: + - When selected, excludes all discovered subnets in this network from consideration for virtual service placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ip6_autocfg_enabled: + description: + - Enable ipv6 auto configuration. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + synced_from_se: + description: + - Boolean flag to set synced_from_se. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vcenter_dvs: + description: + - Boolean flag to set vcenter_dvs. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + vimgrnw_ref: + description: + - It is a reference to an object of type vimgrnwruntime. + vrf_context_ref: + description: + - It is a reference to an object of type vrfcontext. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Network object + community.network.avi_network: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_network +""" + +RETURN = ''' +obj: + description: Network (api/network) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + configured_subnets=dict(type='list',), + dhcp_enabled=dict(type='bool',), + exclude_discovered_subnets=dict(type='bool',), + ip6_autocfg_enabled=dict(type='bool',), + name=dict(type='str', required=True), + synced_from_se=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + vcenter_dvs=dict(type='bool',), + vimgrnw_ref=dict(type='str',), + vrf_context_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'network', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networkprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networkprofile.py new file mode 100644 index 00000000..00ff2cd6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networkprofile.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_networkprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of NetworkProfile Avi RESTful Object +description: + - This module is used to configure NetworkProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + connection_mirror: + description: + - When enabled, avi mirrors all tcp fastpath connections to standby. + - Applicable only in legacy ha mode. + - Field introduced in 18.1.3,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + name: + description: + - The name of the network profile. + required: true + profile: + description: + - Networkprofileunion settings for networkprofile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the network profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a network profile for an UDP application + community.network.avi_networkprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: System-UDP-Fast-Path + profile: + type: PROTOCOL_TYPE_UDP_FAST_PATH + udp_fast_path_profile: + per_pkt_loadbalance: false + session_idle_timeout: 10 + snat: true + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: NetworkProfile (api/networkprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + connection_mirror=dict(type='bool',), + description=dict(type='str',), + name=dict(type='str', required=True), + profile=dict(type='dict', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'networkprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networksecuritypolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networksecuritypolicy.py new file mode 100644 index 00000000..92343848 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_networksecuritypolicy.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_networksecuritypolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of NetworkSecurityPolicy Avi RESTful Object +description: + - This module is used to configure NetworkSecurityPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for network sec policy. + - Internally set by cloud connector. + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + name: + description: + - Name of the object. + rules: + description: + - List of networksecurityrule. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a network security policy to block clients represented by ip group known_attackers + community.network.avi_networksecuritypolicy: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: vs-gurutest-ns + rules: + - action: NETWORK_SECURITY_POLICY_ACTION_TYPE_DENY + age: 0 + enable: true + index: 1 + log: false + match: + client_ip: + group_refs: + - Demo:known_attackers + match_criteria: IS_IN + name: Rule 1 + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: NetworkSecurityPolicy (api/networksecuritypolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str',), + rules=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'networksecuritypolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pkiprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pkiprofile.py new file mode 100644 index 00000000..7107f28a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pkiprofile.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_pkiprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PKIProfile Avi RESTful Object +description: + - This module is used to configure PKIProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + ca_certs: + description: + - List of certificate authorities (root and intermediate) trusted that is used for certificate validation. + created_by: + description: + - Creator name. + crl_check: + description: + - When enabled, avi will verify via crl checks that certificates in the trust chain have not been revoked. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + crls: + description: + - Certificate revocation lists. + ignore_peer_chain: + description: + - When enabled, avi will not trust intermediate and root certs presented by a client. + - Instead, only the chain certs configured in the certificate authority section will be used to verify trust of the client's cert. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name of the pki profile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + validate_only_leaf_crl: + description: + - When enabled, avi will only validate the revocation status of the leaf certificate using crl. + - To enable validation for the entire chain, disable this option and provide all the relevant crls. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PKIProfile object + community.network.avi_pkiprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_pkiprofile +""" + +RETURN = ''' +obj: + description: PKIProfile (api/pkiprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + ca_certs=dict(type='list',), + created_by=dict(type='str',), + crl_check=dict(type='bool',), + crls=dict(type='list',), + ignore_peer_chain=dict(type='bool',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + validate_only_leaf_crl=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'pkiprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pool.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pool.py new file mode 100644 index 00000000..ffc21d13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_pool.py @@ -0,0 +1,497 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_pool +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Pool Avi RESTful Object +description: + - This module is used to configure Pool object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + a_pool: + description: + - Name of container cloud application that constitutes a pool in a a-b pool configuration, if different from vs app. + - Field deprecated in 18.1.2. + ab_pool: + description: + - A/b pool configuration. + - Field deprecated in 18.1.2. + ab_priority: + description: + - Priority of this pool in a a-b pool pair. + - Internally used. + - Field deprecated in 18.1.2. + analytics_policy: + description: + - Determines analytics settings for the pool. + - Field introduced in 18.1.5, 18.2.1. + analytics_profile_ref: + description: + - Specifies settings related to analytics. + - It is a reference to an object of type analyticsprofile. + - Field introduced in 18.1.4,18.2.1. + apic_epg_name: + description: + - Synchronize cisco apic epg members with pool servers. + application_persistence_profile_ref: + description: + - Persistence will ensure the same user sticks to the same server for a desired duration of time. + - It is a reference to an object of type applicationpersistenceprofile. + autoscale_launch_config_ref: + description: + - If configured then avi will trigger orchestration of pool server creation and deletion. + - It is only supported for container clouds like mesos, openshift, kubernetes, docker, etc. + - It is a reference to an object of type autoscalelaunchconfig. + autoscale_networks: + description: + - Network ids for the launch configuration. + autoscale_policy_ref: + description: + - Reference to server autoscale policy. + - It is a reference to an object of type serverautoscalepolicy. + capacity_estimation: + description: + - Inline estimation of capacity of servers. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + capacity_estimation_ttfb_thresh: + description: + - The maximum time-to-first-byte of a server. + - Allowed values are 1-5000. + - Special values are 0 - 'automatic'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + cloud_config_cksum: + description: + - Checksum of cloud configuration for pool. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + conn_pool_properties: + description: + - Connection pool properties. + - Field introduced in 18.2.1. + connection_ramp_duration: + description: + - Duration for which new connections will be gradually ramped up to a server recently brought online. + - Useful for lb algorithms that are least connection based. + - Allowed values are 1-300. + - Special values are 0 - 'immediate'. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + created_by: + description: + - Creator name. + default_server_port: + description: + - Traffic sent to servers will use this destination server port unless overridden by the server's specific port attribute. + - The ssl checkbox enables avi to server encryption. + - Allowed values are 1-65535. + - Default value when not specified in API or module is interpreted by Avi Controller as 80. + delete_server_on_dns_refresh: + description: + - Indicates whether existing ips are disabled(false) or deleted(true) on dns hostname refreshdetail -- on a dns refresh, some ips set on pool may + - no longer be returned by the resolver. + - These ips are deleted from the pool when this knob is set to true. + - They are disabled, if the knob is set to false. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + description: + description: + - A description of the pool. + domain_name: + description: + - Comma separated list of domain names which will be used to verify the common names or subject alternative names presented by server certificates. + - It is performed only when common name check host_check_enabled is enabled. + east_west: + description: + - Inherited config from virtualservice. + type: bool + enabled: + description: + - Enable or disable the pool. + - Disabling will terminate all open connections and pause health monitors. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + external_autoscale_groups: + description: + - Names of external auto-scale groups for pool servers. + - Currently available only for aws and azure. + - Field introduced in 17.1.2. + fail_action: + description: + - Enable an action - close connection, http redirect or local http response - when a pool failure happens. + - By default, a connection will be closed, in case the pool experiences a failure. + fewest_tasks_feedback_delay: + description: + - Periodicity of feedback for fewest tasks server selection algorithm. + - Allowed values are 1-300. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + graceful_disable_timeout: + description: + - Used to gracefully disable a server. + - Virtual service waits for the specified time before terminating the existing connections to the servers that are disabled. + - Allowed values are 1-7200. + - Special values are 0 - 'immediate', -1 - 'infinite'. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + gslb_sp_enabled: + description: + - Indicates if the pool is a site-persistence pool. + - Field introduced in 17.2.1. + type: bool + health_monitor_refs: + description: + - Verify server health by applying one or more health monitors. + - Active monitors generate synthetic traffic from each service engine and mark a server up or down based on the response. + - The passive monitor listens only to client to server communication. + - It raises or lowers the ratio of traffic destined to a server based on successful responses. + - It is a reference to an object of type healthmonitor. + host_check_enabled: + description: + - Enable common name check for server certificate. + - If enabled and no explicit domain name is specified, avi will use the incoming host header to do the match. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + inline_health_monitor: + description: + - The passive monitor will monitor client to server connections and requests and adjust traffic load to servers based on successful responses. + - This may alter the expected behavior of the lb method, such as round robin. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ipaddrgroup_ref: + description: + - Use list of servers from ip address group. + - It is a reference to an object of type ipaddrgroup. + lb_algorithm: + description: + - The load balancing algorithm will pick a server within the pool's list of available servers. + - Enum options - LB_ALGORITHM_LEAST_CONNECTIONS, LB_ALGORITHM_ROUND_ROBIN, LB_ALGORITHM_FASTEST_RESPONSE, LB_ALGORITHM_CONSISTENT_HASH, + - LB_ALGORITHM_LEAST_LOAD, LB_ALGORITHM_FEWEST_SERVERS, LB_ALGORITHM_RANDOM, LB_ALGORITHM_FEWEST_TASKS, LB_ALGORITHM_NEAREST_SERVER, + - LB_ALGORITHM_CORE_AFFINITY, LB_ALGORITHM_TOPOLOGY. + - Default value when not specified in API or module is interpreted by Avi Controller as LB_ALGORITHM_LEAST_CONNECTIONS. + lb_algorithm_consistent_hash_hdr: + description: + - Http header name to be used for the hash key. + lb_algorithm_core_nonaffinity: + description: + - Degree of non-affinity for core affinity based server selection. + - Allowed values are 1-65535. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + lb_algorithm_hash: + description: + - Criteria used as a key for determining the hash between the client and server. + - Enum options - LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS, LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS_AND_PORT, + - LB_ALGORITHM_CONSISTENT_HASH_URI, LB_ALGORITHM_CONSISTENT_HASH_CUSTOM_HEADER, LB_ALGORITHM_CONSISTENT_HASH_CUSTOM_STRING, + - LB_ALGORITHM_CONSISTENT_HASH_CALLID. + - Default value when not specified in API or module is interpreted by Avi Controller as LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS. + lookup_server_by_name: + description: + - Allow server lookup by name. + - Field introduced in 17.1.11,17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + max_concurrent_connections_per_server: + description: + - The maximum number of concurrent connections allowed to each server within the pool. + - Note applied value will be no less than the number of service engines that the pool is placed on. + - If set to 0, no limit is applied. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + max_conn_rate_per_server: + description: + - Rate limit connections to each server. + min_health_monitors_up: + description: + - Minimum number of health monitors in up state to mark server up. + - Field introduced in 18.2.1, 17.2.12. + min_servers_up: + description: + - Minimum number of servers in up state for marking the pool up. + - Field introduced in 18.2.1, 17.2.12. + name: + description: + - The name of the pool. + required: true + networks: + description: + - (internal-use) networks designated as containing servers for this pool. + - The servers may be further narrowed down by a filter. + - This field is used internally by avi, not editable by the user. + nsx_securitygroup: + description: + - A list of nsx service groups where the servers for the pool are created. + - Field introduced in 17.1.1. + pki_profile_ref: + description: + - Avi will validate the ssl certificate present by a server against the selected pki profile. + - It is a reference to an object of type pkiprofile. + placement_networks: + description: + - Manually select the networks and subnets used to provide reachability to the pool's servers. + - Specify the subnet using the following syntax 10-1-1-0/24. + - Use static routes in vrf configuration when pool servers are not directly connected butroutable from the service engine. + prst_hdr_name: + description: + - Header name for custom header persistence. + - Field deprecated in 18.1.2. + request_queue_depth: + description: + - Minimum number of requests to be queued when pool is full. + - Default value when not specified in API or module is interpreted by Avi Controller as 128. + request_queue_enabled: + description: + - Enable request queue when pool is full. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + rewrite_host_header_to_server_name: + description: + - Rewrite incoming host header to server name of the server to which the request is proxied. + - Enabling this feature rewrites host header for requests to all servers in the pool. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + rewrite_host_header_to_sni: + description: + - If sni server name is specified, rewrite incoming host header to the sni server name. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + server_auto_scale: + description: + - Server autoscale. + - Not used anymore. + - Field deprecated in 18.1.2. + type: bool + server_count: + description: + - Field deprecated in 18.2.1. + server_name: + description: + - Fully qualified dns hostname which will be used in the tls sni extension in server connections if sni is enabled. + - If no value is specified, avi will use the incoming host header instead. + server_reselect: + description: + - Server reselect configuration for http requests. + server_timeout: + description: + - Server timeout value specifies the time within which a server connection needs to be established and a request-response exchange completes + - between avi and the server. + - Value of 0 results in using default timeout of 60 minutes. + - Allowed values are 0-3600000. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + servers: + description: + - The pool directs load balanced traffic to this list of destination servers. + - The servers can be configured by ip address, name, network or via ip address group. + service_metadata: + description: + - Metadata pertaining to the service provided by this pool. + - In openshift/kubernetes environments, app metadata info is stored. + - Any user input to this field will be overwritten by avi vantage. + - Field introduced in 17.2.14,18.1.5,18.2.1. + sni_enabled: + description: + - Enable tls sni for server connections. + - If disabled, avi will not send the sni extension as part of the handshake. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ssl_key_and_certificate_ref: + description: + - Service engines will present a client ssl certificate to the server. + - It is a reference to an object of type sslkeyandcertificate. + ssl_profile_ref: + description: + - When enabled, avi re-encrypts traffic to the backend servers. + - The specific ssl profile defines which ciphers and ssl versions will be supported. + - It is a reference to an object of type sslprofile. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_service_port: + description: + - Do not translate the client's destination port when sending the connection to the server. + - The pool or servers specified service port will still be used for health monitoring. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Uuid of the pool. + vrf_ref: + description: + - Virtual routing context that the pool is bound to. + - This is used to provide the isolation of the set of networks the pool is attached to. + - The pool inherits the virtual routing context of the virtual service, and this field is used only internally, and is set by pb-transform. + - It is a reference to an object of type vrfcontext. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a Pool with two servers and HTTP monitor + community.network.avi_pool: + controller: 10.10.1.20 + username: avi_user + password: avi_password + name: testpool1 + description: testpool1 + state: present + health_monitor_refs: + - '/api/healthmonitor?name=System-HTTP' + servers: + - ip: + addr: 10.10.2.20 + type: V4 + - ip: + addr: 10.10.2.21 + type: V4 + +- name: Patch pool with a single server using patch op and avi_credentials + community.network.avi_pool: + avi_api_update_method: patch + avi_api_patch_op: delete + avi_credentials: "{{avi_credentials}}" + name: test-pool + servers: + - ip: + addr: 10.90.64.13 + type: 'V4' + register: pool + when: + - state | default("present") == "present" +""" + +RETURN = ''' +obj: + description: Pool (api/pool) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + a_pool=dict(type='str',), + ab_pool=dict(type='dict',), + ab_priority=dict(type='int',), + analytics_policy=dict(type='dict',), + analytics_profile_ref=dict(type='str',), + apic_epg_name=dict(type='str',), + application_persistence_profile_ref=dict(type='str',), + autoscale_launch_config_ref=dict(type='str',), + autoscale_networks=dict(type='list',), + autoscale_policy_ref=dict(type='str',), + capacity_estimation=dict(type='bool',), + capacity_estimation_ttfb_thresh=dict(type='int',), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + conn_pool_properties=dict(type='dict',), + connection_ramp_duration=dict(type='int',), + created_by=dict(type='str',), + default_server_port=dict(type='int',), + delete_server_on_dns_refresh=dict(type='bool',), + description=dict(type='str',), + domain_name=dict(type='list',), + east_west=dict(type='bool',), + enabled=dict(type='bool',), + external_autoscale_groups=dict(type='list',), + fail_action=dict(type='dict',), + fewest_tasks_feedback_delay=dict(type='int',), + graceful_disable_timeout=dict(type='int',), + gslb_sp_enabled=dict(type='bool',), + health_monitor_refs=dict(type='list',), + host_check_enabled=dict(type='bool',), + inline_health_monitor=dict(type='bool',), + ipaddrgroup_ref=dict(type='str',), + lb_algorithm=dict(type='str',), + lb_algorithm_consistent_hash_hdr=dict(type='str',), + lb_algorithm_core_nonaffinity=dict(type='int',), + lb_algorithm_hash=dict(type='str',), + lookup_server_by_name=dict(type='bool',), + max_concurrent_connections_per_server=dict(type='int',), + max_conn_rate_per_server=dict(type='dict',), + min_health_monitors_up=dict(type='int',), + min_servers_up=dict(type='int',), + name=dict(type='str', required=True), + networks=dict(type='list',), + nsx_securitygroup=dict(type='list',), + pki_profile_ref=dict(type='str',), + placement_networks=dict(type='list',), + prst_hdr_name=dict(type='str',), + request_queue_depth=dict(type='int',), + request_queue_enabled=dict(type='bool',), + rewrite_host_header_to_server_name=dict(type='bool',), + rewrite_host_header_to_sni=dict(type='bool',), + server_auto_scale=dict(type='bool',), + server_count=dict(type='int',), + server_name=dict(type='str',), + server_reselect=dict(type='dict',), + server_timeout=dict(type='int',), + servers=dict(type='list',), + service_metadata=dict(type='str',), + sni_enabled=dict(type='bool',), + ssl_key_and_certificate_ref=dict(type='str',), + ssl_profile_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_service_port=dict(type='bool',), + uuid=dict(type='str',), + vrf_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'pool', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroup.py new file mode 100644 index 00000000..c66269dd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroup.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_poolgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PoolGroup Avi RESTful Object +description: + - This module is used to configure PoolGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for poolgroup. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + created_by: + description: + - Name of the user who created the object. + deployment_policy_ref: + description: + - When setup autoscale manager will automatically promote new pools into production when deployment goals are met. + - It is a reference to an object of type poolgroupdeploymentpolicy. + description: + description: + - Description of pool group. + fail_action: + description: + - Enable an action - close connection, http redirect, or local http response - when a pool group failure happens. + - By default, a connection will be closed, in case the pool group experiences a failure. + implicit_priority_labels: + description: + - Whether an implicit set of priority labels is generated. + - Field introduced in 17.1.9,17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + members: + description: + - List of pool group members object of type poolgroupmember. + min_servers: + description: + - The minimum number of servers to distribute traffic to. + - Allowed values are 1-65535. + - Special values are 0 - 'disable'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + name: + description: + - The name of the pool group. + required: true + priority_labels_ref: + description: + - Uuid of the priority labels. + - If not provided, pool group member priority label will be interpreted as a number with a larger number considered higher priority. + - It is a reference to an object of type prioritylabels. + service_metadata: + description: + - Metadata pertaining to the service provided by this poolgroup. + - In openshift/kubernetes environments, app metadata info is stored. + - Any user input to this field will be overwritten by avi vantage. + - Field introduced in 17.2.14,18.1.5,18.2.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the pool group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PoolGroup object + community.network.avi_poolgroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_poolgroup +""" + +RETURN = ''' +obj: + description: PoolGroup (api/poolgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + created_by=dict(type='str',), + deployment_policy_ref=dict(type='str',), + description=dict(type='str',), + fail_action=dict(type='dict',), + implicit_priority_labels=dict(type='bool',), + members=dict(type='list',), + min_servers=dict(type='int',), + name=dict(type='str', required=True), + priority_labels_ref=dict(type='str',), + service_metadata=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'poolgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroupdeploymentpolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroupdeploymentpolicy.py new file mode 100644 index 00000000..952de35a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_poolgroupdeploymentpolicy.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_poolgroupdeploymentpolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PoolGroupDeploymentPolicy Avi RESTful Object +description: + - This module is used to configure PoolGroupDeploymentPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + auto_disable_old_prod_pools: + description: + - It will automatically disable old production pools once there is a new production candidate. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + description: + description: + - User defined description for the object. + evaluation_duration: + description: + - Duration of evaluation period for automatic deployment. + - Allowed values are 60-86400. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + name: + description: + - The name of the pool group deployment policy. + required: true + rules: + description: + - List of pgdeploymentrule. + scheme: + description: + - Deployment scheme. + - Enum options - BLUE_GREEN, CANARY. + - Default value when not specified in API or module is interpreted by Avi Controller as BLUE_GREEN. + target_test_traffic_ratio: + description: + - Target traffic ratio before pool is made production. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + tenant_ref: + description: + - It is a reference to an object of type tenant. + test_traffic_ratio_rampup: + description: + - Ratio of the traffic that is sent to the pool under test. + - Test ratio of 100 means blue green. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the pool group deployment policy. + webhook_ref: + description: + - Webhook configured with url that avi controller will pass back information about pool group, old and new pool information and current deployment + - rule results. + - It is a reference to an object of type webhook. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PoolGroupDeploymentPolicy object + community.network.avi_poolgroupdeploymentpolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_poolgroupdeploymentpolicy +""" + +RETURN = ''' +obj: + description: PoolGroupDeploymentPolicy (api/poolgroupdeploymentpolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + auto_disable_old_prod_pools=dict(type='bool',), + description=dict(type='str',), + evaluation_duration=dict(type='int',), + name=dict(type='str', required=True), + rules=dict(type='list',), + scheme=dict(type='str',), + target_test_traffic_ratio=dict(type='int',), + tenant_ref=dict(type='str',), + test_traffic_ratio_rampup=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + webhook_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'poolgroupdeploymentpolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_prioritylabels.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_prioritylabels.py new file mode 100644 index 00000000..fc8c38ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_prioritylabels.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_prioritylabels +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PriorityLabels Avi RESTful Object +description: + - This module is used to configure PriorityLabels object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + description: + description: + - A description of the priority labels. + equivalent_labels: + description: + - Equivalent priority labels in descending order. + name: + description: + - The name of the priority labels. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the priority labels. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PriorityLabels object + community.network.avi_prioritylabels: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_prioritylabels +""" + +RETURN = ''' +obj: + description: PriorityLabels (api/prioritylabels) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + description=dict(type='str',), + equivalent_labels=dict(type='list',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'prioritylabels', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_role.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_role.py new file mode 100644 index 00000000..fc71de19 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_role.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_role +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Role Avi RESTful Object +description: + - This module is used to configure Role object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the object. + required: true + privileges: + description: + - List of permission. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Role object + community.network.avi_role: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_role +""" + +RETURN = ''' +obj: + description: Role (api/role) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + privileges=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'role', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_scheduler.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_scheduler.py new file mode 100644 index 00000000..ef3cabb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_scheduler.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_scheduler +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Scheduler Avi RESTful Object +description: + - This module is used to configure Scheduler object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + backup_config_ref: + description: + - Backup configuration to be executed by this scheduler. + - It is a reference to an object of type backupconfiguration. + enabled: + description: + - Boolean flag to set enabled. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + end_date_time: + description: + - Scheduler end date and time. + frequency: + description: + - Frequency at which custom scheduler will run. + - Allowed values are 0-60. + frequency_unit: + description: + - Unit at which custom scheduler will run. + - Enum options - SCHEDULER_FREQUENCY_UNIT_MIN, SCHEDULER_FREQUENCY_UNIT_HOUR, SCHEDULER_FREQUENCY_UNIT_DAY, SCHEDULER_FREQUENCY_UNIT_WEEK, + - SCHEDULER_FREQUENCY_UNIT_MONTH. + name: + description: + - Name of scheduler. + required: true + run_mode: + description: + - Scheduler run mode. + - Enum options - RUN_MODE_PERIODIC, RUN_MODE_AT, RUN_MODE_NOW. + run_script_ref: + description: + - Control script to be executed by this scheduler. + - It is a reference to an object of type alertscriptconfig. + scheduler_action: + description: + - Define scheduler action. + - Enum options - SCHEDULER_ACTION_RUN_A_SCRIPT, SCHEDULER_ACTION_BACKUP. + - Default value when not specified in API or module is interpreted by Avi Controller as SCHEDULER_ACTION_BACKUP. + start_date_time: + description: + - Scheduler start date and time. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Scheduler object + community.network.avi_scheduler: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_scheduler +""" + +RETURN = ''' +obj: + description: Scheduler (api/scheduler) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + backup_config_ref=dict(type='str',), + enabled=dict(type='bool',), + end_date_time=dict(type='str',), + frequency=dict(type='int',), + frequency_unit=dict(type='str',), + name=dict(type='str', required=True), + run_mode=dict(type='str',), + run_script_ref=dict(type='str',), + scheduler_action=dict(type='str',), + start_date_time=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'scheduler', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_seproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_seproperties.py new file mode 100644 index 00000000..3150cafb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_seproperties.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_seproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SeProperties Avi RESTful Object +description: + - This module is used to configure SeProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + se_agent_properties: + description: + - Seagentproperties settings for seproperties. + se_bootup_properties: + description: + - Sebootupproperties settings for seproperties. + se_runtime_properties: + description: + - Seruntimeproperties settings for seproperties. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + - Default value when not specified in API or module is interpreted by Avi Controller as default. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SeProperties object + community.network.avi_seproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_seproperties +""" + +RETURN = ''' +obj: + description: SeProperties (api/seproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + se_agent_properties=dict(type='dict',), + se_bootup_properties=dict(type='dict',), + se_runtime_properties=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'seproperties', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serverautoscalepolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serverautoscalepolicy.py new file mode 100644 index 00000000..0f97a1bb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serverautoscalepolicy.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serverautoscalepolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServerAutoScalePolicy Avi RESTful Object +description: + - This module is used to configure ServerAutoScalePolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + intelligent_autoscale: + description: + - Use avi intelligent autoscale algorithm where autoscale is performed by comparing load on the pool against estimated capacity of all the servers. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + intelligent_scalein_margin: + description: + - Maximum extra capacity as percentage of load used by the intelligent scheme. + - Scalein is triggered when available capacity is more than this margin. + - Allowed values are 1-99. + - Default value when not specified in API or module is interpreted by Avi Controller as 40. + intelligent_scaleout_margin: + description: + - Minimum extra capacity as percentage of load used by the intelligent scheme. + - Scaleout is triggered when available capacity is less than this margin. + - Allowed values are 1-99. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + max_scalein_adjustment_step: + description: + - Maximum number of servers to scalein simultaneously. + - The actual number of servers to scalein is chosen such that target number of servers is always more than or equal to the min_size. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_scaleout_adjustment_step: + description: + - Maximum number of servers to scaleout simultaneously. + - The actual number of servers to scaleout is chosen such that target number of servers is always less than or equal to the max_size. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_size: + description: + - Maximum number of servers after scaleout. + - Allowed values are 0-400. + min_size: + description: + - No scale-in happens once number of operationally up servers reach min_servers. + - Allowed values are 0-400. + name: + description: + - Name of the object. + required: true + scalein_alertconfig_refs: + description: + - Trigger scalein when alerts due to any of these alert configurations are raised. + - It is a reference to an object of type alertconfig. + scalein_cooldown: + description: + - Cooldown period during which no new scalein is triggered to allow previous scalein to successfully complete. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + scaleout_alertconfig_refs: + description: + - Trigger scaleout when alerts due to any of these alert configurations are raised. + - It is a reference to an object of type alertconfig. + scaleout_cooldown: + description: + - Cooldown period during which no new scaleout is triggered to allow previous scaleout to successfully complete. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_predicted_load: + description: + - Use predicted load rather than current load. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServerAutoScalePolicy object + community.network.avi_serverautoscalepolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serverautoscalepolicy +""" + +RETURN = ''' +obj: + description: ServerAutoScalePolicy (api/serverautoscalepolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + intelligent_autoscale=dict(type='bool',), + intelligent_scalein_margin=dict(type='int',), + intelligent_scaleout_margin=dict(type='int',), + max_scalein_adjustment_step=dict(type='int',), + max_scaleout_adjustment_step=dict(type='int',), + max_size=dict(type='int',), + min_size=dict(type='int',), + name=dict(type='str', required=True), + scalein_alertconfig_refs=dict(type='list',), + scalein_cooldown=dict(type='int',), + scaleout_alertconfig_refs=dict(type='list',), + scaleout_cooldown=dict(type='int',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_predicted_load=dict(type='bool',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serverautoscalepolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceengine.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceengine.py new file mode 100644 index 00000000..c1e54725 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceengine.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serviceengine +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServiceEngine Avi RESTful Object +description: + - This module is used to configure ServiceEngine object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + availability_zone: + description: + - Availability_zone of serviceengine. + cloud_ref: + description: + - It is a reference to an object of type cloud. + container_mode: + description: + - Boolean flag to set container_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + container_type: + description: + - Enum options - container_type_bridge, container_type_host, container_type_host_dpdk. + - Default value when not specified in API or module is interpreted by Avi Controller as CONTAINER_TYPE_HOST. + controller_created: + description: + - Boolean flag to set controller_created. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + controller_ip: + description: + - Controller_ip of serviceengine. + data_vnics: + description: + - List of vnic. + enable_state: + description: + - Inorder to disable se set this field appropriately. + - Enum options - SE_STATE_ENABLED, SE_STATE_DISABLED_FOR_PLACEMENT, SE_STATE_DISABLED, SE_STATE_DISABLED_FORCE. + - Default value when not specified in API or module is interpreted by Avi Controller as SE_STATE_ENABLED. + flavor: + description: + - Flavor of serviceengine. + host_ref: + description: + - It is a reference to an object of type vimgrhostruntime. + hypervisor: + description: + - Enum options - default, vmware_esx, kvm, vmware_vsan, xen. + mgmt_vnic: + description: + - Vnic settings for serviceengine. + name: + description: + - Name of the object. + - Default value when not specified in API or module is interpreted by Avi Controller as VM name unknown. + resources: + description: + - Seresources settings for serviceengine. + se_group_ref: + description: + - It is a reference to an object of type serviceenginegroup. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServiceEngine object + community.network.avi_serviceengine: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serviceengine +""" + +RETURN = ''' +obj: + description: ServiceEngine (api/serviceengine) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + availability_zone=dict(type='str',), + cloud_ref=dict(type='str',), + container_mode=dict(type='bool',), + container_type=dict(type='str',), + controller_created=dict(type='bool',), + controller_ip=dict(type='str',), + data_vnics=dict(type='list',), + enable_state=dict(type='str',), + flavor=dict(type='str',), + host_ref=dict(type='str',), + hypervisor=dict(type='str',), + mgmt_vnic=dict(type='dict',), + name=dict(type='str',), + resources=dict(type='dict',), + se_group_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serviceengine', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceenginegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceenginegroup.py new file mode 100644 index 00000000..c7c83be2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_serviceenginegroup.py @@ -0,0 +1,1075 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serviceenginegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServiceEngineGroup Avi RESTful Object +description: + - This module is used to configure ServiceEngineGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + accelerated_networking: + description: + - Enable accelerated networking option for azure se. + - Accelerated networking enables single root i/o virtualization (sr-iov) to a se vm. + - This improves networking performance. + - Field introduced in 17.2.14,18.1.5,18.2.1. + type: bool + active_standby: + description: + - Service engines in active/standby mode for ha failover. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + additional_config_memory: + description: + - Indicates the percent of config memory used for config updates. + - Allowed values are 0-90. + - Field deprecated in 18.1.2. + - Field introduced in 18.1.1. + advertise_backend_networks: + description: + - Advertise reach-ability of backend server networks via adc through bgp for default gateway feature. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + aggressive_failure_detection: + description: + - Enable aggressive failover configuration for ha. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + algo: + description: + - In compact placement, virtual services are placed on existing ses until max_vs_per_se limit is reached. + - Enum options - PLACEMENT_ALGO_PACKED, PLACEMENT_ALGO_DISTRIBUTED. + - Default value when not specified in API or module is interpreted by Avi Controller as PLACEMENT_ALGO_PACKED. + allow_burst: + description: + - Allow ses to be created using burst license. + - Field introduced in 17.2.5. + type: bool + app_cache_percent: + description: + - A percent value of total se memory reserved for application caching. + - This is an se bootup property and requires se restart. + - Allowed values are 0 - 100. + - Special values are 0- 'disable'. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + app_learning_memory_percent: + description: + - A percent value of total se memory reserved for application learning. + - This is an se bootup property and requires se restart. + - Allowed values are 0 - 10. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + archive_shm_limit: + description: + - Amount of se memory in gb until which shared memory is collected in core archive. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + async_ssl: + description: + - Ssl handshakes will be handled by dedicated ssl threads. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + async_ssl_threads: + description: + - Number of async ssl threads per se_dp. + - Allowed values are 1-16. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + auto_rebalance: + description: + - If set, virtual services will be automatically migrated when load on an se is less than minimum or more than maximum thresholds. + - Only alerts are generated when the auto_rebalance is not set. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + auto_rebalance_capacity_per_se: + description: + - Capacities of se for auto rebalance for each criteria. + - Field introduced in 17.2.4. + auto_rebalance_criteria: + description: + - Set of criteria for se auto rebalance. + - Enum options - SE_AUTO_REBALANCE_CPU, SE_AUTO_REBALANCE_PPS, SE_AUTO_REBALANCE_MBPS, SE_AUTO_REBALANCE_OPEN_CONNS, SE_AUTO_REBALANCE_CPS. + - Field introduced in 17.2.3. + auto_rebalance_interval: + description: + - Frequency of rebalance, if 'auto rebalance' is enabled. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + auto_redistribute_active_standby_load: + description: + - Redistribution of virtual services from the takeover se to the replacement se can cause momentary traffic loss. + - If the auto-redistribute load option is left in its default off state, any desired rebalancing requires calls to rest api. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + bgp_state_update_interval: + description: + - Bgp peer state update interval. + - Allowed values are 5-100. + - Field introduced in 17.2.14,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + buffer_se: + description: + - Excess service engine capacity provisioned for ha failover. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + cloud_ref: + description: + - It is a reference to an object of type cloud. + config_debugs_on_all_cores: + description: + - Enable config debugs on all cores of se. + - Field introduced in 17.2.13,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + connection_memory_percentage: + description: + - Percentage of memory for connection state. + - This will come at the expense of memory used for http in-memory cache. + - Allowed values are 10-90. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + cpu_reserve: + description: + - Boolean flag to set cpu_reserve. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + cpu_socket_affinity: + description: + - Allocate all the cpu cores for the service engine virtual machines on the same cpu socket. + - Applicable only for vcenter cloud. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + custom_securitygroups_data: + description: + - Custom security groups to be associated with data vnics for se instances in openstack and aws clouds. + - Field introduced in 17.1.3. + custom_securitygroups_mgmt: + description: + - Custom security groups to be associated with management vnic for se instances in openstack and aws clouds. + - Field introduced in 17.1.3. + custom_tag: + description: + - Custom tag will be used to create the tags for se instance in aws. + - Note this is not the same as the prefix for se name. + data_network_id: + description: + - Subnet used to spin up the data nic for service engines, used only for azure cloud. + - Overrides the cloud level setting for service engine subnet. + - Field introduced in 18.2.3. + datascript_timeout: + description: + - Number of instructions before datascript times out. + - Allowed values are 0-100000000. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1000000. + dedicated_dispatcher_core: + description: + - Dedicate the core that handles packet receive/transmit from the network to just the dispatching function. + - Don't use it for tcp/ip and ssl functions. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + disable_avi_securitygroups: + description: + - By default, avi creates and manages security groups along with custom sg provided by user. + - Set this to true to disallow avi to create and manage new security groups. + - Avi will only make use of custom security groups provided by user. + - This option is only supported for aws cloud type. + - Field introduced in 17.2.13,18.1.4,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_csum_offloads: + description: + - Stop using tcp/udp and ip checksum offload features of nics. + - Field introduced in 17.1.14, 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_gro: + description: + - Disable generic receive offload (gro) in dpdk poll-mode driver packet receive path. + - Gro is on by default on nics that do not support lro (large receive offload) or do not gain performance boost from lro. + - Field introduced in 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + disable_se_memory_check: + description: + - If set, disable the config memory check done in service engine. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_tso: + description: + - Disable tcp segmentation offload (tso) in dpdk poll-mode driver packet transmit path. + - Tso is on by default on nics that support it. + - Field introduced in 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + disk_per_se: + description: + - Amount of disk space for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + distribute_load_active_standby: + description: + - Use both the active and standby service engines for virtual service placement in the legacy active standby ha mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + distribute_queues: + description: + - Distributes queue ownership among cores so multiple cores handle dispatcher duties. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_hsm_priming: + description: + - (this is a beta feature). + - Enable hsm key priming. + - If enabled, key handles on the hsm will be synced to se before processing client connections. + - Field introduced in 17.2.7, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_multi_lb: + description: + - Applicable only for azure cloud with basic sku lb. + - If set, additional azure lbs will be automatically created if resources in existing lb are exhausted. + - Field introduced in 17.2.10, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_routing: + description: + - Enable routing for this serviceenginegroup . + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_vip_on_all_interfaces: + description: + - Enable vip on all interfaces of se. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_vmac: + description: + - Use virtual mac address for interfaces on which floating interface ips are placed. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ephemeral_portrange_end: + description: + - End local ephemeral port number for outbound connections. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + ephemeral_portrange_start: + description: + - Start local ephemeral port number for outbound connections. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + extra_config_multiplier: + description: + - Multiplier for extra config to support large vs/pool config. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + extra_shared_config_memory: + description: + - Extra config memory to support large geo db configuration. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + floating_intf_ip: + description: + - If serviceenginegroup is configured for legacy 1+1 active standby ha mode, floating ip's will be advertised only by the active se in the pair. + - Virtual services in this group must be disabled/enabled for any changes to the floating ip's to take effect. + - Only active se hosting vs tagged with active standby se 1 tag will advertise this floating ip when manual load distribution is enabled. + floating_intf_ip_se_2: + description: + - If serviceenginegroup is configured for legacy 1+1 active standby ha mode, floating ip's will be advertised only by the active se in the pair. + - Virtual services in this group must be disabled/enabled for any changes to the floating ip's to take effect. + - Only active se hosting vs tagged with active standby se 2 tag will advertise this floating ip when manual load distribution is enabled. + flow_table_new_syn_max_entries: + description: + - Maximum number of flow table entries that have not completed tcp three-way handshake yet. + - Field introduced in 17.2.5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + free_list_size: + description: + - Number of entries in the free list. + - Field introduced in 17.2.10, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1024. + ha_mode: + description: + - High availability mode for all the virtual services using this service engine group. + - Enum options - HA_MODE_SHARED_PAIR, HA_MODE_SHARED, HA_MODE_LEGACY_ACTIVE_STANDBY. + - Default value when not specified in API or module is interpreted by Avi Controller as HA_MODE_SHARED. + hardwaresecuritymodulegroup_ref: + description: + - It is a reference to an object of type hardwaresecuritymodulegroup. + heap_minimum_config_memory: + description: + - Minimum required heap memory to apply any configuration. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + hm_on_standby: + description: + - Enable active health monitoring from the standby se for all placed virtual services. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + host_attribute_key: + description: + - Key of a (key, value) pair identifying a label for a set of nodes usually in container clouds. + - Needs to be specified together with host_attribute_value. + - Ses can be configured differently including ha modes across different se groups. + - May also be used for isolation between different classes of virtualservices. + - Virtualservices' se group may be specified via annotations/labels. + - A openshift/kubernetes namespace maybe annotated with a matching se group label as openshift.io/node-selector apptype=prod. + - When multiple se groups are used in a cloud with host attributes specified,just a single se group can exist as a match-all se group without a + - host_attribute_key. + host_attribute_value: + description: + - Value of a (key, value) pair identifying a label for a set of nodes usually in container clouds. + - Needs to be specified together with host_attribute_key. + host_gateway_monitor: + description: + - Enable the host gateway monitor when service engine is deployed as docker container. + - Disabled by default. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + hypervisor: + description: + - Override default hypervisor. + - Enum options - DEFAULT, VMWARE_ESX, KVM, VMWARE_VSAN, XEN. + ignore_rtt_threshold: + description: + - Ignore rtt samples if it is above threshold. + - Field introduced in 17.1.6,17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 5000. + ingress_access_data: + description: + - Program se security group ingress rules to allow vip data access from remote cidr type. + - Enum options - SG_INGRESS_ACCESS_NONE, SG_INGRESS_ACCESS_ALL, SG_INGRESS_ACCESS_VPC. + - Field introduced in 17.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_INGRESS_ACCESS_ALL. + ingress_access_mgmt: + description: + - Program se security group ingress rules to allow ssh/icmp management access from remote cidr type. + - Enum options - SG_INGRESS_ACCESS_NONE, SG_INGRESS_ACCESS_ALL, SG_INGRESS_ACCESS_VPC. + - Field introduced in 17.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_INGRESS_ACCESS_ALL. + instance_flavor: + description: + - Instance/flavor name for se instance. + iptables: + description: + - Iptables rules. + least_load_core_selection: + description: + - Select core with least load for new flow. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + license_tier: + description: + - Specifies the license tier which would be used. + - This field by default inherits the value from cloud. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + license_type: + description: + - If no license type is specified then default license enforcement for the cloud type is chosen. + - Enum options - LIC_BACKEND_SERVERS, LIC_SOCKETS, LIC_CORES, LIC_HOSTS, LIC_SE_BANDWIDTH, LIC_METERED_SE_BANDWIDTH. + - Field introduced in 17.2.5. + log_disksz: + description: + - Maximum disk capacity (in mb) to be allocated to an se. + - This is exclusively used for debug and log data. + - Default value when not specified in API or module is interpreted by Avi Controller as 10000. + max_cpu_usage: + description: + - When cpu usage on an se exceeds this threshold, virtual services hosted on this se may be rebalanced to other ses to reduce load. + - A new se may be created as part of this process. + - Allowed values are 40-90. + - Default value when not specified in API or module is interpreted by Avi Controller as 80. + max_memory_per_mempool: + description: + - Max bytes that can be allocated in a single mempool. + - Field introduced in 18.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. + max_public_ips_per_lb: + description: + - Applicable to azure platform only. + - Maximum number of public ips per azure lb. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + max_rules_per_lb: + description: + - Applicable to azure platform only. + - Maximum number of rules per azure lb. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 150. + max_scaleout_per_vs: + description: + - Maximum number of active service engines for the virtual service. + - Allowed values are 1-64. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + max_se: + description: + - Maximum number of services engines in this group. + - Allowed values are 0-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + max_vs_per_se: + description: + - Maximum number of virtual services that can be placed on a single service engine. + - East west virtual services are excluded from this limit. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + mem_reserve: + description: + - Boolean flag to set mem_reserve. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + memory_for_config_update: + description: + - Indicates the percent of memory reserved for config updates. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + memory_per_se: + description: + - Amount of memory for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 2048. + mgmt_network_ref: + description: + - Management network to use for avi service engines. + - It is a reference to an object of type network. + mgmt_subnet: + description: + - Management subnet to use for avi service engines. + min_cpu_usage: + description: + - When cpu usage on an se falls below the minimum threshold, virtual services hosted on the se may be consolidated onto other underutilized ses. + - After consolidation, unused service engines may then be eligible for deletion. + - Allowed values are 20-60. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + min_scaleout_per_vs: + description: + - Minimum number of active service engines for the virtual service. + - Allowed values are 1-64. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + min_se: + description: + - Minimum number of services engines in this group (relevant for se autorebalance only). + - Allowed values are 0-1000. + - Field introduced in 17.2.13,18.1.3,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + minimum_connection_memory: + description: + - Indicates the percent of memory reserved for connections. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + minimum_required_config_memory: + description: + - Required available config memory to apply any configuration. + - Allowed values are 0-90. + - Field deprecated in 18.1.2. + - Field introduced in 18.1.1. + n_log_streaming_threads: + description: + - Number of threads to use for log streaming. + - Allowed values are 1-100. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + name: + description: + - Name of the object. + required: true + non_significant_log_throttle: + description: + - This setting limits the number of non-significant logs generated per second per core on this se. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + num_dispatcher_cores: + description: + - Number of dispatcher cores (0,1,2,4,8 or 16). + - If set to 0, then number of dispatcher cores is deduced automatically. + - Allowed values are 0,1,2,4,8,16. + - Field introduced in 17.2.12, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + num_flow_cores_sum_changes_to_ignore: + description: + - Number of changes in num flow cores sum to ignore. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + openstack_availability_zone: + description: + - Field deprecated in 17.1.1. + openstack_availability_zones: + description: + - Field introduced in 17.1.1. + openstack_mgmt_network_name: + description: + - Avi management network name. + openstack_mgmt_network_uuid: + description: + - Management network uuid. + os_reserved_memory: + description: + - Amount of extra memory to be reserved for use by the operating system on a service engine. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + per_app: + description: + - Per-app se mode is designed for deploying dedicated load balancers per app (vs). + - In this mode, each se is limited to a max of 2 vss. + - Vcpus in per-app ses count towards licensing usage at 25% rate. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + placement_mode: + description: + - If placement mode is 'auto', virtual services are automatically placed on service engines. + - Enum options - PLACEMENT_MODE_AUTO. + - Default value when not specified in API or module is interpreted by Avi Controller as PLACEMENT_MODE_AUTO. + realtime_se_metrics: + description: + - Enable or disable real time se metrics. + reboot_on_stop: + description: + - Reboot the system if the se is stopped. + - Field introduced in 17.2.16,18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_bandwidth_type: + description: + - Select the se bandwidth for the bandwidth license. + - Enum options - SE_BANDWIDTH_UNLIMITED, SE_BANDWIDTH_25M, SE_BANDWIDTH_200M, SE_BANDWIDTH_1000M, SE_BANDWIDTH_10000M. + - Field introduced in 17.2.5. + se_deprovision_delay: + description: + - Duration to preserve unused service engine virtual machines before deleting them. + - If traffic to a virtual service were to spike up abruptly, this se would still be available to be utilized again rather than creating a new se. + - If this value is set to 0, controller will never delete any ses and administrator has to manually cleanup unused ses. + - Allowed values are 0-525600. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + se_dos_profile: + description: + - Dosthresholdprofile settings for serviceenginegroup. + se_dpdk_pmd: + description: + - Determines if dpdk pool mode driver should be used or not 0 automatically determine based on hypervisor/nic type 1 unconditionally use dpdk + - poll mode driver 2 don't use dpdk poll mode driver. + - Allowed values are 0-2. + - Field introduced in 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_flow_probe_retries: + description: + - Flow probe retry count if no replies are received. + - Allowed values are 0-5. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + se_flow_probe_timer: + description: + - Timeout in milliseconds for flow probe entries. + - Allowed values are 10-200. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + se_ipc_udp_port: + description: + - Udp port for se_dp ipc in docker bridge mode. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + se_name_prefix: + description: + - Prefix to use for virtual machine name of service engines. + - Default value when not specified in API or module is interpreted by Avi Controller as Avi. + se_pcap_lookahead: + description: + - Enables lookahead mode of packet receive in pcap mode. + - Introduced to overcome an issue with hv_netvsc driver. + - Lookahead mode attempts to ensure that application and kernel's view of the receive rings are consistent. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_pcap_reinit_frequency: + description: + - Frequency in seconds at which periodically a pcap reinit check is triggered. + - May be used in conjunction with the configuration pcap_reinit_threshold. + - (valid range 15 mins - 12 hours, 0 - disables). + - Allowed values are 900-43200. + - Special values are 0- 'disable'. + - Field introduced in 17.2.13, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_pcap_reinit_threshold: + description: + - Threshold for input packet receive errors in pcap mode exceeding which a pcap reinit is triggered. + - If not set, an unconditional reinit is performed. + - This value is checked every pcap_reinit_frequency interval. + - Field introduced in 17.2.13, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_probe_port: + description: + - Tcp port on se where echo service will be run. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 7. + se_remote_punt_udp_port: + description: + - Udp port for punted packets in docker bridge mode. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1501. + se_routing: + description: + - Enable routing via service engine datapath. + - When disabled, routing is done by the linux kernel. + - Ip routing needs to be enabled in service engine group for se routing to be effective. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + se_sb_dedicated_core: + description: + - Sideband traffic will be handled by a dedicated core. + - Field introduced in 16.5.2, 17.1.9, 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_sb_threads: + description: + - Number of sideband threads per se. + - Allowed values are 1-128. + - Field introduced in 16.5.2, 17.1.9, 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + se_thread_multiplier: + description: + - Multiplier for se threads based on vcpu. + - Allowed values are 1-10. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + se_tracert_port_range: + description: + - Traceroute port range. + - Field introduced in 17.2.8. + se_tunnel_mode: + description: + - Determines if dsr from secondary se is active or not 0 automatically determine based on hypervisor type. + - 1 disable dsr unconditionally. + - 2 enable dsr unconditionally. + - Allowed values are 0-2. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_tunnel_udp_port: + description: + - Udp port for tunneled packets from secondary to primary se in docker bridge mode. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1550. + se_udp_encap_ipc: + description: + - Determines if se-se ipc messages are encapsulated in a udp header 0 automatically determine based on hypervisor type. + - 1 use udp encap unconditionally. + - Allowed values are 0-1. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_use_dpdk: + description: + - Determines if dpdk library should be used or not 0 automatically determine based on hypervisor type 1 use dpdk if pcap is not enabled 2 + - don't use dpdk. + - Allowed values are 0-2. + - Field introduced in 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_vs_hb_max_pkts_in_batch: + description: + - Maximum number of aggregated vs heartbeat packets to send in a batch. + - Allowed values are 1-256. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. + se_vs_hb_max_vs_in_pkt: + description: + - Maximum number of virtualservices for which heartbeat messages are aggregated in one packet. + - Allowed values are 1-1024. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 256. + self_se_election: + description: + - Enable ses to elect a primary amongst themselves in the absence of a connectivity to controller. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + service_ip6_subnets: + description: + - Ipv6 subnets assigned to the se group. + - Required for vs group placement. + - Field introduced in 18.1.1. + service_ip_subnets: + description: + - Subnets assigned to the se group. + - Required for vs group placement. + - Field introduced in 17.1.1. + shm_minimum_config_memory: + description: + - Minimum required shared memory to apply any configuration. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + significant_log_throttle: + description: + - This setting limits the number of significant logs generated per second per core on this se. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + ssl_preprocess_sni_hostname: + description: + - (beta) preprocess ssl client hello for sni hostname extension.if set to true, this will apply sni child's ssl protocol(s), if they are different + - from sni parent's allowed ssl protocol(s). + - Field introduced in 17.2.12, 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + udf_log_throttle: + description: + - This setting limits the number of udf logs generated per second per core on this se. + - Udf logs are generated due to the configured client log filters or the rules with logging enabled. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + url: + description: + - Avi controller URL of the object. + use_standard_alb: + description: + - Use standard sku azure load balancer. + - By default cloud level flag is set. + - If not set, it inherits/uses the use_standard_alb flag from the cloud. + - Field introduced in 18.2.3. + type: bool + uuid: + description: + - Unique object identifier of the object. + vcenter_clusters: + description: + - Vcenterclusters settings for serviceenginegroup. + vcenter_datastore_mode: + description: + - Enum options - vcenter_datastore_any, vcenter_datastore_local, vcenter_datastore_shared. + - Default value when not specified in API or module is interpreted by Avi Controller as VCENTER_DATASTORE_ANY. + vcenter_datastores: + description: + - List of vcenterdatastore. + vcenter_datastores_include: + description: + - Boolean flag to set vcenter_datastores_include. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + vcenter_folder: + description: + - Folder to place all the service engine virtual machines in vcenter. + - Default value when not specified in API or module is interpreted by Avi Controller as AviSeFolder. + vcenter_hosts: + description: + - Vcenterhosts settings for serviceenginegroup. + vcpus_per_se: + description: + - Number of vcpus for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + vip_asg: + description: + - When vip_asg is set, vip configuration will be managed by avi.user will be able to configure vip_asg or vips individually at the time of create. + - Field introduced in 17.2.12, 18.1.2. + vs_host_redundancy: + description: + - Ensure primary and secondary service engines are deployed on different physical hosts. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + vs_scalein_timeout: + description: + - Time to wait for the scaled in se to drain existing flows before marking the scalein done. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + vs_scalein_timeout_for_upgrade: + description: + - During se upgrade, time to wait for the scaled-in se to drain existing flows before marking the scalein done. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + vs_scaleout_timeout: + description: + - Time to wait for the scaled out se to become ready before marking the scaleout done. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + vs_se_scaleout_additional_wait_time: + description: + - Wait time for sending scaleout ready notification after virtual service is marked up. + - In certain deployments, there may be an additional delay to accept traffic. + - For example, for bgp, some time is needed for route advertisement. + - Allowed values are 0-20. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + vs_se_scaleout_ready_timeout: + description: + - Timeout in seconds for service engine to sendscaleout ready notification of a virtual service. + - Allowed values are 0-60. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 25. + vs_switchover_timeout: + description: + - During se upgrade in a legacy active/standby segroup, time to wait for the new primary se to accept flows before marking the switchover done. + - Field introduced in 17.2.13,18.1.4,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + vss_placement: + description: + - Parameters to place virtual services on only a subset of the cores of an se. + - Field introduced in 17.2.5. + vss_placement_enabled: + description: + - If set, virtual services will be placed on only a subset of the cores of an se. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + waf_learning_interval: + description: + - Frequency with which se publishes waf learning. + - Allowed values are 1-43200. + - Field deprecated in 18.2.3. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + waf_learning_memory: + description: + - Amount of memory reserved on se for waf learning. + - Cannot exceed 5% of se memory. + - Field deprecated in 18.2.3. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + waf_mempool: + description: + - Enable memory pool for waf. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + waf_mempool_size: + description: + - Memory pool size used for waf. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServiceEngineGroup object + community.network.avi_serviceenginegroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serviceenginegroup +""" + +RETURN = ''' +obj: + description: ServiceEngineGroup (api/serviceenginegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + accelerated_networking=dict(type='bool',), + active_standby=dict(type='bool',), + additional_config_memory=dict(type='int',), + advertise_backend_networks=dict(type='bool',), + aggressive_failure_detection=dict(type='bool',), + algo=dict(type='str',), + allow_burst=dict(type='bool',), + app_cache_percent=dict(type='int',), + app_learning_memory_percent=dict(type='int',), + archive_shm_limit=dict(type='int',), + async_ssl=dict(type='bool',), + async_ssl_threads=dict(type='int',), + auto_rebalance=dict(type='bool',), + auto_rebalance_capacity_per_se=dict(type='list',), + auto_rebalance_criteria=dict(type='list',), + auto_rebalance_interval=dict(type='int',), + auto_redistribute_active_standby_load=dict(type='bool',), + bgp_state_update_interval=dict(type='int',), + buffer_se=dict(type='int',), + cloud_ref=dict(type='str',), + config_debugs_on_all_cores=dict(type='bool',), + connection_memory_percentage=dict(type='int',), + cpu_reserve=dict(type='bool',), + cpu_socket_affinity=dict(type='bool',), + custom_securitygroups_data=dict(type='list',), + custom_securitygroups_mgmt=dict(type='list',), + custom_tag=dict(type='list',), + data_network_id=dict(type='str',), + datascript_timeout=dict(type='int',), + dedicated_dispatcher_core=dict(type='bool',), + description=dict(type='str',), + disable_avi_securitygroups=dict(type='bool',), + disable_csum_offloads=dict(type='bool',), + disable_gro=dict(type='bool',), + disable_se_memory_check=dict(type='bool',), + disable_tso=dict(type='bool',), + disk_per_se=dict(type='int',), + distribute_load_active_standby=dict(type='bool',), + distribute_queues=dict(type='bool',), + enable_hsm_priming=dict(type='bool',), + enable_multi_lb=dict(type='bool',), + enable_routing=dict(type='bool',), + enable_vip_on_all_interfaces=dict(type='bool',), + enable_vmac=dict(type='bool',), + ephemeral_portrange_end=dict(type='int',), + ephemeral_portrange_start=dict(type='int',), + extra_config_multiplier=dict(type='float',), + extra_shared_config_memory=dict(type='int',), + floating_intf_ip=dict(type='list',), + floating_intf_ip_se_2=dict(type='list',), + flow_table_new_syn_max_entries=dict(type='int',), + free_list_size=dict(type='int',), + ha_mode=dict(type='str',), + hardwaresecuritymodulegroup_ref=dict(type='str',), + heap_minimum_config_memory=dict(type='int',), + hm_on_standby=dict(type='bool',), + host_attribute_key=dict(type='str',), + host_attribute_value=dict(type='str',), + host_gateway_monitor=dict(type='bool',), + hypervisor=dict(type='str',), + ignore_rtt_threshold=dict(type='int',), + ingress_access_data=dict(type='str',), + ingress_access_mgmt=dict(type='str',), + instance_flavor=dict(type='str',), + iptables=dict(type='list',), + least_load_core_selection=dict(type='bool',), + license_tier=dict(type='str',), + license_type=dict(type='str',), + log_disksz=dict(type='int',), + max_cpu_usage=dict(type='int',), + max_memory_per_mempool=dict(type='int',), + max_public_ips_per_lb=dict(type='int',), + max_rules_per_lb=dict(type='int',), + max_scaleout_per_vs=dict(type='int',), + max_se=dict(type='int',), + max_vs_per_se=dict(type='int',), + mem_reserve=dict(type='bool',), + memory_for_config_update=dict(type='int',), + memory_per_se=dict(type='int',), + mgmt_network_ref=dict(type='str',), + mgmt_subnet=dict(type='dict',), + min_cpu_usage=dict(type='int',), + min_scaleout_per_vs=dict(type='int',), + min_se=dict(type='int',), + minimum_connection_memory=dict(type='int',), + minimum_required_config_memory=dict(type='int',), + n_log_streaming_threads=dict(type='int',), + name=dict(type='str', required=True), + non_significant_log_throttle=dict(type='int',), + num_dispatcher_cores=dict(type='int',), + num_flow_cores_sum_changes_to_ignore=dict(type='int',), + openstack_availability_zone=dict(type='str',), + openstack_availability_zones=dict(type='list',), + openstack_mgmt_network_name=dict(type='str',), + openstack_mgmt_network_uuid=dict(type='str',), + os_reserved_memory=dict(type='int',), + per_app=dict(type='bool',), + placement_mode=dict(type='str',), + realtime_se_metrics=dict(type='dict',), + reboot_on_stop=dict(type='bool',), + se_bandwidth_type=dict(type='str',), + se_deprovision_delay=dict(type='int',), + se_dos_profile=dict(type='dict',), + se_dpdk_pmd=dict(type='int',), + se_flow_probe_retries=dict(type='int',), + se_flow_probe_timer=dict(type='int',), + se_ipc_udp_port=dict(type='int',), + se_name_prefix=dict(type='str',), + se_pcap_lookahead=dict(type='bool',), + se_pcap_reinit_frequency=dict(type='int',), + se_pcap_reinit_threshold=dict(type='int',), + se_probe_port=dict(type='int',), + se_remote_punt_udp_port=dict(type='int',), + se_routing=dict(type='bool',), + se_sb_dedicated_core=dict(type='bool',), + se_sb_threads=dict(type='int',), + se_thread_multiplier=dict(type='int',), + se_tracert_port_range=dict(type='dict',), + se_tunnel_mode=dict(type='int',), + se_tunnel_udp_port=dict(type='int',), + se_udp_encap_ipc=dict(type='int',), + se_use_dpdk=dict(type='int',), + se_vs_hb_max_pkts_in_batch=dict(type='int',), + se_vs_hb_max_vs_in_pkt=dict(type='int',), + self_se_election=dict(type='bool',), + service_ip6_subnets=dict(type='list',), + service_ip_subnets=dict(type='list',), + shm_minimum_config_memory=dict(type='int',), + significant_log_throttle=dict(type='int',), + ssl_preprocess_sni_hostname=dict(type='bool',), + tenant_ref=dict(type='str',), + udf_log_throttle=dict(type='int',), + url=dict(type='str',), + use_standard_alb=dict(type='bool',), + uuid=dict(type='str',), + vcenter_clusters=dict(type='dict',), + vcenter_datastore_mode=dict(type='str',), + vcenter_datastores=dict(type='list',), + vcenter_datastores_include=dict(type='bool',), + vcenter_folder=dict(type='str',), + vcenter_hosts=dict(type='dict',), + vcpus_per_se=dict(type='int',), + vip_asg=dict(type='dict',), + vs_host_redundancy=dict(type='bool',), + vs_scalein_timeout=dict(type='int',), + vs_scalein_timeout_for_upgrade=dict(type='int',), + vs_scaleout_timeout=dict(type='int',), + vs_se_scaleout_additional_wait_time=dict(type='int',), + vs_se_scaleout_ready_timeout=dict(type='int',), + vs_switchover_timeout=dict(type='int',), + vss_placement=dict(type='dict',), + vss_placement_enabled=dict(type='bool',), + waf_learning_interval=dict(type='int',), + waf_learning_memory=dict(type='int',), + waf_mempool=dict(type='bool',), + waf_mempool_size=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serviceenginegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_snmptrapprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_snmptrapprofile.py new file mode 100644 index 00000000..d8dec87d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_snmptrapprofile.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_snmptrapprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SnmpTrapProfile Avi RESTful Object +description: + - This module is used to configure SnmpTrapProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - A user-friendly name of the snmp trap configuration. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + trap_servers: + description: + - The ip address or hostname of the snmp trap destination server. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the snmp trap profile object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SnmpTrapProfile object + community.network.avi_snmptrapprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_snmptrapprofile +""" + +RETURN = ''' +obj: + description: SnmpTrapProfile (api/snmptrapprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + trap_servers=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'snmptrapprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslkeyandcertificate.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslkeyandcertificate.py new file mode 100644 index 00000000..e3328e88 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslkeyandcertificate.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_sslkeyandcertificate +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SSLKeyAndCertificate Avi RESTful Object +description: + - This module is used to configure SSLKeyAndCertificate object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + ca_certs: + description: + - Ca certificates in certificate chain. + certificate: + description: + - Sslcertificate settings for sslkeyandcertificate. + required: true + certificate_base64: + description: + - States if the certificate is base64 encoded. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + certificate_management_profile_ref: + description: + - It is a reference to an object of type certificatemanagementprofile. + created_by: + description: + - Creator name. + dynamic_params: + description: + - Dynamic parameters needed for certificate management profile. + enckey_base64: + description: + - Encrypted private key corresponding to the private key (e.g. + - Those generated by an hsm such as thales nshield). + enckey_name: + description: + - Name of the encrypted private key (e.g. + - Those generated by an hsm such as thales nshield). + format: + description: + - Format of the key/certificate file. + - Enum options - SSL_PEM, SSL_PKCS12. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_PEM. + hardwaresecuritymodulegroup_ref: + description: + - It is a reference to an object of type hardwaresecuritymodulegroup. + key: + description: + - Private key. + key_base64: + description: + - States if the private key is base64 encoded. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + key_params: + description: + - Sslkeyparams settings for sslkeyandcertificate. + key_passphrase: + description: + - Passphrase used to encrypt the private key. + - Field introduced in 18.1.2, 18.2.1. + name: + description: + - Name of the object. + required: true + status: + description: + - Enum options - ssl_certificate_finished, ssl_certificate_pending. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_CERTIFICATE_FINISHED. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Enum options - ssl_certificate_type_virtualservice, ssl_certificate_type_system, ssl_certificate_type_ca. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a SSL Key and Certificate + community.network.avi_sslkeyandcertificate: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + key: | + -----BEGIN PRIVATE KEY----- + .... + -----END PRIVATE KEY----- + certificate: + self_signed: true + certificate: | + -----BEGIN CERTIFICATE----- + .... + -----END CERTIFICATE----- + type: SSL_CERTIFICATE_TYPE_VIRTUALSERVICE + name: MyTestCert +""" + +RETURN = ''' +obj: + description: SSLKeyAndCertificate (api/sslkeyandcertificate) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + ca_certs=dict(type='list',), + certificate=dict(type='dict', required=True), + certificate_base64=dict(type='bool',), + certificate_management_profile_ref=dict(type='str',), + created_by=dict(type='str',), + dynamic_params=dict(type='list',), + enckey_base64=dict(type='str',), + enckey_name=dict(type='str',), + format=dict(type='str',), + hardwaresecuritymodulegroup_ref=dict(type='str',), + key=dict(type='str', no_log=True,), + key_base64=dict(type='bool',), + key_params=dict(type='dict',), + key_passphrase=dict(type='str', no_log=True,), + name=dict(type='str', required=True), + status=dict(type='str',), + tenant_ref=dict(type='str',), + type=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'sslkeyandcertificate', + set(['key_passphrase', 'key'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslprofile.py new file mode 100644 index 00000000..c98fbf14 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_sslprofile.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_sslprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SSLProfile Avi RESTful Object +description: + - This module is used to configure SSLProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + accepted_ciphers: + description: + - Ciphers suites represented as defined by U(http://www.openssl.org/docs/apps/ciphers.html). + - Default value when not specified in API or module is interpreted by Avi Controller as AES:3DES:RC4. + accepted_versions: + description: + - Set of versions accepted by the server. + cipher_enums: + description: + - Enum options - tls_ecdhe_ecdsa_with_aes_128_gcm_sha256, tls_ecdhe_ecdsa_with_aes_256_gcm_sha384, tls_ecdhe_rsa_with_aes_128_gcm_sha256, + - tls_ecdhe_rsa_with_aes_256_gcm_sha384, tls_ecdhe_ecdsa_with_aes_128_cbc_sha256, tls_ecdhe_ecdsa_with_aes_256_cbc_sha384, + - tls_ecdhe_rsa_with_aes_128_cbc_sha256, tls_ecdhe_rsa_with_aes_256_cbc_sha384, tls_rsa_with_aes_128_gcm_sha256, tls_rsa_with_aes_256_gcm_sha384, + - tls_rsa_with_aes_128_cbc_sha256, tls_rsa_with_aes_256_cbc_sha256, tls_ecdhe_ecdsa_with_aes_128_cbc_sha, tls_ecdhe_ecdsa_with_aes_256_cbc_sha, + - tls_ecdhe_rsa_with_aes_128_cbc_sha, tls_ecdhe_rsa_with_aes_256_cbc_sha, tls_rsa_with_aes_128_cbc_sha, tls_rsa_with_aes_256_cbc_sha, + - tls_rsa_with_3des_ede_cbc_sha, tls_rsa_with_rc4_128_sha. + description: + description: + - User defined description for the object. + dhparam: + description: + - Dh parameters used in ssl. + - At this time, it is not configurable and is set to 2048 bits. + enable_ssl_session_reuse: + description: + - Enable ssl session re-use. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + prefer_client_cipher_ordering: + description: + - Prefer the ssl cipher ordering presented by the client during the ssl handshake over the one specified in the ssl profile. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + send_close_notify: + description: + - Send 'close notify' alert message for a clean shutdown of the ssl connection. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ssl_rating: + description: + - Sslrating settings for sslprofile. + ssl_session_timeout: + description: + - The amount of time in seconds before an ssl session expires. + - Default value when not specified in API or module is interpreted by Avi Controller as 86400. + tags: + description: + - List of tag. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Ssl profile type. + - Enum options - SSL_PROFILE_TYPE_APPLICATION, SSL_PROFILE_TYPE_SYSTEM. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_PROFILE_TYPE_APPLICATION. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create SSL profile with list of allowed ciphers + community.network.avi_sslprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + accepted_ciphers: > + ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA: + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384: + AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA: + AES256-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA384: + ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA + accepted_versions: + - type: SSL_VERSION_TLS1 + - type: SSL_VERSION_TLS1_1 + - type: SSL_VERSION_TLS1_2 + cipher_enums: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_CBC_SHA256 + - TLS_RSA_WITH_AES_256_CBC_SHA256 + - TLS_RSA_WITH_AES_128_CBC_SHA + - TLS_RSA_WITH_AES_256_CBC_SHA + - TLS_RSA_WITH_3DES_EDE_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + name: PFS-BOTH-RSA-EC + send_close_notify: true + ssl_rating: + compatibility_rating: SSL_SCORE_EXCELLENT + performance_rating: SSL_SCORE_EXCELLENT + security_score: '100.0' + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: SSLProfile (api/sslprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + accepted_ciphers=dict(type='str',), + accepted_versions=dict(type='list',), + cipher_enums=dict(type='list',), + description=dict(type='str',), + dhparam=dict(type='str',), + enable_ssl_session_reuse=dict(type='bool',), + name=dict(type='str', required=True), + prefer_client_cipher_ordering=dict(type='bool',), + send_close_notify=dict(type='bool',), + ssl_rating=dict(type='dict',), + ssl_session_timeout=dict(type='int',), + tags=dict(type='list',), + tenant_ref=dict(type='str',), + type=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'sslprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_stringgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_stringgroup.py new file mode 100644 index 00000000..d88ed4d9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_stringgroup.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_stringgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of StringGroup Avi RESTful Object +description: + - This module is used to configure StringGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + kv: + description: + - Configure key value in the string group. + name: + description: + - Name of the string group. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of stringgroup. + - Enum options - SG_TYPE_STRING, SG_TYPE_KEYVAL. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_TYPE_STRING. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the string group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a string group configuration + community.network.avi_stringgroup: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + kv: + - key: text/html + - key: text/xml + - key: text/plain + - key: text/css + - key: text/javascript + - key: application/javascript + - key: application/x-javascript + - key: application/xml + - key: application/pdf + name: System-Compressible-Content-Types + tenant_ref: admin + type: SG_TYPE_STRING +""" + +RETURN = ''' +obj: + description: StringGroup (api/stringgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + kv=dict(type='list',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'stringgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_systemconfiguration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_systemconfiguration.py new file mode 100644 index 00000000..e44ac67c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_systemconfiguration.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_systemconfiguration +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SystemConfiguration Avi RESTful Object +description: + - This module is used to configure SystemConfiguration object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + admin_auth_configuration: + description: + - Adminauthconfiguration settings for systemconfiguration. + default_license_tier: + description: + - Specifies the default license tier which would be used by new clouds. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + - Default value when not specified in API or module is interpreted by Avi Controller as ENTERPRISE_18. + dns_configuration: + description: + - Dnsconfiguration settings for systemconfiguration. + dns_virtualservice_refs: + description: + - Dns virtualservices hosting fqdn records for applications across avi vantage. + - If no virtualservices are provided, avi vantage will provide dns services for configured applications. + - Switching back to avi vantage from dns virtualservices is not allowed. + - It is a reference to an object of type virtualservice. + docker_mode: + description: + - Boolean flag to set docker_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + email_configuration: + description: + - Emailconfiguration settings for systemconfiguration. + global_tenant_config: + description: + - Tenantconfiguration settings for systemconfiguration. + linux_configuration: + description: + - Linuxconfiguration settings for systemconfiguration. + mgmt_ip_access_control: + description: + - Configure ip access control for controller to restrict open access. + ntp_configuration: + description: + - Ntpconfiguration settings for systemconfiguration. + portal_configuration: + description: + - Portalconfiguration settings for systemconfiguration. + proxy_configuration: + description: + - Proxyconfiguration settings for systemconfiguration. + secure_channel_configuration: + description: + - Configure secure channel properties. + - Field introduced in 18.1.4, 18.2.1. + snmp_configuration: + description: + - Snmpconfiguration settings for systemconfiguration. + ssh_ciphers: + description: + - Allowed ciphers list for ssh to the management interface on the controller and service engines. + - If this is not specified, all the default ciphers are allowed. + ssh_hmacs: + description: + - Allowed hmac list for ssh to the management interface on the controller and service engines. + - If this is not specified, all the default hmacs are allowed. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + welcome_workflow_complete: + description: + - This flag is set once the initial controller setup workflow is complete. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SystemConfiguration object + community.network.avi_systemconfiguration: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_systemconfiguration +""" + +RETURN = ''' +obj: + description: SystemConfiguration (api/systemconfiguration) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + admin_auth_configuration=dict(type='dict',), + default_license_tier=dict(type='str',), + dns_configuration=dict(type='dict',), + dns_virtualservice_refs=dict(type='list',), + docker_mode=dict(type='bool',), + email_configuration=dict(type='dict',), + global_tenant_config=dict(type='dict',), + linux_configuration=dict(type='dict',), + mgmt_ip_access_control=dict(type='dict',), + ntp_configuration=dict(type='dict',), + portal_configuration=dict(type='dict',), + proxy_configuration=dict(type='dict',), + secure_channel_configuration=dict(type='dict',), + snmp_configuration=dict(type='dict',), + ssh_ciphers=dict(type='list',), + ssh_hmacs=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + welcome_workflow_complete=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'systemconfiguration', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_tenant.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_tenant.py new file mode 100644 index 00000000..d3c12088 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_tenant.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_tenant +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Tenant Avi RESTful Object +description: + - This module is used to configure Tenant object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + config_settings: + description: + - Tenantconfiguration settings for tenant. + created_by: + description: + - Creator of this tenant. + description: + description: + - User defined description for the object. + local: + description: + - Boolean flag to set local. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Tenant using Service Engines in provider mode + community.network.avi_tenant: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + config_settings: + se_in_provider_context: false + tenant_access_to_provider_se: true + tenant_vrf: false + description: VCenter, Open Stack, AWS Virtual services + local: true + name: Demo +""" + +RETURN = ''' +obj: + description: Tenant (api/tenant) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + config_settings=dict(type='dict',), + created_by=dict(type='str',), + description=dict(type='str',), + local=dict(type='bool',), + name=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'tenant', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_trafficcloneprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_trafficcloneprofile.py new file mode 100644 index 00000000..406ccc6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_trafficcloneprofile.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_trafficcloneprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of TrafficCloneProfile Avi RESTful Object +description: + - This module is used to configure TrafficCloneProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + clone_servers: + description: + - Field introduced in 17.1.1. + cloud_ref: + description: + - It is a reference to an object of type cloud. + - Field introduced in 17.1.1. + name: + description: + - Name for the traffic clone profile. + - Field introduced in 17.1.1. + required: true + preserve_client_ip: + description: + - Specifies if client ip needs to be preserved to clone destination. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the traffic clone profile. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create TrafficCloneProfile object + community.network.avi_trafficcloneprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_trafficcloneprofile +""" + +RETURN = ''' +obj: + description: TrafficCloneProfile (api/trafficcloneprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + clone_servers=dict(type='list',), + cloud_ref=dict(type='str',), + name=dict(type='str', required=True), + preserve_client_ip=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'trafficcloneprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_user.py new file mode 100644 index 00000000..fb55695d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_user.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +""" +# Created on Aug 2, 2018 +# +# @author: Shrikant Chaudhari (shrikant.chaudhari@avinetworks.com) GitHub ID: gitshrikant +# +# module_check: supported +# +# 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 . +# +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_user +author: Shrikant Chaudhari (@gitshrikant) +short_description: Avi User Module +description: + - This module can be used for creation, updation and deletion of a user. +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + type: str + name: + description: + - Full name of the user. + required: true + type: str + obj_username: + description: + - Name that the user will supply when signing into Avi Vantage, such as jdoe or jdoe@avinetworks.com. + required: true + type: str + obj_password: + description: + - You may either enter a case-sensitive password in this field for the new or existing user. + required: true + type: str + email: + description: + - Email address of the user. This field is used when a user loses their password and requests to have it reset. See Password Recovery. + type: str + access: + description: + - Access settings (write, read, or no access) for each type of resource within Vantage. + type: list + is_superuser: + description: + - If the user will need to have the same privileges as the admin account, set it to true. + type: bool + is_active: + description: + - Activates the current user account. + type: bool + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["post", "put", "patch"] + type: str + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + type: str + user_profile_ref: + description: + - Refer user profile. + - This can also be full URI same as it comes in response payload + type: str + default_tenant_ref: + description: + - Default tenant reference. + - This can also be full URI same as it comes in response payload + default: /api/tenant?name=admin + type: str + + +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: User creation + community.network.avi_user: + controller: "" + username: "" + password: "" + api_version: "" + name: "testuser" + obj_username: "testuser" + obj_password: "test123" + email: "test@abc.test" + access: + - role_ref: "/api/role?name=Tenant-Admin" + tenant_ref: "/api/tenant/admin#admin" + user_profile_ref: "/api/useraccountprofile?name=Default-User-Account-Profile" + is_active: true + is_superuser: true + default_tenant_ref: "/api/tenant?name=admin" + + - name: User creation + community.network.avi_user: + controller: "" + username: "" + password: "" + api_version: "" + name: "testuser" + obj_username: "testuser2" + obj_password: "password" + email: "testuser2@abc.test" + access: + - role_ref: "https://192.0.2.10/api/role?name=Tenant-Admin" + tenant_ref: "https://192.0.2.10/api/tenant/admin#admin" + user_profile_ref: "https://192.0.2.10/api/useraccountprofile?name=Default-User-Account-Profile" + is_active: true + is_superuser: true + default_tenant_ref: "https://192.0.2.10/api/tenant?name=admin" +''' + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.ansible_utils import ( + avi_ansible_api) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + name=dict(type='str', required=True), + obj_username=dict(type='str', required=True), + obj_password=dict(type='str', required=True, no_log=True), + access=dict(type='list',), + email=dict(type='str',), + is_superuser=dict(type='bool',), + is_active=dict(type='bool',), + avi_api_update_method=dict(default='put', + choices=['post', 'put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + user_profile_ref=dict(type='str',), + default_tenant_ref=dict(type='str', default='/api/tenant?name=admin'), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'user', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccount.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccount.py new file mode 100644 index 00000000..9d3f4c2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccount.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: not supported +# +# 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 . +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_useraccount +author: Chaitanya Deshpande (@chaitanyaavi) +short_description: Avi UserAccount Module +description: + - This module can be used for updating the password of a user. + - This module is useful for setting up admin password for Controller bootstrap. +requirements: [ avisdk ] +options: + old_password: + description: + - Old password for update password or default password for bootstrap. + force_change: + description: + - If specifically set to true then old password is tried first for controller and then the new password is + tried. If not specified this flag then the new password is tried first. + default: false + +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Update user password + community.network.avi_useraccount: + controller: "" + username: "" + password: new_password + old_password: "" + api_version: "" + force_change: false + + - name: Update user password using avi_credentials + community.network.avi_useraccount: + avi_credentials: "" + old_password: "" + force_change: false +''' + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, avi_obj_cmp, + cleanup_absent_fields, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + old_password=dict(type='str', required=True, no_log=True), + # Flag to specify priority of old/new password while establishing session with controller. + # To handle both Saas and conventional (Entire state in playbook) scenario. + force_change=dict(type='bool', default=False) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + old_password = module.params.get('old_password') + force_change = module.params.get('force_change', False) + data = { + 'old_password': old_password, + 'password': api_creds.password + } + # First try old password if 'force_change' is set to true + if force_change: + first_pwd = old_password + second_pwd = api_creds.password + # First try new password if 'force_change' is set to false or not specified in playbook. + else: + first_pwd = api_creds.password + second_pwd = old_password + password_changed = False + try: + api = ApiSession.get_session( + api_creds.controller, api_creds.username, + password=first_pwd, timeout=api_creds.timeout, + tenant=api_creds.tenant, tenant_uuid=api_creds.tenant_uuid, + token=api_creds.token, port=api_creds.port) + if force_change: + rsp = api.put('useraccount', data=data) + if rsp: + password_changed = True + except Exception: + pass + if not password_changed: + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=second_pwd, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + if not force_change: + rsp = api.put('useraccount', data=data) + if rsp: + password_changed = True + if password_changed: + return ansible_return(module, rsp, True, req=data) + else: + return ansible_return(module, rsp, False, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccountprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccountprofile.py new file mode 100644 index 00000000..d098aff0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_useraccountprofile.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_useraccountprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of UserAccountProfile Avi RESTful Object +description: + - This module is used to configure UserAccountProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + account_lock_timeout: + description: + - Lock timeout period (in minutes). + - Default is 30 minutes. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + credentials_timeout_threshold: + description: + - The time period after which credentials expire. + - Default is 180 days. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + max_concurrent_sessions: + description: + - Maximum number of concurrent sessions allowed. + - There are unlimited sessions by default. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + max_login_failure_count: + description: + - Number of login attempts before lockout. + - Default is 3 attempts. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + max_password_history_count: + description: + - Maximum number of passwords to be maintained in the password history. + - Default is 4 passwords. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + name: + description: + - Name of the object. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create UserAccountProfile object + community.network.avi_useraccountprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_useraccountprofile +""" + +RETURN = ''' +obj: + description: UserAccountProfile (api/useraccountprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + account_lock_timeout=dict(type='int',), + credentials_timeout_threshold=dict(type='int',), + max_concurrent_sessions=dict(type='int',), + max_login_failure_count=dict(type='int',), + max_password_history_count=dict(type='int',), + name=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'useraccountprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_virtualservice.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_virtualservice.py new file mode 100644 index 00000000..eec62055 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_virtualservice.py @@ -0,0 +1,652 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_virtualservice +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VirtualService Avi RESTful Object +description: + - This module is used to configure VirtualService object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + active_standby_se_tag: + description: + - This configuration only applies if the virtualservice is in legacy active standby ha mode and load distribution among active standby is enabled. + - This field is used to tag the virtualservice so that virtualservices with the same tag will share the same active serviceengine. + - Virtualservices with different tags will have different active serviceengines. + - If one of the serviceengine's in the serviceenginegroup fails, all virtualservices will end up using the same active serviceengine. + - Redistribution of the virtualservices can be either manual or automated when the failed serviceengine recovers. + - Redistribution is based on the auto redistribute property of the serviceenginegroup. + - Enum options - ACTIVE_STANDBY_SE_1, ACTIVE_STANDBY_SE_2. + - Default value when not specified in API or module is interpreted by Avi Controller as ACTIVE_STANDBY_SE_1. + allow_invalid_client_cert: + description: + - Process request even if invalid client certificate is presented. + - Datascript apis need to be used for processing of such requests. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + analytics_policy: + description: + - Determines analytics settings for the application. + analytics_profile_ref: + description: + - Specifies settings related to analytics. + - It is a reference to an object of type analyticsprofile. + apic_contract_graph: + description: + - The name of the contract/graph associated with the virtual service. + - Should be in the format. + - This is applicable only for service integration mode with cisco apic controller. + - Field introduced in 17.2.12,18.1.2. + application_profile_ref: + description: + - Enable application layer specific features for the virtual service. + - It is a reference to an object of type applicationprofile. + auto_allocate_floating_ip: + description: + - Auto-allocate floating/elastic ip from the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + auto_allocate_ip: + description: + - Auto-allocate vip from the provided subnet. + - Field deprecated in 17.1.1. + type: bool + availability_zone: + description: + - Availability-zone to place the virtual service. + - Field deprecated in 17.1.1. + avi_allocated_fip: + description: + - (internal-use) fip allocated by avi in the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + avi_allocated_vip: + description: + - (internal-use) vip allocated by avi in the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + azure_availability_set: + description: + - (internal-use)applicable for azure only. + - Azure availability set to which this vs is associated. + - Internally set by the cloud connector. + - Field introduced in 17.2.12, 18.1.2. + bulk_sync_kvcache: + description: + - (this is a beta feature). + - Sync key-value cache to the new ses when vs is scaled out. + - For ex ssl sessions are stored using vs's key-value cache. + - When the vs is scaled out, the ssl session information is synced to the new se, allowing existing ssl sessions to be reused on the new se. + - Field introduced in 17.2.7, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + client_auth: + description: + - Http authentication configuration for protected resources. + close_client_conn_on_config_update: + description: + - Close client connection on vs config update. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + cloud_config_cksum: + description: + - Checksum of cloud configuration for vs. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + cloud_type: + description: + - Enum options - cloud_none, cloud_vcenter, cloud_openstack, cloud_aws, cloud_vca, cloud_apic, cloud_mesos, cloud_linuxserver, cloud_docker_ucp, + - cloud_rancher, cloud_oshift_k8s, cloud_azure, cloud_gcp. + - Default value when not specified in API or module is interpreted by Avi Controller as CLOUD_NONE. + connections_rate_limit: + description: + - Rate limit the incoming connections to this virtual service. + content_rewrite: + description: + - Profile used to match and rewrite strings in request and/or response body. + created_by: + description: + - Creator name. + delay_fairness: + description: + - Select the algorithm for qos fairness. + - This determines how multiple virtual services sharing the same service engines will prioritize traffic over a congested network. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + discovered_network_ref: + description: + - (internal-use) discovered networks providing reachability for client facing virtual service ip. + - This field is deprecated. + - It is a reference to an object of type network. + - Field deprecated in 17.1.1. + discovered_networks: + description: + - (internal-use) discovered networks providing reachability for client facing virtual service ip. + - This field is used internally by avi, not editable by the user. + - Field deprecated in 17.1.1. + discovered_subnet: + description: + - (internal-use) discovered subnets providing reachability for client facing virtual service ip. + - This field is deprecated. + - Field deprecated in 17.1.1. + dns_info: + description: + - Service discovery specific data including fully qualified domain name, type and time-to-live of the dns record. + - Note that only one of fqdn and dns_info setting is allowed. + dns_policies: + description: + - Dns policies applied on the dns traffic of the virtual service. + - Field introduced in 17.1.1. + east_west_placement: + description: + - Force placement on all se's in service group (mesos mode only). + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_autogw: + description: + - Response traffic to clients will be sent back to the source mac address of the connection, rather than statically sent to a default gateway. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_rhi: + description: + - Enable route health injection using the bgp config in the vrf context. + type: bool + enable_rhi_snat: + description: + - Enable route health injection for source nat'ted floating ip address using the bgp config in the vrf context. + type: bool + enabled: + description: + - Enable or disable the virtual service. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + error_page_profile_ref: + description: + - Error page profile to be used for this virtualservice.this profile is used to send the custom error page to the client generated by the proxy. + - It is a reference to an object of type errorpageprofile. + - Field introduced in 17.2.4. + floating_ip: + description: + - Floating ip to associate with this virtual service. + - Field deprecated in 17.1.1. + floating_subnet_uuid: + description: + - If auto_allocate_floating_ip is true and more than one floating-ip subnets exist, then the subnet for the floating ip address allocation. + - This field is applicable only if the virtualservice belongs to an openstack or aws cloud. + - In openstack or aws cloud it is required when auto_allocate_floating_ip is selected. + - Field deprecated in 17.1.1. + flow_dist: + description: + - Criteria for flow distribution among ses. + - Enum options - LOAD_AWARE, CONSISTENT_HASH_SOURCE_IP_ADDRESS, CONSISTENT_HASH_SOURCE_IP_ADDRESS_AND_PORT. + - Default value when not specified in API or module is interpreted by Avi Controller as LOAD_AWARE. + flow_label_type: + description: + - Criteria for flow labelling. + - Enum options - NO_LABEL, APPLICATION_LABEL, SERVICE_LABEL. + - Default value when not specified in API or module is interpreted by Avi Controller as NO_LABEL. + fqdn: + description: + - Dns resolvable, fully qualified domain name of the virtualservice. + - Only one of 'fqdn' and 'dns_info' configuration is allowed. + host_name_xlate: + description: + - Translate the host name sent to the servers to this value. + - Translate the host name sent from servers back to the value used by the client. + http_policies: + description: + - Http policies applied on the data traffic of the virtual service. + ign_pool_net_reach: + description: + - Ignore pool servers network reachability constraints for virtual service placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ip_address: + description: + - Ip address of the virtual service. + - Field deprecated in 17.1.1. + ipam_network_subnet: + description: + - Subnet and/or network for allocating virtualservice ip by ipam provider module. + - Field deprecated in 17.1.1. + l4_policies: + description: + - L4 policies applied to the data traffic of the virtual service. + - Field introduced in 17.2.7. + limit_doser: + description: + - Limit potential dos attackers who exceed max_cps_per_client significantly to a fraction of max_cps_per_client for a while. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + max_cps_per_client: + description: + - Maximum connections per second per client ip. + - Allowed values are 10-1000. + - Special values are 0- 'unlimited'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + microservice_ref: + description: + - Microservice representing the virtual service. + - It is a reference to an object of type microservice. + min_pools_up: + description: + - Minimum number of up pools to mark vs up. + - Field introduced in 18.2.1, 17.2.12. + name: + description: + - Name for the virtual service. + required: true + network_profile_ref: + description: + - Determines network settings such as protocol, tcp or udp, and related options for the protocol. + - It is a reference to an object of type networkprofile. + network_ref: + description: + - Manually override the network on which the virtual service is placed. + - It is a reference to an object of type network. + - Field deprecated in 17.1.1. + network_security_policy_ref: + description: + - Network security policies for the virtual service. + - It is a reference to an object of type networksecuritypolicy. + nsx_securitygroup: + description: + - A list of nsx service groups representing the clients which can access the virtual ip of the virtual service. + - Field introduced in 17.1.1. + performance_limits: + description: + - Optional settings that determine performance limits like max connections or bandwidth etc. + pool_group_ref: + description: + - The pool group is an object that contains pools. + - It is a reference to an object of type poolgroup. + pool_ref: + description: + - The pool is an object that contains destination servers and related attributes such as load-balancing and persistence. + - It is a reference to an object of type pool. + port_uuid: + description: + - (internal-use) network port assigned to the virtual service ip address. + - Field deprecated in 17.1.1. + remove_listening_port_on_vs_down: + description: + - Remove listening port if virtualservice is down. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + requests_rate_limit: + description: + - Rate limit the incoming requests to this virtual service. + saml_sp_config: + description: + - Application-specific saml config. + - Field introduced in 18.2.3. + scaleout_ecmp: + description: + - Disable re-distribution of flows across service engines for a virtual service. + - Enable if the network itself performs flow hashing with ecmp in environments such as gcp. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_group_ref: + description: + - The service engine group to use for this virtual service. + - Moving to a new se group is disruptive to existing connections for this vs. + - It is a reference to an object of type serviceenginegroup. + security_policy_ref: + description: + - Security policy applied on the traffic of the virtual service. + - This policy is used to perform security actions such as distributed denial of service (ddos) attack mitigation, etc. + - It is a reference to an object of type securitypolicy. + - Field introduced in 18.2.1. + server_network_profile_ref: + description: + - Determines the network settings profile for the server side of tcp proxied connections. + - Leave blank to use the same settings as the client to vs side of the connection. + - It is a reference to an object of type networkprofile. + service_metadata: + description: + - Metadata pertaining to the service provided by this virtual service. + - In openshift/kubernetes environments, egress pod info is stored. + - Any user input to this field will be overwritten by avi vantage. + service_pool_select: + description: + - Select pool based on destination port. + services: + description: + - List of services defined for this virtual service. + sideband_profile: + description: + - Sideband configuration to be used for this virtualservice.it can be used for sending traffic to sideband vips for external inspection etc. + snat_ip: + description: + - Nat'ted floating source ip address(es) for upstream connection to servers. + sp_pool_refs: + description: + - Gslb pools used to manage site-persistence functionality. + - Each site-persistence pool contains the virtualservices in all the other sites, that is auto-generated by the gslb manager. + - This is a read-only field for the user. + - It is a reference to an object of type pool. + - Field introduced in 17.2.2. + ssl_key_and_certificate_refs: + description: + - Select or create one or two certificates, ec and/or rsa, that will be presented to ssl/tls terminated connections. + - It is a reference to an object of type sslkeyandcertificate. + ssl_profile_ref: + description: + - Determines the set of ssl versions and ciphers to accept for ssl/tls terminated connections. + - It is a reference to an object of type sslprofile. + ssl_profile_selectors: + description: + - Select ssl profile based on client ip address match. + - Field introduced in 18.2.3. + ssl_sess_cache_avg_size: + description: + - Expected number of ssl session cache entries (may be exceeded). + - Allowed values are 1024-16383. + - Default value when not specified in API or module is interpreted by Avi Controller as 1024. + sso_policy: + description: + - Client authentication and authorization policy for the virtualservice. + - Field deprecated in 18.2.3. + - Field introduced in 18.2.1. + sso_policy_ref: + description: + - The sso policy attached to the virtualservice. + - It is a reference to an object of type ssopolicy. + - Field introduced in 18.2.3. + static_dns_records: + description: + - List of static dns records applied to this virtual service. + - These are static entries and no health monitoring is performed against the ip addresses. + subnet: + description: + - Subnet providing reachability for client facing virtual service ip. + - Field deprecated in 17.1.1. + subnet_uuid: + description: + - It represents subnet for the virtual service ip address allocation when auto_allocate_ip is true.it is only applicable in openstack or aws cloud. + - This field is required if auto_allocate_ip is true. + - Field deprecated in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + topology_policies: + description: + - Topology policies applied on the dns traffic of the virtual service based ongslb topology algorithm. + - Field introduced in 18.2.3. + traffic_clone_profile_ref: + description: + - Server network or list of servers for cloning traffic. + - It is a reference to an object of type trafficcloneprofile. + - Field introduced in 17.1.1. + traffic_enabled: + description: + - Knob to enable the virtual service traffic on its assigned service engines. + - This setting is effective only when the enabled flag is set to true. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + type: + description: + - Specify if this is a normal virtual service, or if it is the parent or child of an sni-enabled virtual hosted virtual service. + - Enum options - VS_TYPE_NORMAL, VS_TYPE_VH_PARENT, VS_TYPE_VH_CHILD. + - Default value when not specified in API or module is interpreted by Avi Controller as VS_TYPE_NORMAL. + url: + description: + - Avi controller URL of the object. + use_bridge_ip_as_vip: + description: + - Use bridge ip as vip on each host in mesos deployments. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + use_vip_as_snat: + description: + - Use the virtual ip as the snat ip for health monitoring and sending traffic to the backend servers instead of the service engine interface ip. + - The caveat of enabling this option is that the virtualservice cannot be configued in an active-active ha mode. + - Dns based multi vip solution has to be used for ha & non-disruptive upgrade purposes. + - Field introduced in 17.1.9,17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Uuid of the virtualservice. + vh_domain_name: + description: + - The exact name requested from the client's sni-enabled tls hello domain name field. + - If this is a match, the parent vs will forward the connection to this child vs. + vh_parent_vs_uuid: + description: + - Specifies the virtual service acting as virtual hosting (sni) parent. + vip: + description: + - List of virtual service ips. + - While creating a 'shared vs',please use vsvip_ref to point to the shared entities. + - Field introduced in 17.1.1. + vrf_context_ref: + description: + - Virtual routing context that the virtual service is bound to. + - This is used to provide the isolation of the set of networks the application is attached to. + - It is a reference to an object of type vrfcontext. + vs_datascripts: + description: + - Datascripts applied on the data traffic of the virtual service. + vsvip_cloud_config_cksum: + description: + - Checksum of cloud configuration for vsvip. + - Internally set by cloud connector. + - Field introduced in 17.2.9, 18.1.2. + vsvip_ref: + description: + - Mostly used during the creation of shared vs, this field refers to entities that can be shared across virtual services. + - It is a reference to an object of type vsvip. + - Field introduced in 17.1.1. + waf_policy_ref: + description: + - Waf policy for the virtual service. + - It is a reference to an object of type wafpolicy. + - Field introduced in 17.2.1. + weight: + description: + - The quality of service weight to assign to traffic transmitted from this virtual service. + - A higher weight will prioritize traffic versus other virtual services sharing the same service engines. + - Allowed values are 1-128. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create SSL Virtual Service using Pool testpool2 + community.network.avi_virtualservice: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + name: newtestvs + state: present + performance_limits: + max_concurrent_connections: 1000 + services: + - port: 443 + enable_ssl: true + - port: 80 + ssl_profile_ref: '/api/sslprofile?name=System-Standard' + application_profile_ref: '/api/applicationprofile?name=System-Secure-HTTP' + ssl_key_and_certificate_refs: + - '/api/sslkeyandcertificate?name=System-Default-Cert' + ip_address: + addr: 10.90.131.103 + type: V4 + pool_ref: '/api/pool?name=testpool2' +""" + +RETURN = ''' +obj: + description: VirtualService (api/virtualservice) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + active_standby_se_tag=dict(type='str',), + allow_invalid_client_cert=dict(type='bool',), + analytics_policy=dict(type='dict',), + analytics_profile_ref=dict(type='str',), + apic_contract_graph=dict(type='str',), + application_profile_ref=dict(type='str',), + auto_allocate_floating_ip=dict(type='bool',), + auto_allocate_ip=dict(type='bool',), + availability_zone=dict(type='str',), + avi_allocated_fip=dict(type='bool',), + avi_allocated_vip=dict(type='bool',), + azure_availability_set=dict(type='str',), + bulk_sync_kvcache=dict(type='bool',), + client_auth=dict(type='dict',), + close_client_conn_on_config_update=dict(type='bool',), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + cloud_type=dict(type='str',), + connections_rate_limit=dict(type='dict',), + content_rewrite=dict(type='dict',), + created_by=dict(type='str',), + delay_fairness=dict(type='bool',), + description=dict(type='str',), + discovered_network_ref=dict(type='list',), + discovered_networks=dict(type='list',), + discovered_subnet=dict(type='list',), + dns_info=dict(type='list',), + dns_policies=dict(type='list',), + east_west_placement=dict(type='bool',), + enable_autogw=dict(type='bool',), + enable_rhi=dict(type='bool',), + enable_rhi_snat=dict(type='bool',), + enabled=dict(type='bool',), + error_page_profile_ref=dict(type='str',), + floating_ip=dict(type='dict',), + floating_subnet_uuid=dict(type='str',), + flow_dist=dict(type='str',), + flow_label_type=dict(type='str',), + fqdn=dict(type='str',), + host_name_xlate=dict(type='str',), + http_policies=dict(type='list',), + ign_pool_net_reach=dict(type='bool',), + ip_address=dict(type='dict',), + ipam_network_subnet=dict(type='dict',), + l4_policies=dict(type='list',), + limit_doser=dict(type='bool',), + max_cps_per_client=dict(type='int',), + microservice_ref=dict(type='str',), + min_pools_up=dict(type='int',), + name=dict(type='str', required=True), + network_profile_ref=dict(type='str',), + network_ref=dict(type='str',), + network_security_policy_ref=dict(type='str',), + nsx_securitygroup=dict(type='list',), + performance_limits=dict(type='dict',), + pool_group_ref=dict(type='str',), + pool_ref=dict(type='str',), + port_uuid=dict(type='str',), + remove_listening_port_on_vs_down=dict(type='bool',), + requests_rate_limit=dict(type='dict',), + saml_sp_config=dict(type='dict',), + scaleout_ecmp=dict(type='bool',), + se_group_ref=dict(type='str',), + security_policy_ref=dict(type='str',), + server_network_profile_ref=dict(type='str',), + service_metadata=dict(type='str',), + service_pool_select=dict(type='list',), + services=dict(type='list',), + sideband_profile=dict(type='dict',), + snat_ip=dict(type='list',), + sp_pool_refs=dict(type='list',), + ssl_key_and_certificate_refs=dict(type='list',), + ssl_profile_ref=dict(type='str',), + ssl_profile_selectors=dict(type='list',), + ssl_sess_cache_avg_size=dict(type='int',), + sso_policy=dict(type='dict',), + sso_policy_ref=dict(type='str',), + static_dns_records=dict(type='list',), + subnet=dict(type='dict',), + subnet_uuid=dict(type='str',), + tenant_ref=dict(type='str',), + topology_policies=dict(type='list',), + traffic_clone_profile_ref=dict(type='str',), + traffic_enabled=dict(type='bool',), + type=dict(type='str',), + url=dict(type='str',), + use_bridge_ip_as_vip=dict(type='bool',), + use_vip_as_snat=dict(type='bool',), + uuid=dict(type='str',), + vh_domain_name=dict(type='list',), + vh_parent_vs_uuid=dict(type='str',), + vip=dict(type='list',), + vrf_context_ref=dict(type='str',), + vs_datascripts=dict(type='list',), + vsvip_cloud_config_cksum=dict(type='str',), + vsvip_ref=dict(type='str',), + waf_policy_ref=dict(type='str',), + weight=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'virtualservice', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vrfcontext.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vrfcontext.py new file mode 100644 index 00000000..c4294245 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vrfcontext.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vrfcontext +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VrfContext Avi RESTful Object +description: + - This module is used to configure VrfContext object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + bgp_profile: + description: + - Bgp local and peer info. + cloud_ref: + description: + - It is a reference to an object of type cloud. + debugvrfcontext: + description: + - Configure debug flags for vrf. + - Field introduced in 17.1.1. + description: + description: + - User defined description for the object. + gateway_mon: + description: + - Configure ping based heartbeat check for gateway in service engines of vrf. + internal_gateway_monitor: + description: + - Configure ping based heartbeat check for all default gateways in service engines of vrf. + - Field introduced in 17.1.1. + name: + description: + - Name of the object. + required: true + static_routes: + description: + - List of staticroute. + system_default: + description: + - Boolean flag to set system_default. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VrfContext object + community.network.avi_vrfcontext: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vrfcontext +""" + +RETURN = ''' +obj: + description: VrfContext (api/vrfcontext) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + bgp_profile=dict(type='dict',), + cloud_ref=dict(type='str',), + debugvrfcontext=dict(type='dict',), + description=dict(type='str',), + gateway_mon=dict(type='list',), + internal_gateway_monitor=dict(type='dict',), + name=dict(type='str', required=True), + static_routes=dict(type='list',), + system_default=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vrfcontext', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsdatascriptset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsdatascriptset.py new file mode 100644 index 00000000..1558d00c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsdatascriptset.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vsdatascriptset +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VSDataScriptSet Avi RESTful Object +description: + - This module is used to configure VSDataScriptSet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.1.11,17.2.4. + datascript: + description: + - Datascripts to execute. + description: + description: + - User defined description for the object. + ipgroup_refs: + description: + - Uuid of ip groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type ipaddrgroup. + name: + description: + - Name for the virtual service datascript collection. + required: true + pool_group_refs: + description: + - Uuid of pool groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type poolgroup. + pool_refs: + description: + - Uuid of pools that could be referred by vsdatascriptset objects. + - It is a reference to an object of type pool. + protocol_parser_refs: + description: + - List of protocol parsers that could be referred by vsdatascriptset objects. + - It is a reference to an object of type protocolparser. + - Field introduced in 18.2.3. + string_group_refs: + description: + - Uuid of string groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type stringgroup. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the virtual service datascript collection. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VSDataScriptSet object + community.network.avi_vsdatascriptset: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vsdatascriptset +""" + +RETURN = ''' +obj: + description: VSDataScriptSet (api/vsdatascriptset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + datascript=dict(type='list',), + description=dict(type='str',), + ipgroup_refs=dict(type='list',), + name=dict(type='str', required=True), + pool_group_refs=dict(type='list',), + pool_refs=dict(type='list',), + protocol_parser_refs=dict(type='list',), + string_group_refs=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vsdatascriptset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsvip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsvip.py new file mode 100644 index 00000000..6aa86a3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_vsvip.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vsvip +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VsVip Avi RESTful Object +description: + - This module is used to configure VsVip object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + - Field introduced in 17.1.1. + dns_info: + description: + - Service discovery specific data including fully qualified domain name, type and time-to-live of the dns record. + - Field introduced in 17.1.1. + east_west_placement: + description: + - Force placement on all service engines in the service engine group (container clouds only). + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name for the vsvip object. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + use_standard_alb: + description: + - This overrides the cloud level default and needs to match the se group value in which it will be used if the se group use_standard_alb value is + - set. + - This is only used when fip is used for vs on azure cloud. + - Field introduced in 18.2.3. + type: bool + uuid: + description: + - Uuid of the vsvip object. + - Field introduced in 17.1.1. + vip: + description: + - List of virtual service ips and other shareable entities. + - Field introduced in 17.1.1. + vrf_context_ref: + description: + - Virtual routing context that the virtual service is bound to. + - This is used to provide the isolation of the set of networks the application is attached to. + - It is a reference to an object of type vrfcontext. + - Field introduced in 17.1.1. + vsvip_cloud_config_cksum: + description: + - Checksum of cloud configuration for vsvip. + - Internally set by cloud connector. + - Field introduced in 17.2.9, 18.1.2. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VsVip object + community.network.avi_vsvip: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vsvip +""" + +RETURN = ''' +obj: + description: VsVip (api/vsvip) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + dns_info=dict(type='list',), + east_west_placement=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_standard_alb=dict(type='bool',), + uuid=dict(type='str',), + vip=dict(type='list',), + vrf_context_ref=dict(type='str',), + vsvip_cloud_config_cksum=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vsvip', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_webhook.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_webhook.py new file mode 100644 index 00000000..2374076d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/avi_webhook.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_webhook +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Webhook Avi RESTful Object +description: + - This module is used to configure Webhook object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + callback_url: + description: + - Callback url for the webhook. + - Field introduced in 17.1.1. + description: + description: + - Field introduced in 17.1.1. + name: + description: + - The name of the webhook profile. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the webhook profile. + - Field introduced in 17.1.1. + verification_token: + description: + - Verification token sent back with the callback asquery parameters. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Webhook object + community.network.avi_webhook: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_webhook +""" + +RETURN = ''' +obj: + description: Webhook (api/webhook) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + callback_url=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + verification_token=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'webhook', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/bcf_switch.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bcf_switch.py new file mode 100644 index 00000000..ee15fa24 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bcf_switch.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: bcf_switch +author: "Ted (@tedelhourani)" +short_description: Create and remove a bcf switch. +description: + - Create and remove a Big Cloud Fabric switch. +options: + name: + description: + - The name of the switch. + required: true + fabric_role: + description: + - Fabric role of the switch. + choices: ['spine', 'leaf'] + required: true + leaf_group: + description: + - The leaf group of the switch if the switch is a leaf. + required: false + mac: + description: + - The MAC address of the switch. + required: true + state: + description: + - Whether the switch should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller IP address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Big Cloud Fabric access token. If this isn't set then the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. +''' + + +EXAMPLES = ''' +- name: Bcf leaf switch + community.network.bcf_switch: + name: Rack1Leaf1 + fabric_role: leaf + leaf_group: R1 + mac: 00:00:00:02:00:02 + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + + +RETURN = ''' # ''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def switch(module, check_mode): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + fabric_role = module.params['fabric_role'] + leaf_group = module.params['leaf_group'] + dpid = '00:00:' + module.params['mac'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/core') + + response = rest.get('switch-config', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing switch config: {0}".format(response.json['description'])) + + config_present = False + for switch in response.json: + if all((switch['name'] == name, + switch['fabric-role'] == fabric_role, + switch['dpid'] == dpid)): + config_present = switch.get('leaf-group', None) == leaf_group + if config_present: + break + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if check_mode: + module.exit_json(changed=True) + + if state in ('present'): + data = {'name': name, 'fabric-role': fabric_role, 'leaf-group': leaf_group, 'dpid': dpid} + response = rest.put('switch-config[name="%s"]' % name, data) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error configuring switch '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('switch-config[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting switch '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + fabric_role=dict(choices=['spine', 'leaf'], required=True), + leaf_group=dict(type='str', required=False), + mac=dict(type='str', required=True), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ), + supports_check_mode=True, + ) + + try: + switch(module, check_mode=module.check_mode) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_chain.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_chain.py new file mode 100644 index 00000000..a393f4e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_chain.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ansible module to manage Big Monitoring Fabric service chains + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: bigmon_chain +author: "Ted (@tedelhourani)" +short_description: Create and remove a bigmon inline service chain. +description: + - Create and remove a bigmon inline service chain. +options: + name: + description: + - The name of the chain. + required: true + state: + description: + - Whether the service chain should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller IP address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Bigmon access token. If this isn't set, the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. +''' + + +EXAMPLES = ''' +- name: Bigmon inline service chain + community.network.bigmon_chain: + name: MyChain + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + + +RETURN = ''' # ''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def chain(module): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/applications/bigchain') + + if None in (name, state, controller): + module.fail_json(msg='parameter `name` is missing') + + response = rest.get('chain?config=true', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing chain config: {0}".format(response.json['description'])) + + config_present = False + matching = [chain for chain in response.json if chain['name'] == name] + if matching: + config_present = True + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if state in ('present'): + response = rest.put('chain[name="%s"]' % name, data={'name': name}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error creating chain '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('chain[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting chain '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ) + ) + + try: + chain(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_policy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_policy.py new file mode 100644 index 00000000..f35d7780 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/bigmon_policy.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ansible module to manage Big Monitoring Fabric service chains + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: bigmon_policy +author: "Ted (@tedelhourani)" +short_description: Create and remove a bigmon out-of-band policy. +description: + - Create and remove a bigmon out-of-band policy. +options: + name: + description: + - The name of the policy. + required: true + policy_description: + description: + - Description of policy. + action: + description: + - Forward matching packets to delivery interfaces, Drop is for measure rate of matching packets, + but do not forward to delivery interfaces, capture packets and write to a PCAP file, or enable NetFlow generation. + default: forward + choices: ['forward', 'drop', 'flow-gen'] + priority: + description: + - A priority associated with this policy. The higher priority policy takes precedence over a lower priority. + default: 100 + duration: + description: + - Run policy for duration duration or until delivery_packet_count packets are delivered, whichever comes first. + default: 0 + start_time: + description: + - Date the policy becomes active + default: ansible_date_time.iso8601 + delivery_packet_count: + description: + - Run policy until delivery_packet_count packets are delivered. + default: 0 + state: + description: + - Whether the policy should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Bigmon access token. If this isn't set, the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. + +''' + +EXAMPLES = ''' +- name: Policy to aggregate filter and deliver data center (DC) 1 traffic + community.network.bigmon_policy: + name: policy1 + policy_description: DC 1 traffic policy + action: drop + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + +RETURN = ''' # ''' + +import datetime +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def policy(module): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + policy_description = module.params['policy_description'] + action = module.params['action'] + priority = module.params['priority'] + duration = module.params['duration'] + start_time = module.params['start_time'] + delivery_packet_count = module.params['delivery_packet_count'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/applications/bigtap') + + if name is None: + module.fail_json(msg='parameter `name` is missing') + + response = rest.get('policy?config=true', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing policy config: {0}".format(response.json['description'])) + + config_present = False + + matching = [policy for policy in response.json + if policy['name'] == name and + policy['duration'] == duration and + policy['delivery-packet-count'] == delivery_packet_count and + policy['policy-description'] == policy_description and + policy['action'] == action and + policy['priority'] == priority] + + if matching: + config_present = True + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if state in ('present'): + data = {'name': name, 'action': action, 'policy-description': policy_description, + 'priority': priority, 'duration': duration, 'start-time': start_time, + 'delivery-packet-count': delivery_packet_count} + + response = rest.put('policy[name="%s"]' % name, data=data) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error creating policy '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('policy[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting policy '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + policy_description=dict(type='str', default=''), + action=dict(choices=['forward', 'drop', 'capture', 'flow-gen'], default='forward'), + priority=dict(type='int', default=100), + duration=dict(type='int', default=0), + start_time=dict(type='str', default=datetime.datetime.now().isoformat() + '+00:00'), + delivery_packet_count=dict(type='int', default=0), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ) + ) + + try: + policy(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server.py new file mode 100644 index 00000000..31cbb2a4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server.py @@ -0,0 +1,2176 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: ce_aaa_server +short_description: Manages AAA server global configuration on HUAWEI CloudEngine switches. +description: + - Manages AAA server global configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + type: str + choices: [ absent, present ] + default: present + authen_scheme_name: + description: + - Name of an authentication scheme. + The value is a string of 1 to 32 characters. + type: str + first_authen_mode: + description: + - Preferred authentication mode. + type: str + choices: ['invalid', 'local', 'hwtacacs', 'radius', 'none'] + default: local + author_scheme_name: + description: + - Name of an authorization scheme. + The value is a string of 1 to 32 characters. + type: str + first_author_mode: + description: + - Preferred authorization mode. + type: str + choices: ['invalid', 'local', 'hwtacacs', 'if-authenticated', 'none'] + default: local + acct_scheme_name: + description: + - Accounting scheme name. + The value is a string of 1 to 32 characters. + type: str + accounting_mode: + description: + - Accounting Mode. + type: str + choices: ['invalid', 'hwtacacs', 'radius', 'none'] + default: none + domain_name: + description: + - Name of a domain. + The value is a string of 1 to 64 characters. + type: str + radius_server_group: + description: + - RADIUS server group's name. + The value is a string of 1 to 32 case-insensitive characters. + type: str + hwtacas_template: + description: + - Name of a HWTACACS template. + The value is a string of 1 to 32 case-insensitive characters. + type: str + local_user_group: + description: + - Name of the user group where the user belongs. The user inherits all the rights of the user group. + The value is a string of 1 to 32 characters. + type: str +''' + +EXAMPLES = r''' + +- name: AAA server test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Radius authentication Server Basic settings" + community.network.ce_aaa_server: + state: present + authen_scheme_name: test1 + first_authen_mode: radius + radius_server_group: test2 + provider: "{{ cli }}" + + - name: "Undo radius authentication Server Basic settings" + community.network.ce_aaa_server: + state: absent + authen_scheme_name: test1 + first_authen_mode: radius + radius_server_group: test2 + provider: "{{ cli }}" + + - name: "Hwtacacs accounting Server Basic settings" + community.network.ce_aaa_server: + state: present + acct_scheme_name: test1 + accounting_mode: hwtacacs + hwtacas_template: test2 + provider: "{{ cli }}" + + - name: "Undo hwtacacs accounting Server Basic settings" + community.network.ce_aaa_server: + state: absent + acct_scheme_name: test1 + accounting_mode: hwtacacs + hwtacas_template: test2 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"accounting_mode": "hwtacacs", "acct_scheme_name": "test1", + "hwtacas_template": "test2", "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"accounting scheme": [["hwtacacs"], ["default"]], + "hwtacacs template": ["huawei"]} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"accounting scheme": [["hwtacacs", "test1"]], + "hwtacacs template": ["huawei", "test2"]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["accounting-scheme test1", + "accounting-mode hwtacacs", + "hwtacacs server template test2", + "hwtacacs enable"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +SUCCESS = """success""" +FAILED = """failed""" + +INVALID_SCHEME_CHAR = [' ', '/', '\\', ':', '*', '?', '"', '|', '<', '>'] +INVALID_DOMAIN_CHAR = [' ', '*', '?', '"', '\''] +INVALID_GROUP_CHAR = ['/', '\\', ':', '*', '?', '"', '|', '<', '>'] + + +# get authentication scheme +CE_GET_AUTHENTICATION_SCHEME = """ + + + + + + + + + + + +""" + +# merge authentication scheme +CE_MERGE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# create authentication scheme +CE_CREATE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# delete authentication scheme +CE_DELETE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# get authorization scheme +CE_GET_AUTHORIZATION_SCHEME = """ + + + + + + + + + + + +""" + +# merge authorization scheme +CE_MERGE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# create authorization scheme +CE_CREATE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# delete authorization scheme +CE_DELETE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# get accounting scheme +CE_GET_ACCOUNTING_SCHEME = """ + + + + + + + + + + +""" + +# merge accounting scheme +CE_MERGE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# create accounting scheme +CE_CREATE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# delete accounting scheme +CE_DELETE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# get authentication domain +CE_GET_AUTHENTICATION_DOMAIN = """ + + + + + + + + + + +""" + +# merge authentication domain +CE_MERGE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create authentication domain +CE_CREATE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete authentication domain +CE_DELETE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get authorization domain +CE_GET_AUTHORIZATION_DOMAIN = """ + + + + + + + + + + +""" + +# merge authorization domain +CE_MERGE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create authorization domain +CE_CREATE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete authorization domain +CE_DELETE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get accounting domain +CE_GET_ACCOUNTING_DOMAIN = """ + + + + + + + + + + +""" + +# merge accounting domain +CE_MERGE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create accounting domain +CE_CREATE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete accounting domain +CE_DELETE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get radius template +CE_GET_RADIUS_TEMPLATE = """ + + + + + + + + + + + +""" + +# merge radius template +CE_MERGE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# create radius template +CE_CREATE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# delete radius template +CE_DELETE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# get hwtacacs template +CE_GET_HWTACACS_TEMPLATE = """ + + + + + + + + + + + +""" + +# merge hwtacacs template +CE_MERGE_HWTACACS_TEMPLATE = """ + + + + + %s + true + 5 + + + + +""" + +# create hwtacacs template +CE_CREATE_HWTACACS_TEMPLATE = """ + + + + + %s + true + 5 + + + + +""" + +# delete hwtacacs template +CE_DELETE_HWTACACS_TEMPLATE = """ + + + + + %s + + + + +""" + +# get radius client +CE_GET_RADIUS_CLIENT = """ + + + + + + + + + +""" + +# merge radius client +CE_MERGE_RADIUS_CLIENT = """ + + + + %s + + + +""" + +# get hwtacacs global config +CE_GET_HWTACACS_GLOBAL_CFG = """ + + + + + + + + + +""" + +# merge hwtacacs global config +CE_MERGE_HWTACACS_GLOBAL_CFG = """ + + + + %s + + + +""" + +# get local user group +CE_GET_LOCAL_USER_GROUP = """ + + + + + + + + + +""" +# merge local user group +CE_MERGE_LOCAL_USER_GROUP = """ + + + + + %s + + + + +""" +# delete local user group +CE_DELETE_LOCAL_USER_GROUP = """ + + + + + %s + + + + +""" + + +class AaaServer(object): + """ Manages aaa configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + recv_xml = set_nc_config(module, conf_str) + + return recv_xml + + def get_authentication_scheme(self, **kwargs): + """ Get scheme of authentication """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHENTICATION_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_authentication_domain(self, **kwargs): + """ Get domain of authentication """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHENTICATION_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_authentication_scheme(self, **kwargs): + """ Merge scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authentication scheme failed.') + + cmds = [] + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode %s" % first_authen_mode + cmds.append(cmd) + + return cmds + + def merge_authentication_domain(self, **kwargs): + """ Merge domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authentication domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + + return cmds + + def create_authentication_scheme(self, **kwargs): + """ Create scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authentication scheme failed.') + + cmds = [] + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode %s" % first_authen_mode + cmds.append(cmd) + + return cmds + + def create_authentication_domain(self, **kwargs): + """ Create domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authentication domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + + return cmds + + def delete_authentication_scheme(self, **kwargs): + """ Delete scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + + if authen_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authentication scheme failed.') + + cmds = [] + cmd = "undo authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode none" + cmds.append(cmd) + + return cmds + + def delete_authentication_domain(self, **kwargs): + """ Delete domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authentication domain failed.') + + cmds = [] + cmd = "undo authentication-scheme" + cmds.append(cmd) + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + + return cmds + + def get_authorization_scheme(self, **kwargs): + """ Get scheme of authorization """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHORIZATION_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_authorization_domain(self, **kwargs): + """ Get domain of authorization """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHORIZATION_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_authorization_scheme(self, **kwargs): + """ Merge scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authorization scheme failed.') + + cmds = [] + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode %s" % first_author_mode + cmds.append(cmd) + + return cmds + + def merge_authorization_domain(self, **kwargs): + """ Merge domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authorization domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + + return cmds + + def create_authorization_scheme(self, **kwargs): + """ Create scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authorization scheme failed.') + + cmds = [] + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode %s" % first_author_mode + cmds.append(cmd) + + return cmds + + def create_authorization_domain(self, **kwargs): + """ Create domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authorization domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + + return cmds + + def delete_authorization_scheme(self, **kwargs): + """ Delete scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + + if author_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authorization scheme failed.') + + cmds = [] + cmd = "undo authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode none" + cmds.append(cmd) + + return cmds + + def delete_authorization_domain(self, **kwargs): + """ Delete domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authorization domain failed.') + + cmds = [] + cmd = "undo authorization-scheme" + cmds.append(cmd) + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + + return cmds + + def get_accounting_scheme(self, **kwargs): + """ Get scheme of accounting """ + + module = kwargs["module"] + conf_str = CE_GET_ACCOUNTING_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall(r'.*(.*)\s*(.*)', xml_str) + if re_find: + return re_find + else: + return result + + def get_accounting_domain(self, **kwargs): + """ Get domain of accounting """ + + module = kwargs["module"] + conf_str = CE_GET_ACCOUNTING_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_accounting_scheme(self, **kwargs): + """ Merge scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge accounting scheme failed.') + + cmds = [] + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode %s" % accounting_mode + cmds.append(cmd) + + return cmds + + def merge_accounting_domain(self, **kwargs): + """ Merge domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_ACCOUNTING_DOMAIN % (domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge accounting domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + + return cmds + + def create_accounting_scheme(self, **kwargs): + """ Create scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create accounting scheme failed.') + + cmds = [] + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode %s" % accounting_mode + cmds.append(cmd) + + return cmds + + def create_accounting_domain(self, **kwargs): + """ Create domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_ACCOUNTING_DOMAIN % ( + domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create accounting domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + + return cmds + + def delete_accounting_scheme(self, **kwargs): + """ Delete scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + + if acct_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete accounting scheme failed.') + + cmds = [] + cmd = "undo accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode none" + cmds.append(cmd) + + return cmds + + def delete_accounting_domain(self, **kwargs): + """ Delete domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_ACCOUNTING_DOMAIN % ( + domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete accounting domain failed.') + + cmds = [] + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + cmd = "undo accounting-scheme" + cmds.append(cmd) + + return cmds + + def get_radius_template(self, **kwargs): + """ Get radius template """ + + module = kwargs["module"] + conf_str = CE_GET_RADIUS_TEMPLATE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_radius_template(self, **kwargs): + """ Merge radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_MERGE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge radius template failed.') + + cmds = [] + cmd = "radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def create_radius_template(self, **kwargs): + """ Create radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_CREATE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create radius template failed.') + + cmds = [] + cmd = "radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def delete_radius_template(self, **kwargs): + """ Delete radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_DELETE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete radius template failed.') + + cmds = [] + cmd = "undo radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def get_radius_client(self, **kwargs): + """ Get radius client """ + + module = kwargs["module"] + conf_str = CE_GET_RADIUS_CLIENT + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_radius_client(self, **kwargs): + """ Merge radius client """ + + enable = kwargs["isEnable"] + module = kwargs["module"] + conf_str = CE_MERGE_RADIUS_CLIENT % enable + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge radius client failed.') + + cmds = [] + if enable == "true": + cmd = "radius enable" + else: + cmd = "undo radius enable" + cmds.append(cmd) + + return cmds + + def get_hwtacacs_template(self, **kwargs): + """ Get hwtacacs template """ + + module = kwargs["module"] + conf_str = CE_GET_HWTACACS_TEMPLATE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_hwtacacs_template(self, **kwargs): + """ Merge hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_MERGE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge hwtacacs template failed.') + + cmds = [] + cmd = "hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def create_hwtacacs_template(self, **kwargs): + """ Create hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_CREATE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create hwtacacs template failed.') + + cmds = [] + cmd = "hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def delete_hwtacacs_template(self, **kwargs): + """ Delete hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_DELETE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete hwtacacs template failed.') + + cmds = [] + cmd = "undo hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def get_hwtacacs_global_cfg(self, **kwargs): + """ Get hwtacacs global configure """ + + module = kwargs["module"] + conf_str = CE_GET_HWTACACS_GLOBAL_CFG + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_hwtacacs_global_cfg(self, **kwargs): + """ Merge hwtacacs global configure """ + + enable = kwargs["isEnable"] + module = kwargs["module"] + conf_str = CE_MERGE_HWTACACS_GLOBAL_CFG % enable + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge hwtacacs global config failed.') + + cmds = [] + + if enable == "true": + cmd = "hwtacacs enable" + else: + cmd = "undo hwtacacs enable" + cmds.append(cmd) + + return cmds + + def get_local_user_group(self, **kwargs): + """ Get local user group """ + + module = kwargs["module"] + conf_str = CE_GET_LOCAL_USER_GROUP + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_local_user_group(self, **kwargs): + """ Merge local user group """ + + local_user_group = kwargs["local_user_group"] + module = kwargs["module"] + conf_str = CE_MERGE_LOCAL_USER_GROUP % local_user_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge local user group failed.') + + cmds = [] + cmd = "user-group %s" % local_user_group + cmds.append(cmd) + + return cmds + + def delete_local_user_group(self, **kwargs): + """ Delete local user group """ + + local_user_group = kwargs["local_user_group"] + module = kwargs["module"] + conf_str = CE_DELETE_LOCAL_USER_GROUP % local_user_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete local user group failed.') + + cmds = [] + cmd = "undo user-group %s" % local_user_group + cmds.append(cmd) + + return cmds + + +def check_name(**kwargs): + """ Check invalid name """ + + module = kwargs["module"] + name = kwargs["name"] + invalid_char = kwargs["invalid_char"] + + for item in invalid_char: + if item in name: + module.fail_json( + msg='Error: invalid char %s is in the name %s.' % (item, name)) + + +def check_module_argument(**kwargs): + """ Check module argument """ + + module = kwargs["module"] + + authen_scheme_name = module.params['authen_scheme_name'] + author_scheme_name = module.params['author_scheme_name'] + acct_scheme_name = module.params['acct_scheme_name'] + domain_name = module.params['domain_name'] + radius_server_group = module.params['radius_server_group'] + hwtacas_template = module.params['hwtacas_template'] + local_user_group = module.params['local_user_group'] + + if authen_scheme_name: + if len(authen_scheme_name) > 32: + module.fail_json( + msg='Error: authen_scheme_name %s ' + 'is large than 32.' % authen_scheme_name) + check_name(module=module, name=authen_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if author_scheme_name: + if len(author_scheme_name) > 32: + module.fail_json( + msg='Error: author_scheme_name %s ' + 'is large than 32.' % author_scheme_name) + check_name(module=module, name=author_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if acct_scheme_name: + if len(acct_scheme_name) > 32: + module.fail_json( + msg='Error: acct_scheme_name %s ' + 'is large than 32.' % acct_scheme_name) + check_name(module=module, name=acct_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if domain_name: + if len(domain_name) > 64: + module.fail_json( + msg='Error: domain_name %s ' + 'is large than 64.' % domain_name) + check_name(module=module, name=domain_name, + invalid_char=INVALID_DOMAIN_CHAR) + if domain_name == "-" or domain_name == "--": + module.fail_json(msg='domain_name %s ' + 'is invalid.' % domain_name) + + if radius_server_group and len(radius_server_group) > 32: + module.fail_json(msg='Error: radius_server_group %s ' + 'is large than 32.' % radius_server_group) + + if hwtacas_template and len(hwtacas_template) > 32: + module.fail_json( + msg='Error: hwtacas_template %s ' + 'is large than 32.' % hwtacas_template) + + if local_user_group: + if len(local_user_group) > 32: + module.fail_json( + msg='Error: local_user_group %s ' + 'is large than 32.' % local_user_group) + check_name(module=module, name=local_user_group, invalid_char=INVALID_GROUP_CHAR) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + authen_scheme_name=dict(type='str'), + first_authen_mode=dict(default='local', choices=['invalid', 'local', 'hwtacacs', 'radius', 'none']), + author_scheme_name=dict(type='str'), + first_author_mode=dict(default='local', choices=['invalid', 'local', 'hwtacacs', 'if-authenticated', 'none']), + acct_scheme_name=dict(type='str'), + accounting_mode=dict(default='none', choices=['invalid', 'hwtacacs', 'radius', 'none']), + domain_name=dict(type='str'), + radius_server_group=dict(type='str'), + hwtacas_template=dict(type='str'), + local_user_group=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + check_module_argument(module=module) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + authen_scheme_name = module.params['authen_scheme_name'] + first_authen_mode = module.params['first_authen_mode'] + author_scheme_name = module.params['author_scheme_name'] + first_author_mode = module.params['first_author_mode'] + acct_scheme_name = module.params['acct_scheme_name'] + accounting_mode = module.params['accounting_mode'] + domain_name = module.params['domain_name'] + radius_server_group = module.params['radius_server_group'] + hwtacas_template = module.params['hwtacas_template'] + local_user_group = module.params['local_user_group'] + + ce_aaa_server = AaaServer() + + if not ce_aaa_server: + module.fail_json(msg='Error: init module failed.') + + # get proposed + proposed["state"] = state + if authen_scheme_name: + proposed["authen_scheme_name"] = authen_scheme_name + if first_authen_mode: + proposed["first_authen_mode"] = first_authen_mode + if author_scheme_name: + proposed["author_scheme_name"] = author_scheme_name + if first_author_mode: + proposed["first_author_mode"] = first_author_mode + if acct_scheme_name: + proposed["acct_scheme_name"] = acct_scheme_name + if accounting_mode: + proposed["accounting_mode"] = accounting_mode + if domain_name: + proposed["domain_name"] = domain_name + if radius_server_group: + proposed["radius_server_group"] = radius_server_group + if hwtacas_template: + proposed["hwtacas_template"] = hwtacas_template + if local_user_group: + proposed["local_user_group"] = local_user_group + + # authentication + if authen_scheme_name: + + scheme_exist = ce_aaa_server.get_authentication_scheme(module=module) + scheme_new = (authen_scheme_name.lower(), first_authen_mode.lower(), "invalid") + + existing["authentication scheme"] = scheme_exist + + if state == "present": + # present authentication scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + + updates.append(cmd) + changed = True + + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + updates.append(cmd) + changed = True + + # present authentication domain + if domain_name: + domain_exist = ce_aaa_server.get_authentication_domain( + module=module) + domain_new = (domain_name.lower(), authen_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent authentication scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + updates.append(cmd) + changed = True + + # absent authentication domain + else: + domain_exist = ce_aaa_server.get_authentication_domain( + module=module) + domain_new = (domain_name.lower(), authen_scheme_name.lower()) + + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_authentication_scheme(module=module) + end_state["authentication scheme"] = scheme_end + + # authorization + if author_scheme_name: + + scheme_exist = ce_aaa_server.get_authorization_scheme(module=module) + scheme_new = (author_scheme_name.lower(), first_author_mode.lower(), "invalid") + + existing["authorization scheme"] = scheme_exist + + if state == "present": + # present authorization scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + + # present authorization domain + if domain_name: + domain_exist = ce_aaa_server.get_authorization_domain( + module=module) + domain_new = (domain_name.lower(), author_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent authorization scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + + # absent authorization domain + else: + domain_exist = ce_aaa_server.get_authorization_domain( + module=module) + domain_new = (domain_name.lower(), author_scheme_name.lower()) + + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_authorization_scheme(module=module) + end_state["authorization scheme"] = scheme_end + + # accounting + if acct_scheme_name: + + scheme_exist = ce_aaa_server.get_accounting_scheme(module=module) + scheme_new = (acct_scheme_name.lower(), accounting_mode.lower()) + + existing["accounting scheme"] = scheme_exist + + if state == "present": + # present accounting scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + + # present accounting domain + if domain_name: + domain_exist = ce_aaa_server.get_accounting_domain( + module=module) + domain_new = (domain_name.lower(), acct_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent accounting scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + + # absent accounting domain + else: + domain_exist = ce_aaa_server.get_accounting_domain( + module=module) + domain_new = (domain_name.lower(), acct_scheme_name.lower()) + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_accounting_scheme(module=module) + end_state["accounting scheme"] = scheme_end + + # radius group name + if (authen_scheme_name and first_authen_mode.lower() == "radius") \ + or (acct_scheme_name and accounting_mode.lower() == "radius"): + + if not radius_server_group: + module.fail_json(msg='please input radius_server_group when use radius.') + + rds_template_exist = ce_aaa_server.get_radius_template(module=module) + rds_template_new = (radius_server_group) + + rds_enable_exist = ce_aaa_server.get_radius_client(module=module) + + existing["radius template"] = rds_template_exist + existing["radius enable"] = rds_enable_exist + + if state == "present": + # present radius group name + if len(rds_template_exist) == 0: + cmd = ce_aaa_server.create_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + elif rds_template_new not in rds_template_exist: + cmd = ce_aaa_server.merge_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + + rds_enable_new = ("true") + if rds_enable_new not in rds_enable_exist: + cmd = ce_aaa_server.merge_radius_client( + module=module, isEnable="true") + updates.append(cmd) + changed = True + + else: + # absent radius group name + if len(rds_template_exist) == 0: + pass + elif rds_template_new not in rds_template_exist: + pass + else: + cmd = ce_aaa_server.delete_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + + rds_enable_new = ("false") + if rds_enable_new not in rds_enable_exist: + cmd = ce_aaa_server.merge_radius_client( + module=module, isEnable="false") + updates.append(cmd) + changed = True + else: + pass + + rds_template_end = ce_aaa_server.get_radius_template(module=module) + end_state["radius template"] = rds_template_end + + rds_enable_end = ce_aaa_server.get_radius_client(module=module) + end_state["radius enable"] = rds_enable_end + + tmp_scheme = author_scheme_name + + # hwtacas template + if (authen_scheme_name and first_authen_mode.lower() == "hwtacacs") \ + or (tmp_scheme and first_author_mode.lower() == "hwtacacs") \ + or (acct_scheme_name and accounting_mode.lower() == "hwtacacs"): + + if not hwtacas_template: + module.fail_json( + msg='please input hwtacas_template when use hwtacas.') + + hwtacacs_exist = ce_aaa_server.get_hwtacacs_template(module=module) + hwtacacs_new = (hwtacas_template) + + hwtacacs_enbale_exist = ce_aaa_server.get_hwtacacs_global_cfg( + module=module) + + existing["hwtacacs template"] = hwtacacs_exist + existing["hwtacacs enable"] = hwtacacs_enbale_exist + + if state == "present": + # present hwtacas template + if len(hwtacacs_exist) == 0: + cmd = ce_aaa_server.create_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + elif hwtacacs_new not in hwtacacs_exist: + cmd = ce_aaa_server.merge_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + + hwtacacs_enbale_new = ("true") + if hwtacacs_enbale_new not in hwtacacs_enbale_exist: + cmd = ce_aaa_server.merge_hwtacacs_global_cfg( + module=module, isEnable="true") + updates.append(cmd) + changed = True + + else: + # absent hwtacas template + if len(hwtacacs_exist) == 0: + pass + elif hwtacacs_new not in hwtacacs_exist: + pass + else: + cmd = ce_aaa_server.delete_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + + hwtacacs_enbale_new = ("false") + if hwtacacs_enbale_new not in hwtacacs_enbale_exist: + cmd = ce_aaa_server.merge_hwtacacs_global_cfg( + module=module, isEnable="false") + updates.append(cmd) + changed = True + else: + pass + + hwtacacs_end = ce_aaa_server.get_hwtacacs_template(module=module) + end_state["hwtacacs template"] = hwtacacs_end + + hwtacacs_enable_end = ce_aaa_server.get_hwtacacs_global_cfg( + module=module) + end_state["hwtacacs enable"] = hwtacacs_enable_end + + # local user group + if local_user_group: + + user_group_exist = ce_aaa_server.get_local_user_group(module=module) + user_group_new = (local_user_group) + + existing["local user group"] = user_group_exist + + if state == "present": + # present local user group + if len(user_group_exist) == 0: + cmd = ce_aaa_server.merge_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + elif user_group_new not in user_group_exist: + cmd = ce_aaa_server.merge_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + + else: + # absent local user group + if len(user_group_exist) == 0: + pass + elif user_group_new not in user_group_exist: + pass + else: + cmd = ce_aaa_server.delete_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + + user_group_end = ce_aaa_server.get_local_user_group(module=module) + end_state["local user group"] = user_group_end + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server_host.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server_host.py new file mode 100644 index 00000000..00fc4cdd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_aaa_server_host.py @@ -0,0 +1,2636 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_aaa_server_host +short_description: Manages AAA server host configuration on HUAWEI CloudEngine switches. +description: + - Manages AAA server host configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + local_user_name: + description: + - Name of a local user. + The value is a string of 1 to 253 characters. + local_password: + description: + - Login password of a user. The password can contain letters, numbers, and special characters. + The value is a string of 1 to 255 characters. + local_service_type: + description: + - The type of local user login through, such as ftp ssh snmp telnet. + local_ftp_dir: + description: + - FTP user directory. + The value is a string of 1 to 255 characters. + local_user_level: + description: + - Login level of a local user. + The value is an integer ranging from 0 to 15. + local_user_group: + description: + - Name of the user group where the user belongs. The user inherits all the rights of the user group. + The value is a string of 1 to 32 characters. + radius_group_name: + description: + - RADIUS server group's name. + The value is a string of 1 to 32 case-insensitive characters. + radius_server_type: + description: + - Type of Radius Server. + choices: ['Authentication', 'Accounting'] + radius_server_ip: + description: + - IPv4 address of configured server. + The value is a string of 0 to 255 characters, in dotted decimal notation. + radius_server_ipv6: + description: + - IPv6 address of configured server. + The total length is 128 bits. + radius_server_port: + description: + - Configured server port for a particular server. + The value is an integer ranging from 1 to 65535. + radius_server_mode: + description: + - Configured primary or secondary server for a particular server. + choices: ['Secondary-server', 'Primary-server'] + radius_vpn_name: + description: + - Set VPN instance. + The value is a string of 1 to 31 case-sensitive characters. + radius_server_name: + description: + - Hostname of configured server. + The value is a string of 0 to 255 case-sensitive characters. + hwtacacs_template: + description: + - Name of a HWTACACS template. + The value is a string of 1 to 32 case-insensitive characters. + hwtacacs_server_ip: + description: + - Server IPv4 address. Must be a valid unicast IP address. + The value is a string of 0 to 255 characters, in dotted decimal notation. + hwtacacs_server_ipv6: + description: + - Server IPv6 address. Must be a valid unicast IP address. + The total length is 128 bits. + hwtacacs_server_type: + description: + - Hwtacacs server type. + choices: ['Authentication', 'Authorization', 'Accounting', 'Common'] + hwtacacs_is_secondary_server: + description: + - Whether the server is secondary. + type: bool + default: 'no' + hwtacacs_vpn_name: + description: + - VPN instance name. + hwtacacs_is_public_net: + description: + - Set the public-net. + type: bool + default: 'no' + hwtacacs_server_host_name: + description: + - Hwtacacs server host name. +''' + +EXAMPLES = ''' + +- name: AAA server host test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config local user when use local scheme" + community.network.ce_aaa_server_host: + state: present + local_user_name: user1 + local_password: 123456 + provider: "{{ cli }}" + + - name: "Undo local user when use local scheme" + community.network.ce_aaa_server_host: + state: absent + local_user_name: user1 + local_password: 123456 + provider: "{{ cli }}" + + - name: "Config radius server ip" + community.network.ce_aaa_server_host: + state: present + radius_group_name: group1 + radius_server_type: Authentication + radius_server_ip: 10.1.10.1 + radius_server_port: 2000 + radius_server_mode: Primary-server + radius_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Undo radius server ip" + community.network.ce_aaa_server_host: + state: absent + radius_group_name: group1 + radius_server_type: Authentication + radius_server_ip: 10.1.10.1 + radius_server_port: 2000 + radius_server_mode: Primary-server + radius_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Config hwtacacs server ip" + community.network.ce_aaa_server_host: + state: present + hwtacacs_template: template + hwtacacs_server_ip: 10.10.10.10 + hwtacacs_server_type: Authorization + hwtacacs_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Undo hwtacacs server ip" + community.network.ce_aaa_server_host: + state: absent + hwtacacs_template: template + hwtacacs_server_ip: 10.10.10.10 + hwtacacs_server_type: Authorization + hwtacacs_vpn_name: _public_ + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"hwtacacs_is_public_net": "false", + "hwtacacs_is_secondary_server": "false", + "hwtacacs_server_ip": "10.135.182.157", + "hwtacacs_server_type": "Authorization", + "hwtacacs_template": "wdz", + "hwtacacs_vpn_name": "_public_", + "local_password": "******", + "state": "present"} +existing: + description: k/v pairs of existing aaa server host + returned: always + type: dict + sample: {"radius server ipv4": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"radius server ipv4": [ + [ + "10.1.10.1", + "Authentication", + "2000", + "Primary-server", + "_public_" + ] + ]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["hwtacacs server template test", + "hwtacacs server authorization 10.135.182.157 vpn-instance test_vpn public-net"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +SUCCESS = """success""" +FAILED = """failed""" + +INVALID_USER_NAME_CHAR = [' ', '/', '\\', + ':', '*', '?', '"', '\'', '<', '>', '%'] + +# get local user name +CE_GET_LOCAL_USER_INFO_HEADER = """ + + + + + + + +""" +CE_GET_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# merge local user name +CE_MERGE_LOCAL_USER_INFO_HEADER = """ + + + + + + %s +""" +CE_MERGE_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# delete local user name +CE_DELETE_LOCAL_USER_INFO_HEADER = """ + + + + + + %s +""" +CE_DELETE_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# get radius server config ipv4 +CE_GET_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge radius server config ipv4 +CE_MERGE_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete radius server config ipv4 +CE_DELETE_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get radius server config ipv6 +CE_GET_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + + + + + + + + + + +""" + +# merge radius server config ipv6 +CE_MERGE_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# delete radius server config ipv6 +CE_DELETE_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# get radius server name +CE_GET_RADIUS_SERVER_NAME = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge radius server name +CE_MERGE_RADIUS_SERVER_NAME = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete radius server name +CE_DELETE_RADIUS_SERVER_NAME = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs server config ipv4 +CE_GET_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge hwtacacs server config ipv4 +CE_MERGE_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs server config ipv4 +CE_DELETE_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs server config ipv6 +CE_GET_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + + + + + + + + + + +""" + +# merge hwtacacs server config ipv6 +CE_MERGE_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs server config ipv6 +CE_DELETE_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs host server config +CE_GET_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge hwtacacs host server config +CE_MERGE_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs host server config +CE_DELETE_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + + +class AaaServerHost(object): + """ Manages aaa server host configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + recv_xml = set_nc_config(module, conf_str) + + return recv_xml + + def get_local_user_info(self, **kwargs): + """ Get local user information """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + state = module.params['state'] + + result = dict() + result["local_user_info"] = [] + need_cfg = False + + conf_str = CE_GET_LOCAL_USER_INFO_HEADER + + if local_service_type: + if local_service_type == "none": + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + elif local_service_type == "dot1x": + conf_str += "" + else: + option = local_service_type.split(" ") + for tmp in option: + if tmp == "dot1x": + module.fail_json( + msg='Error: Do not input dot1x with other service type.') + elif tmp == "none": + module.fail_json( + msg='Error: Do not input none with other service type.') + elif tmp == "ftp": + conf_str += "" + elif tmp == "snmp": + conf_str += "" + elif tmp == "ssh": + conf_str += "" + elif tmp == "telnet": + conf_str += "" + elif tmp == "terminal": + conf_str += "" + else: + module.fail_json( + msg='Error: Do not support the type [%s].' % tmp) + + if local_ftp_dir: + conf_str += "" + + if local_user_level: + conf_str += "" + + if local_user_group: + conf_str += "" + + conf_str += CE_GET_LOCAL_USER_INFO_TAIL + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_user_info = root.findall("aaa/lam/users/user") + if local_user_info: + for tmp in local_user_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["userName", "password", "userLevel", "ftpDir", "userGroupName", + "serviceTerminal", "serviceTelnet", "serviceFtp", "serviceSsh", + "serviceSnmp", "serviceDot1x"]: + tmp_dict[site.tag] = site.text + + result["local_user_info"].append(tmp_dict) + + if state == "present": + need_cfg = True + else: + if result["local_user_info"]: + for tmp in result["local_user_info"]: + if "userName" in tmp.keys(): + if tmp["userName"] == local_user_name: + + if not local_service_type and not local_user_level \ + and not local_ftp_dir and not local_user_group: + + need_cfg = True + + if local_service_type: + if local_service_type == "none": + if tmp.get("serviceTerminal") == "true" or \ + tmp.get("serviceTelnet") == "true" or \ + tmp.get("serviceFtp") == "true" or \ + tmp.get("serviceSsh") == "true" or \ + tmp.get("serviceSnmp") == "true" or \ + tmp.get("serviceDot1x") == "true": + need_cfg = True + elif local_service_type == "dot1x": + if tmp.get("serviceDot1x") == "true": + need_cfg = True + elif tmp == "ftp": + if tmp.get("serviceFtp") == "true": + need_cfg = True + elif tmp == "snmp": + if tmp.get("serviceSnmp") == "true": + need_cfg = True + elif tmp == "ssh": + if tmp.get("serviceSsh") == "true": + need_cfg = True + elif tmp == "telnet": + if tmp.get("serviceTelnet") == "true": + need_cfg = True + elif tmp == "terminal": + if tmp.get("serviceTerminal") == "true": + need_cfg = True + + if local_user_level: + if tmp.get("userLevel") == local_user_level: + need_cfg = True + + if local_ftp_dir: + if tmp.get("ftpDir") == local_ftp_dir: + need_cfg = True + + if local_user_group: + if tmp.get("userGroupName") == local_user_group: + need_cfg = True + + break + + result["need_cfg"] = need_cfg + return result + + def merge_local_user_info(self, **kwargs): + """ Merge local user information by netconf """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + state = module.params['state'] + + cmds = [] + + conf_str = CE_MERGE_LOCAL_USER_INFO_HEADER % local_user_name + + if local_password: + conf_str += "%s" % local_password + + if state == "present": + cmd = "local-user %s password cipher %s" % ( + local_user_name, local_password) + cmds.append(cmd) + + if local_service_type: + if local_service_type == "none": + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + + cmd = "local-user %s service-type none" % local_user_name + cmds.append(cmd) + + elif local_service_type == "dot1x": + if state == "present": + conf_str += "true" + cmd = "local-user %s service-type dot1x" % local_user_name + else: + conf_str += "false" + cmd = "undo local-user %s service-type" % local_user_name + + cmds.append(cmd) + + else: + option = local_service_type.split(" ") + for tmp in option: + if tmp == "dot1x": + module.fail_json( + msg='Error: Do not input dot1x with other service type.') + if tmp == "none": + module.fail_json( + msg='Error: Do not input none with other service type.') + + if state == "present": + if tmp == "ftp": + conf_str += "true" + cmd = "local-user %s service-type ftp" % local_user_name + elif tmp == "snmp": + conf_str += "true" + cmd = "local-user %s service-type snmp" % local_user_name + elif tmp == "ssh": + conf_str += "true" + cmd = "local-user %s service-type ssh" % local_user_name + elif tmp == "telnet": + conf_str += "true" + cmd = "local-user %s service-type telnet" % local_user_name + elif tmp == "terminal": + conf_str += "true" + cmd = "local-user %s service-type terminal" % local_user_name + + cmds.append(cmd) + + else: + if tmp == "ftp": + conf_str += "false" + elif tmp == "snmp": + conf_str += "false" + elif tmp == "ssh": + conf_str += "false" + elif tmp == "telnet": + conf_str += "false" + elif tmp == "terminal": + conf_str += "false" + + if state == "absent": + cmd = "undo local-user %s service-type" % local_user_name + cmds.append(cmd) + + if local_ftp_dir: + if state == "present": + conf_str += "%s" % local_ftp_dir + cmd = "local-user %s ftp-directory %s" % ( + local_user_name, local_ftp_dir) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s ftp-directory" % local_user_name + cmds.append(cmd) + + if local_user_level: + if state == "present": + conf_str += "%s" % local_user_level + cmd = "local-user %s level %s" % ( + local_user_name, local_user_level) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s level" % local_user_name + cmds.append(cmd) + + if local_user_group: + if state == "present": + conf_str += "%s" % local_user_group + cmd = "local-user %s user-group %s" % ( + local_user_name, local_user_group) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s user-group" % local_user_name + cmds.append(cmd) + + conf_str += CE_MERGE_LOCAL_USER_INFO_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge local user info failed.') + + return cmds + + def delete_local_user_info(self, **kwargs): + """ Delete local user information by netconf """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + conf_str = CE_DELETE_LOCAL_USER_INFO_HEADER % local_user_name + conf_str += CE_DELETE_LOCAL_USER_INFO_TAIL + + cmds = [] + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete local user info failed.') + + cmd = "undo local-user %s" % local_user_name + cmds.append(cmd) + + return cmds + + def get_radius_server_cfg_ipv4(self, **kwargs): + """ Get radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + state = module.params['state'] + + result = dict() + result["radius_server_ip_v4"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_CFG_IPV4 % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_ip_v4 = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerIPV4s/rdsServerIPV4") + if radius_server_ip_v4: + for tmp in radius_server_ip_v4: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverIPAddress", "serverPort", "serverMode", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["radius_server_ip_v4"].append(tmp_dict) + + if result["radius_server_ip_v4"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_ip: + cfg["serverIPAddress"] = radius_server_ip.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + if radius_vpn_name: + cfg["vpnName"] = radius_vpn_name.lower() + + for tmp in result["radius_server_ip_v4"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_ip: + exist_cfg["serverIPAddress"] = tmp.get("serverIPAddress").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + if radius_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_cfg_ipv4(self, **kwargs): + """ Merge radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_MERGE_RADIUS_SERVER_CFG_IPV4 % ( + radius_group_name, radius_server_type, + radius_server_ip, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge radius server config ipv4 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_cfg_ipv4(self, **kwargs): + """ Delete radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_DELETE_RADIUS_SERVER_CFG_IPV4 % ( + radius_group_name, radius_server_type, + radius_server_ip, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Create radius server config ipv4 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_radius_server_cfg_ipv6(self, **kwargs): + """ Get radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + state = module.params['state'] + + result = dict() + result["radius_server_ip_v6"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_CFG_IPV6 % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_ip_v6 = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerIPV6s/rdsServerIPV6") + if radius_server_ip_v6: + for tmp in radius_server_ip_v6: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverIPAddress", "serverPort", "serverMode"]: + tmp_dict[site.tag] = site.text + + result["radius_server_ip_v6"].append(tmp_dict) + + if result["radius_server_ip_v6"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_ipv6: + cfg["serverIPAddress"] = radius_server_ipv6.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + + for tmp in result["radius_server_ip_v6"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_ipv6: + exist_cfg["serverIPAddress"] = tmp.get("serverIPAddress").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_cfg_ipv6(self, **kwargs): + """ Merge radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + + conf_str = CE_MERGE_RADIUS_SERVER_CFG_IPV6 % ( + radius_group_name, radius_server_type, + radius_server_ipv6, radius_server_port, + radius_server_mode) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge radius server config ipv6 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_cfg_ipv6(self, **kwargs): + """ Delete radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + + conf_str = CE_DELETE_RADIUS_SERVER_CFG_IPV6 % ( + radius_group_name, radius_server_type, + radius_server_ipv6, radius_server_port, + radius_server_mode) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Create radius server config ipv6 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_radius_server_name(self, **kwargs): + """ Get radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + state = module.params['state'] + + result = dict() + result["radius_server_name_cfg"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_NAME % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_name_cfg = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerNames/rdsServerName") + if radius_server_name_cfg: + for tmp in radius_server_name_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverName", "serverPort", "serverMode", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["radius_server_name_cfg"].append(tmp_dict) + + if result["radius_server_name_cfg"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_name: + cfg["serverName"] = radius_server_name.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + if radius_vpn_name: + cfg["vpnName"] = radius_vpn_name.lower() + + for tmp in result["radius_server_name_cfg"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_name: + exist_cfg["serverName"] = tmp.get("serverName").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + if radius_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_name(self, **kwargs): + """ Merge radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_MERGE_RADIUS_SERVER_NAME % ( + radius_group_name, radius_server_type, + radius_server_name, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge radius server name failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_name(self, **kwargs): + """ Delete radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_DELETE_RADIUS_SERVER_NAME % ( + radius_group_name, radius_server_type, + radius_server_name, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: delete radius server name failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Get hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + state = module.params["state"] + + result = dict() + result["hwtacacs_server_cfg_ipv4"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_SERVER_CFG_IPV4 % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_cfg_ipv4 = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacSrvCfgs/hwTacSrvCfg") + if hwtacacs_server_cfg_ipv4: + for tmp in hwtacacs_server_cfg_ipv4: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverIpAddress", "serverType", "isSecondaryServer", "isPublicNet", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_cfg_ipv4"].append(tmp_dict) + + if result["hwtacacs_server_cfg_ipv4"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_ip: + cfg["serverIpAddress"] = hwtacacs_server_ip.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_is_public_net: + cfg["isPublicNet"] = str(hwtacacs_is_public_net).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_cfg_ipv4"]: + exist_cfg = dict() + if hwtacacs_server_ip: + exist_cfg["serverIpAddress"] = tmp.get("serverIpAddress").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_is_public_net: + exist_cfg["isPublicNet"] = tmp.get("isPublicNet").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Merge hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_MERGE_HWTACACS_SERVER_CFG_IPV4 % ( + hwtacacs_template, hwtacacs_server_ip, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs server config ipv4 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Delete hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_DELETE_HWTACACS_SERVER_CFG_IPV4 % ( + hwtacacs_template, hwtacacs_server_ip, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs server config ipv4 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Get hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + state = module.params["state"] + + result = dict() + result["hwtacacs_server_cfg_ipv6"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_SERVER_CFG_IPV6 % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_cfg_ipv6 = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacIpv6SrvCfgs/hwTacIpv6SrvCfg") + if hwtacacs_server_cfg_ipv6: + for tmp in hwtacacs_server_cfg_ipv6: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverIpAddress", "serverType", "isSecondaryServer", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_cfg_ipv6"].append(tmp_dict) + + if result["hwtacacs_server_cfg_ipv6"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_ipv6: + cfg["serverIpAddress"] = hwtacacs_server_ipv6.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_cfg_ipv6"]: + exist_cfg = dict() + if hwtacacs_server_ipv6: + exist_cfg["serverIpAddress"] = tmp.get("serverIpAddress").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Merge hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + + conf_str = CE_MERGE_HWTACACS_SERVER_CFG_IPV6 % ( + hwtacacs_template, hwtacacs_server_ipv6, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs server config ipv6 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Delete hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + + conf_str = CE_DELETE_HWTACACS_SERVER_CFG_IPV6 % ( + hwtacacs_template, hwtacacs_server_ipv6, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs server config ipv6 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_host_server_cfg(self, **kwargs): + """ Get hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = "true" if module.params[ + "hwtacacs_is_secondary_server"] is True else "false" + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = "true" if module.params[ + "hwtacacs_is_public_net"] is True else "false" + state = module.params["state"] + + result = dict() + result["hwtacacs_server_name_cfg"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_HOST_SERVER_CFG % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_name_cfg = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacHostSrvCfgs/hwTacHostSrvCfg") + if hwtacacs_server_name_cfg: + for tmp in hwtacacs_server_name_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverHostName", "serverType", "isSecondaryServer", "isPublicNet", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_name_cfg"].append(tmp_dict) + + if result["hwtacacs_server_name_cfg"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_host_name: + cfg["serverHostName"] = hwtacacs_server_host_name.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_is_public_net: + cfg["isPublicNet"] = str(hwtacacs_is_public_net).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_name_cfg"]: + exist_cfg = dict() + if hwtacacs_server_host_name: + exist_cfg["serverHostName"] = tmp.get("serverHostName").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_is_public_net: + exist_cfg["isPublicNet"] = tmp.get("isPublicNet").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_host_server_cfg(self, **kwargs): + """ Merge hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_MERGE_HWTACACS_HOST_SERVER_CFG % ( + hwtacacs_template, hwtacacs_server_host_name, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs host server config failed.') + + cmds = [] + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server host host-name %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_host_server_cfg(self, **kwargs): + """ Delete hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_DELETE_HWTACACS_HOST_SERVER_CFG % ( + hwtacacs_template, hwtacacs_server_host_name, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs host server config failed.') + + cmds = [] + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + +def check_name(**kwargs): + """ Check invalid name """ + + module = kwargs["module"] + name = kwargs["name"] + invalid_char = kwargs["invalid_char"] + + for item in invalid_char: + if item in name: + module.fail_json( + msg='Error: Invalid char %s is in the name %s ' % (item, name)) + + +def check_module_argument(**kwargs): + """ Check module argument """ + + module = kwargs["module"] + + # local para + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + + # radius para + radius_group_name = module.params['radius_group_name'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_vpn_name = module.params['radius_vpn_name'] + radius_server_name = module.params['radius_server_name'] + + # hwtacacs para + hwtacacs_template = module.params['hwtacacs_template'] + hwtacacs_server_ip = module.params['hwtacacs_server_ip'] + hwtacacs_vpn_name = module.params['hwtacacs_vpn_name'] + hwtacacs_server_host_name = module.params['hwtacacs_server_host_name'] + + if local_user_name: + if len(local_user_name) > 253: + module.fail_json( + msg='Error: The local_user_name %s is large than 253.' % local_user_name) + check_name(module=module, name=local_user_name, + invalid_char=INVALID_USER_NAME_CHAR) + + if local_password and len(local_password) > 255: + module.fail_json( + msg='Error: The local_password %s is large than 255.' % local_password) + + if local_user_level: + if int(local_user_level) > 15 or int(local_user_level) < 0: + module.fail_json( + msg='Error: The local_user_level %s is out of [0 - 15].' % local_user_level) + + if local_ftp_dir: + if len(local_ftp_dir) > 255: + module.fail_json( + msg='Error: The local_ftp_dir %s is large than 255.' % local_ftp_dir) + + if local_user_group: + if len(local_user_group) > 32 or len(local_user_group) < 1: + module.fail_json( + msg='Error: The local_user_group %s is out of [1 - 32].' % local_user_group) + + if radius_group_name and len(radius_group_name) > 32: + module.fail_json( + msg='Error: The radius_group_name %s is large than 32.' % radius_group_name) + + if radius_server_ip and not check_ip_addr(radius_server_ip): + module.fail_json( + msg='Error: The radius_server_ip %s is invalid.' % radius_server_ip) + + if radius_server_port and not radius_server_port.isdigit(): + module.fail_json( + msg='Error: The radius_server_port %s is invalid.' % radius_server_port) + + if radius_vpn_name: + if len(radius_vpn_name) > 31: + module.fail_json( + msg='Error: The radius_vpn_name %s is large than 31.' % radius_vpn_name) + if ' ' in radius_vpn_name: + module.fail_json( + msg='Error: The radius_vpn_name %s include space.' % radius_vpn_name) + + if radius_server_name: + if len(radius_server_name) > 255: + module.fail_json( + msg='Error: The radius_server_name %s is large than 255.' % radius_server_name) + if ' ' in radius_server_name: + module.fail_json( + msg='Error: The radius_server_name %s include space.' % radius_server_name) + + if hwtacacs_template and len(hwtacacs_template) > 32: + module.fail_json( + msg='Error: The hwtacacs_template %s is large than 32.' % hwtacacs_template) + + if hwtacacs_server_ip and not check_ip_addr(hwtacacs_server_ip): + module.fail_json( + msg='Error: The hwtacacs_server_ip %s is invalid.' % hwtacacs_server_ip) + + if hwtacacs_vpn_name: + if len(hwtacacs_vpn_name) > 31: + module.fail_json( + msg='Error: The hwtacacs_vpn_name %s is large than 31.' % hwtacacs_vpn_name) + if ' ' in hwtacacs_vpn_name: + module.fail_json( + msg='Error: The hwtacacs_vpn_name %s include space.' % hwtacacs_vpn_name) + + if hwtacacs_server_host_name: + if len(hwtacacs_server_host_name) > 255: + module.fail_json( + msg='Error: The hwtacacs_server_host_name %s is large than 255.' % hwtacacs_server_host_name) + if ' ' in hwtacacs_server_host_name: + module.fail_json( + msg='Error: The hwtacacs_server_host_name %s include space.' % hwtacacs_server_host_name) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + local_user_name=dict(type='str'), + local_password=dict(type='str', no_log=True), + local_service_type=dict(type='str'), + local_ftp_dir=dict(type='str'), + local_user_level=dict(type='str'), + local_user_group=dict(type='str'), + radius_group_name=dict(type='str'), + radius_server_type=dict(choices=['Authentication', 'Accounting']), + radius_server_ip=dict(type='str'), + radius_server_ipv6=dict(type='str'), + radius_server_port=dict(type='str'), + radius_server_mode=dict( + choices=['Secondary-server', 'Primary-server']), + radius_vpn_name=dict(type='str'), + radius_server_name=dict(type='str'), + hwtacacs_template=dict(type='str'), + hwtacacs_server_ip=dict(type='str'), + hwtacacs_server_ipv6=dict(type='str'), + hwtacacs_server_type=dict( + choices=['Authentication', 'Authorization', 'Accounting', 'Common']), + hwtacacs_is_secondary_server=dict( + required=False, default=False, type='bool'), + hwtacacs_vpn_name=dict(type='str'), + hwtacacs_is_public_net=dict( + required=False, default=False, type='bool'), + hwtacacs_server_host_name=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + check_module_argument(module=module) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + # common para + state = module.params['state'] + + # local para + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + + # radius para + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + radius_server_name = module.params['radius_server_name'] + + # hwtacacs para + hwtacacs_template = module.params['hwtacacs_template'] + hwtacacs_server_ip = module.params['hwtacacs_server_ip'] + hwtacacs_server_ipv6 = module.params['hwtacacs_server_ipv6'] + hwtacacs_server_type = module.params['hwtacacs_server_type'] + hwtacacs_is_secondary_server = module.params[ + 'hwtacacs_is_secondary_server'] + hwtacacs_vpn_name = module.params['hwtacacs_vpn_name'] + hwtacacs_is_public_net = module.params['hwtacacs_is_public_net'] + hwtacacs_server_host_name = module.params['hwtacacs_server_host_name'] + + ce_aaa_server_host = AaaServerHost() + + if not ce_aaa_server_host: + module.fail_json(msg='Error: Construct ce_aaa_server failed.') + + # get proposed + proposed["state"] = state + if local_user_name: + proposed["local_user_name"] = local_user_name + if local_password: + proposed["local_password"] = "******" + if local_service_type: + proposed["local_service_type"] = local_service_type + if local_ftp_dir: + proposed["local_ftp_dir"] = local_ftp_dir + if local_user_level: + proposed["local_user_level"] = local_user_level + if local_user_group: + proposed["local_user_group"] = local_user_group + if radius_group_name: + proposed["radius_group_name"] = radius_group_name + if radius_server_type: + proposed["radius_server_type"] = radius_server_type + if radius_server_ip: + proposed["radius_server_ip"] = radius_server_ip + if radius_server_ipv6: + proposed["radius_server_ipv6"] = radius_server_ipv6 + if radius_server_port: + proposed["radius_server_port"] = radius_server_port + if radius_server_mode: + proposed["radius_server_mode"] = radius_server_mode + if radius_vpn_name: + proposed["radius_vpn_name"] = radius_vpn_name + if radius_server_name: + proposed["radius_server_name"] = radius_server_name + if hwtacacs_template: + proposed["hwtacacs_template"] = hwtacacs_template + if hwtacacs_server_ip: + proposed["hwtacacs_server_ip"] = hwtacacs_server_ip + if hwtacacs_server_ipv6: + proposed["hwtacacs_server_ipv6"] = hwtacacs_server_ipv6 + if hwtacacs_server_type: + proposed["hwtacacs_server_type"] = hwtacacs_server_type + proposed["hwtacacs_is_secondary_server"] = hwtacacs_is_secondary_server + if hwtacacs_vpn_name: + proposed["hwtacacs_vpn_name"] = hwtacacs_vpn_name + proposed["hwtacacs_is_public_net"] = hwtacacs_is_public_net + if hwtacacs_server_host_name: + proposed["hwtacacs_server_host_name"] = hwtacacs_server_host_name + + if local_user_name: + + if state == "present" and not local_password: + module.fail_json( + msg='Error: Please input local_password when config local user.') + + local_user_result = ce_aaa_server_host.get_local_user_info( + module=module) + existing["local user name"] = local_user_result["local_user_info"] + + if state == "present": + # present local user + if local_user_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_local_user_info(module=module) + + changed = True + updates.append(cmd) + + else: + # absent local user + if local_user_result["need_cfg"]: + if not local_service_type and not local_ftp_dir and not local_user_level and not local_user_group: + cmd = ce_aaa_server_host.delete_local_user_info( + module=module) + else: + cmd = ce_aaa_server_host.merge_local_user_info( + module=module) + + changed = True + updates.append(cmd) + + local_user_result = ce_aaa_server_host.get_local_user_info( + module=module) + end_state["local user name"] = local_user_result["local_user_info"] + + if radius_group_name: + + if not radius_server_ip and not radius_server_ipv6 and not radius_server_name: + module.fail_json( + msg='Error: Please input radius_server_ip or radius_server_ipv6 or radius_server_name.') + + if radius_server_ip and radius_server_ipv6: + module.fail_json( + msg='Error: Please do not input radius_server_ip and radius_server_ipv6 at the same time.') + + if not radius_server_type or not radius_server_port or not radius_server_mode or not radius_vpn_name: + module.fail_json( + msg='Error: Please input radius_server_type radius_server_port radius_server_mode radius_vpn_name.') + + if radius_server_ip: + rds_server_ipv4_result = ce_aaa_server_host.get_radius_server_cfg_ipv4( + module=module) + if radius_server_ipv6: + rds_server_ipv6_result = ce_aaa_server_host.get_radius_server_cfg_ipv6( + module=module) + if radius_server_name: + rds_server_name_result = ce_aaa_server_host.get_radius_server_name( + module=module) + + if radius_server_ip and rds_server_ipv4_result["radius_server_ip_v4"]: + existing["radius server ipv4"] = rds_server_ipv4_result[ + "radius_server_ip_v4"] + if radius_server_ipv6 and rds_server_ipv6_result["radius_server_ip_v6"]: + existing["radius server ipv6"] = rds_server_ipv6_result[ + "radius_server_ip_v6"] + if radius_server_name and rds_server_name_result["radius_server_name_cfg"]: + existing["radius server name cfg"] = rds_server_name_result[ + "radius_server_name_cfg"] + + if state == "present": + if radius_server_ip and rds_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ipv6 and rds_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if radius_server_name and rds_server_name_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_name( + module=module) + changed = True + updates.append(cmd) + else: + if radius_server_ip and rds_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ipv6 and rds_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if radius_server_name and rds_server_name_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_name( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ip: + rds_server_ipv4_result = ce_aaa_server_host.get_radius_server_cfg_ipv4( + module=module) + if radius_server_ipv6: + rds_server_ipv6_result = ce_aaa_server_host.get_radius_server_cfg_ipv6( + module=module) + if radius_server_name: + rds_server_name_result = ce_aaa_server_host.get_radius_server_name( + module=module) + + if radius_server_ip and rds_server_ipv4_result["radius_server_ip_v4"]: + end_state["radius server ipv4"] = rds_server_ipv4_result[ + "radius_server_ip_v4"] + if radius_server_ipv6 and rds_server_ipv6_result["radius_server_ip_v6"]: + end_state["radius server ipv6"] = rds_server_ipv6_result[ + "radius_server_ip_v6"] + if radius_server_name and rds_server_name_result["radius_server_name_cfg"]: + end_state["radius server name cfg"] = rds_server_name_result[ + "radius_server_name_cfg"] + + if hwtacacs_template: + + if not hwtacacs_server_ip and not hwtacacs_server_ipv6 and not hwtacacs_server_host_name: + module.fail_json( + msg='Error: Please input hwtacacs_server_ip or hwtacacs_server_ipv6 or hwtacacs_server_host_name.') + + if not hwtacacs_server_type or not hwtacacs_vpn_name: + module.fail_json( + msg='Error: Please input hwtacacs_server_type hwtacacs_vpn_name.') + + if hwtacacs_server_ip and hwtacacs_server_ipv6: + module.fail_json( + msg='Error: Please do not set hwtacacs_server_ip and hwtacacs_server_ipv6 at the same time.') + + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + if hwtacacs_is_public_net: + module.fail_json( + msg='Error: Please do not set vpn and public net at the same time.') + + if hwtacacs_server_ip: + hwtacacs_server_ipv4_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv4( + module=module) + if hwtacacs_server_ipv6: + hwtacacs_server_ipv6_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv6( + module=module) + if hwtacacs_server_host_name: + hwtacacs_host_name_result = ce_aaa_server_host.get_hwtacacs_host_server_cfg( + module=module) + + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["hwtacacs_server_cfg_ipv4"]: + existing["hwtacacs server cfg ipv4"] = hwtacacs_server_ipv4_result[ + "hwtacacs_server_cfg_ipv4"] + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["hwtacacs_server_cfg_ipv6"]: + existing["hwtacacs server cfg ipv6"] = hwtacacs_server_ipv6_result[ + "hwtacacs_server_cfg_ipv6"] + if hwtacacs_server_host_name and hwtacacs_host_name_result["hwtacacs_server_name_cfg"]: + existing["hwtacacs server name cfg"] = hwtacacs_host_name_result[ + "hwtacacs_server_name_cfg"] + + if state == "present": + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_host_name and hwtacacs_host_name_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_host_server_cfg( + module=module) + changed = True + updates.append(cmd) + + else: + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_host_name and hwtacacs_host_name_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_host_server_cfg( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ip: + hwtacacs_server_ipv4_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv4( + module=module) + if hwtacacs_server_ipv6: + hwtacacs_server_ipv6_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv6( + module=module) + if hwtacacs_server_host_name: + hwtacacs_host_name_result = ce_aaa_server_host.get_hwtacacs_host_server_cfg( + module=module) + + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["hwtacacs_server_cfg_ipv4"]: + end_state["hwtacacs server cfg ipv4"] = hwtacacs_server_ipv4_result[ + "hwtacacs_server_cfg_ipv4"] + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["hwtacacs_server_cfg_ipv6"]: + end_state["hwtacacs server cfg ipv6"] = hwtacacs_server_ipv6_result[ + "hwtacacs_server_cfg_ipv6"] + if hwtacacs_server_host_name and hwtacacs_host_name_result["hwtacacs_server_name_cfg"]: + end_state["hwtacacs server name cfg"] = hwtacacs_host_name_result[ + "hwtacacs_server_name_cfg"] + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl.py new file mode 100644 index 00000000..2e7b2c50 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl.py @@ -0,0 +1,1000 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl +short_description: Manages base ACL configuration on HUAWEI CloudEngine switches. +description: + - Manages base ACL configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent','delete_acl'] + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 2000 to 2999 indicates a basic ACL. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + acl_num: + description: + - ACL number. + The value is an integer ranging from 2000 to 2999. + acl_step: + description: + - ACL step. + The value is an integer ranging from 1 to 20. The default value is 5. + acl_description: + description: + - ACL description. + The value is a string of 1 to 127 characters. + rule_name: + description: + - Name of a basic ACL rule. + The value is a string of 1 to 32 characters. + The value is case-insensitive, and cannot contain spaces or begin with an underscore (_). + rule_id: + description: + - ID of a basic ACL rule in configuration mode. + The value is an integer ranging from 0 to 4294967294. + rule_action: + description: + - Matching mode of basic ACL rules. + choices: ['permit','deny'] + source_ip: + description: + - Source IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + src_mask: + description: + - Mask of a source IP address. + The value is an integer ranging from 1 to 32. + frag_type: + description: + - Type of packet fragmentation. + choices: ['fragment', 'clear_fragment'] + vrf_name: + description: + - VPN instance name. + The value is a string of 1 to 31 characters.The default value is _public_. + time_range: + description: + - Name of a time range in which an ACL rule takes effect. + The value is a string of 1 to 32 characters. + The value is case-insensitive, and cannot contain spaces. The name must start with an uppercase + or lowercase letter. In addition, the word "all" cannot be specified as a time range name. + rule_description: + description: + - Description about an ACL rule. + The value is a string of 1 to 127 characters. + log_flag: + description: + - Flag of logging matched data packets. + type: bool + default: 'no' +''' + +EXAMPLES = ''' + +- name: CloudEngine acl test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config ACL" + community.network.ce_acl: + state: present + acl_name: 2200 + provider: "{{ cli }}" + + - name: "Undo ACL" + community.network.ce_acl: + state: delete_acl + acl_name: 2200 + provider: "{{ cli }}" + + - name: "Config ACL base rule" + community.network.ce_acl: + state: present + acl_name: 2200 + rule_name: test_rule + rule_id: 111 + rule_action: permit + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + time_range: wdz_acl_time + provider: "{{ cli }}" + + - name: "undo ACL base rule" + community.network.ce_acl: + state: absent + acl_name: 2200 + rule_name: test_rule + rule_id: 111 + rule_action: permit + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + time_range: wdz_acl_time + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "test", "state": "delete_acl"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"aclNumOrName": "test", "aclType": "Basic"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: ["undo acl name test"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get acl +CE_GET_ACL_HEADER = """ + + + + + +""" +CE_GET_ACL_TAIL = """ + + + + +""" +# merge acl +CE_MERGE_ACL_HEADER = """ + + + + + %s +""" +CE_MERGE_ACL_TAIL = """ + + + + +""" +# delete acl +CE_DELETE_ACL_HEADER = """ + + + + + %s +""" +CE_DELETE_ACL_TAIL = """ + + + + +""" + +# get acl base rule +CE_GET_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + +""" +CE_GET_ACL_BASE_RULE_TAIL = """ + + + + + + +""" +# merge acl base rule +CE_MERGE_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_MERGE_ACL_BASE_RULE_TAIL = """ + + + + + + +""" +# delete acl base rule +CE_DELETE_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_DELETE_ACL_BASE_RULE_TAIL = """ + + + + + + +""" + + +class BaseAcl(object): + """ Manages base acl configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] or None + self.acl_num = self.module.params['acl_num'] or None + self.acl_type = None + self.acl_step = self.module.params['acl_step'] or None + self.acl_description = self.module.params['acl_description'] or None + self.rule_name = self.module.params['rule_name'] or None + self.rule_id = self.module.params['rule_id'] or None + self.rule_action = self.module.params['rule_action'] or None + self.source_ip = self.module.params['source_ip'] or None + self.src_mask = self.module.params['src_mask'] or None + self.src_wild = None + self.frag_type = self.module.params['frag_type'] or None + self.vrf_name = self.module.params['vrf_name'] or None + self.time_range = self.module.params['time_range'] or None + self.rule_description = self.module.params['rule_description'] or None + self.log_flag = self.module.params['log_flag'] + + # cur config + self.cur_acl_cfg = dict() + self.cur_base_rule_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def get_wildcard_mask(self): + """ convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255 """ + + mask_int = ["255"] * 4 + value = int(self.src_mask) + + if value > 32: + self.module.fail_json(msg='Error: IPv4 ipaddress mask length is invalid.') + if value < 8: + mask_int[0] = str(int(~(0xFF << (8 - value % 8)) & 0xFF)) + if value >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (value % 16))) & 0xFF)) + if value >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (value % 24))) & 0xFF)) + if value >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (value % 32))) & 0xFF)) + if value == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + def check_acl_args(self): + """ Check acl invalid args """ + + need_cfg = False + find_flag = False + self.cur_acl_cfg["acl_info"] = [] + + if self.acl_name: + + if self.acl_name.isdigit(): + if int(self.acl_name) < 2000 or int(self.acl_name) > 2999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000-2999] for base ACL.') + + if self.acl_num: + self.module.fail_json( + msg='Error: The acl_name is digit, so should not input acl_num at the same time.') + else: + + self.acl_type = "Basic" + + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.state == "present": + if not self.acl_num and not self.acl_type and not self.rule_name: + self.module.fail_json( + msg='Error: Please input acl_num or acl_type when config ACL.') + + if self.acl_num: + if self.acl_num.isdigit(): + if int(self.acl_num) < 2000 or int(self.acl_num) > 2999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000-2999] for base ACL.') + else: + self.module.fail_json( + msg='Error: The acl_num is not digit.') + + if self.acl_step: + if self.acl_step.isdigit(): + if int(self.acl_step) < 1 or int(self.acl_step) > 20: + self.module.fail_json( + msg='Error: The value of acl_step is out of [1 - 20].') + else: + self.module.fail_json( + msg='Error: The acl_step is not digit.') + + if self.acl_description: + if len(self.acl_description) < 1 or len(self.acl_description) > 127: + self.module.fail_json( + msg='Error: The len of acl_description is out of [1 - 127].') + + conf_str = CE_GET_ACL_HEADER + + if self.acl_type: + conf_str += "" + if self.acl_num or self.acl_name.isdigit(): + conf_str += "" + if self.acl_step: + conf_str += "" + if self.acl_description: + conf_str += "" + + conf_str += CE_GET_ACL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # parse acl + acl_info = root.findall( + "acl/aclGroups/aclGroup") + if acl_info: + for tmp in acl_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclNumOrName", "aclType", "aclNumber", "aclStep", "aclDescription"]: + tmp_dict[site.tag] = site.text + + self.cur_acl_cfg["acl_info"].append(tmp_dict) + + if self.cur_acl_cfg["acl_info"]: + find_list = list() + for tmp in self.cur_acl_cfg["acl_info"]: + cur_cfg_dict = dict() + exist_cfg_dict = dict() + if self.acl_name: + if self.acl_name.isdigit() and tmp.get("aclNumber"): + cur_cfg_dict["aclNumber"] = self.acl_name + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + else: + cur_cfg_dict["aclNumOrName"] = self.acl_name + exist_cfg_dict["aclNumOrName"] = tmp.get("aclNumOrName") + if self.acl_type: + cur_cfg_dict["aclType"] = self.acl_type + exist_cfg_dict["aclType"] = tmp.get("aclType") + if self.acl_num: + cur_cfg_dict["aclNumber"] = self.acl_num + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + if self.acl_step: + cur_cfg_dict["aclStep"] = self.acl_step + exist_cfg_dict["aclStep"] = tmp.get("aclStep") + if self.acl_description: + cur_cfg_dict["aclDescription"] = self.acl_description + exist_cfg_dict["aclDescription"] = tmp.get("aclDescription") + + if cur_cfg_dict == exist_cfg_dict: + find_bool = True + else: + find_bool = False + find_list.append(find_bool) + + for mem in find_list: + if mem: + find_flag = True + break + else: + find_flag = False + + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "delete_acl": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_acl_cfg["need_cfg"] = need_cfg + + def check_base_rule_args(self): + """ Check base rule invalid args """ + + need_cfg = False + find_flag = False + self.cur_base_rule_cfg["base_rule_info"] = [] + + if self.acl_name: + + if self.state == "absent": + if not self.rule_name: + self.module.fail_json( + msg='Error: Please input rule_name when state is absent.') + + # config rule + if self.rule_name: + if len(self.rule_name) < 1 or len(self.rule_name) > 32: + self.module.fail_json( + msg='Error: The len of rule_name is out of [1 - 32].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + if self.rule_id: + if self.rule_id.isdigit(): + if int(self.rule_id) < 0 or int(self.rule_id) > 4294967294: + self.module.fail_json( + msg='Error: The value of rule_id is out of [0 - 4294967294].') + else: + self.module.fail_json( + msg='Error: The rule_id is not digit.') + + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The source_ip %s is invalid.' % self.source_ip) + if not self.src_mask: + self.module.fail_json( + msg='Error: Please input src_mask.') + + if self.src_mask: + if self.src_mask.isdigit(): + if int(self.src_mask) < 1 or int(self.src_mask) > 32: + self.module.fail_json( + msg='Error: The src_mask is out of [1 - 32].') + self.src_wild = self.get_wildcard_mask() + else: + self.module.fail_json( + msg='Error: The src_mask is not digit.') + + if self.vrf_name: + if len(self.vrf_name) < 1 or len(self.vrf_name) > 31: + self.module.fail_json( + msg='Error: The len of vrf_name is out of [1 - 31].') + + if self.time_range: + if len(self.time_range) < 1 or len(self.time_range) > 32: + self.module.fail_json( + msg='Error: The len of time_range is out of [1 - 32].') + + if self.rule_description: + if len(self.rule_description) < 1 or len(self.rule_description) > 127: + self.module.fail_json( + msg='Error: The len of rule_description is out of [1 - 127].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + conf_str = CE_GET_ACL_BASE_RULE_HEADER % self.acl_name + + if self.rule_id: + conf_str += "" + if self.rule_action: + conf_str += "" + if self.source_ip: + conf_str += "" + if self.src_wild: + conf_str += "" + if self.frag_type: + conf_str += "" + if self.vrf_name: + conf_str += "" + if self.time_range: + conf_str += "" + if self.rule_description: + conf_str += "" + conf_str += "" + + conf_str += CE_GET_ACL_BASE_RULE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse base rule + base_rule_info = root.findall( + "acl/aclGroups/aclGroup/aclRuleBas4s/aclRuleBas4") + if base_rule_info: + for tmp in base_rule_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclRuleName", "aclRuleID", "aclAction", "aclSourceIp", "aclSrcWild", + "aclFragType", "vrfName", "aclTimeName", "aclRuleDescription", + "aclLogFlag"]: + tmp_dict[site.tag] = site.text + + self.cur_base_rule_cfg[ + "base_rule_info"].append(tmp_dict) + + if self.cur_base_rule_cfg["base_rule_info"]: + for tmp in self.cur_base_rule_cfg["base_rule_info"]: + find_flag = True + + if self.rule_name and tmp.get("aclRuleName") != self.rule_name: + find_flag = False + if self.rule_id and tmp.get("aclRuleID") != self.rule_id: + find_flag = False + if self.rule_action and tmp.get("aclAction") != self.rule_action: + find_flag = False + if self.source_ip: + tmp_src_ip = self.source_ip.split(".") + tmp_src_wild = self.src_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclSourceIp"): + find_flag = False + if self.src_wild and tmp.get("aclSrcWild") != self.src_wild: + find_flag = False + frag_type = "clear_fragment" if tmp.get("aclFragType") is None else tmp.get("aclFragType") + if self.frag_type and frag_type != self.frag_type: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.time_range and tmp.get("aclTimeName") != self.time_range: + find_flag = False + if self.rule_description and tmp.get("aclRuleDescription") != self.rule_description: + find_flag = False + if tmp.get("aclLogFlag") != str(self.log_flag).lower(): + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_base_rule_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + if self.acl_num: + self.proposed["acl_num"] = self.acl_num + if self.acl_step: + self.proposed["acl_step"] = self.acl_step + if self.acl_description: + self.proposed["acl_description"] = self.acl_description + if self.rule_name: + self.proposed["rule_name"] = self.rule_name + if self.rule_id: + self.proposed["rule_id"] = self.rule_id + if self.rule_action: + self.proposed["rule_action"] = self.rule_action + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.src_mask: + self.proposed["src_mask"] = self.src_mask + if self.frag_type: + self.proposed["frag_type"] = self.frag_type + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.time_range: + self.proposed["time_range"] = self.time_range + if self.rule_description: + self.proposed["rule_description"] = self.rule_description + if self.log_flag: + self.proposed["log_flag"] = self.log_flag + + def get_existing(self): + """ Get existing state """ + + self.existing["acl_info"] = self.cur_acl_cfg["acl_info"] + self.existing["base_rule_info"] = self.cur_base_rule_cfg[ + "base_rule_info"] + + def get_end_state(self): + """ Get end state """ + + self.check_acl_args() + self.end_state["acl_info"] = self.cur_acl_cfg["acl_info"] + + self.check_base_rule_args() + self.end_state["base_rule_info"] = self.cur_base_rule_cfg[ + "base_rule_info"] + + def merge_acl(self): + """ Merge acl operation """ + + conf_str = CE_MERGE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_MERGE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl failed.') + + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + if self.acl_type and not self.acl_num: + cmd = "acl name %s %s" % (self.acl_name, self.acl_type.lower()) + elif self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + elif not self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + self.updates_cmd.append(cmd) + + if self.acl_description: + cmd = "description %s" % self.acl_description + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "step %s" % self.acl_step + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_acl(self): + """ Delete acl operation """ + + conf_str = CE_DELETE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_DELETE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl failed.') + + if self.acl_description: + cmd = "undo description" + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "undo step" + self.updates_cmd.append(cmd) + + if self.acl_name.isdigit(): + cmd = "undo acl number %s" % self.acl_name + else: + cmd = "undo acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_base_rule(self): + """ Merge base rule operation """ + + conf_str = CE_MERGE_ACL_BASE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_MERGE_ACL_BASE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl base rule failed.') + + if self.rule_action: + cmd = "rule" + if self.rule_id: + cmd += " %s" % self.rule_id + cmd += " %s" % self.rule_action + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + if self.rule_description: + cmd = "rule %s description %s" % ( + self.rule_id, self.rule_description) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_base_rule(self): + """ Delete base rule operation """ + + conf_str = CE_DELETE_ACL_BASE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_DELETE_ACL_BASE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl base rule failed.') + + if self.rule_description: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s description" % self.rule_id + self.updates_cmd.append(cmd) + + if self.rule_id: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s" % self.rule_id + self.updates_cmd.append(cmd) + elif self.rule_action: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule" + cmd += " %s" % self.rule_action + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_acl_args() + self.check_base_rule_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_acl_cfg["need_cfg"]: + self.merge_acl() + if self.cur_base_rule_cfg["need_cfg"]: + self.merge_base_rule() + + elif self.state == "absent": + if self.cur_base_rule_cfg["need_cfg"]: + self.delete_base_rule() + + elif self.state == "delete_acl": + if self.cur_acl_cfg["need_cfg"]: + self.delete_acl() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent', + 'delete_acl'], default='present'), + acl_name=dict(type='str', required=True), + acl_num=dict(type='str'), + acl_step=dict(type='str'), + acl_description=dict(type='str'), + rule_name=dict(type='str'), + rule_id=dict(type='str'), + rule_action=dict(choices=['permit', 'deny']), + source_ip=dict(type='str'), + src_mask=dict(type='str'), + frag_type=dict(choices=['fragment', 'clear_fragment']), + vrf_name=dict(type='str'), + time_range=dict(type='str'), + rule_description=dict(type='str'), + log_flag=dict(required=False, default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = BaseAcl(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_advance.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_advance.py new file mode 100644 index 00000000..ce3e420e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_advance.py @@ -0,0 +1,1746 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl_advance +short_description: Manages advanced ACL configuration on HUAWEI CloudEngine switches. +description: + - Manages advanced ACL configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + required: false + default: present + choices: ['present','absent','delete_acl'] + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 3000 to 3999 indicates a advance ACL. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + acl_num: + description: + - ACL number. + The value is an integer ranging from 3000 to 3999. + acl_step: + description: + - ACL step. + The value is an integer ranging from 1 to 20. The default value is 5. + acl_description: + description: + - ACL description. + The value is a string of 1 to 127 characters. + rule_name: + description: + - Name of a basic ACL rule. + The value is a string of 1 to 32 characters. + rule_id: + description: + - ID of a basic ACL rule in configuration mode. + The value is an integer ranging from 0 to 4294967294. + rule_action: + description: + - Matching mode of basic ACL rules. + choices: ['permit','deny'] + protocol: + description: + - Protocol type. + choices: ['ip', 'icmp', 'igmp', 'ipinip', 'tcp', 'udp', 'gre', 'ospf'] + source_ip: + description: + - Source IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + src_mask: + description: + - Source IP address mask. + The value is an integer ranging from 1 to 32. + src_pool_name: + description: + - Name of a source pool. + The value is a string of 1 to 32 characters. + dest_ip: + description: + - Destination IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + dest_mask: + description: + - Destination IP address mask. + The value is an integer ranging from 1 to 32. + dest_pool_name: + description: + - Name of a destination pool. + The value is a string of 1 to 32 characters. + src_port_op: + description: + - Range type of the source port. + choices: ['lt','eq', 'gt', 'range'] + src_port_begin: + description: + - Start port number of the source port. + The value is an integer ranging from 0 to 65535. + src_port_end: + description: + - End port number of the source port. + The value is an integer ranging from 0 to 65535. + src_port_pool_name: + description: + - Name of a source port pool. + The value is a string of 1 to 32 characters. + dest_port_op: + description: + - Range type of the destination port. + choices: ['lt','eq', 'gt', 'range'] + dest_port_begin: + description: + - Start port number of the destination port. + The value is an integer ranging from 0 to 65535. + dest_port_end: + description: + - End port number of the destination port. + The value is an integer ranging from 0 to 65535. + dest_port_pool_name: + description: + - Name of a destination port pool. + The value is a string of 1 to 32 characters. + frag_type: + description: + - Type of packet fragmentation. + choices: ['fragment', 'clear_fragment'] + precedence: + description: + - Data packets can be filtered based on the priority field. + The value is an integer ranging from 0 to 7. + tos: + description: + - ToS value on which data packet filtering is based. + The value is an integer ranging from 0 to 15. + dscp: + description: + - Differentiated Services Code Point. + The value is an integer ranging from 0 to 63. + icmp_name: + description: + - ICMP name. + choices: ['unconfiged', 'echo', 'echo-reply', 'fragmentneed-DFset', 'host-redirect', + 'host-tos-redirect', 'host-unreachable', 'information-reply', 'information-request', + 'net-redirect', 'net-tos-redirect', 'net-unreachable', 'parameter-problem', + 'port-unreachable', 'protocol-unreachable', 'reassembly-timeout', 'source-quench', + 'source-route-failed', 'timestamp-reply', 'timestamp-request', 'ttl-exceeded', + 'address-mask-reply', 'address-mask-request', 'custom'] + icmp_type: + description: + - ICMP type. This parameter is available only when the packet protocol is ICMP. + The value is an integer ranging from 0 to 255. + icmp_code: + description: + - ICMP message code. Data packets can be filtered based on the ICMP message code. + The value is an integer ranging from 0 to 255. + ttl_expired: + description: + - Whether TTL Expired is matched, with the TTL value of 1. + type: bool + default: 'no' + vrf_name: + description: + - VPN instance name. + The value is a string of 1 to 31 characters.The default value is _public_. + syn_flag: + description: + - TCP flag value. + The value is an integer ranging from 0 to 63. + tcp_flag_mask: + description: + - TCP flag mask value. + The value is an integer ranging from 0 to 63. + established: + description: + - Match established connections. + type: bool + default: 'no' + time_range: + description: + - Name of a time range in which an ACL rule takes effect. + rule_description: + description: + - Description about an ACL rule. + igmp_type: + description: + - Internet Group Management Protocol. + choices: ['host-query', 'mrouter-adver', 'mrouter-solic', 'mrouter-termi', 'mtrace-resp', 'mtrace-route', + 'v1host-report', 'v2host-report', 'v2leave-group', 'v3host-report'] + log_flag: + description: + - Flag of logging matched data packets. + type: bool + default: 'no' +''' + +EXAMPLES = ''' + +- name: CloudEngine advance acl test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config ACL" + community.network.ce_acl_advance: + state: present + acl_name: 3200 + provider: "{{ cli }}" + + - name: "Undo ACL" + community.network.ce_acl_advance: + state: delete_acl + acl_name: 3200 + provider: "{{ cli }}" + + - name: "Config ACL advance rule" + community.network.ce_acl_advance: + state: present + acl_name: test + rule_name: test_rule + rule_id: 111 + rule_action: permit + protocol: tcp + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + provider: "{{ cli }}" + + - name: "Undo ACL advance rule" + community.network.ce_acl_advance: + state: absent + acl_name: test + rule_name: test_rule + rule_id: 111 + rule_action: permit + protocol: tcp + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "test", "state": "delete_acl"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"aclNumOrName": "test", "aclType": "Advance"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: ["undo acl name test"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +# get acl +CE_GET_ACL_HEADER = """ + + + + + +""" +CE_GET_ACL_TAIL = """ + + + + +""" +# merge acl +CE_MERGE_ACL_HEADER = """ + + + + + %s +""" +CE_MERGE_ACL_TAIL = """ + + + + +""" +# delete acl +CE_DELETE_ACL_HEADER = """ + + + + + %s +""" +CE_DELETE_ACL_TAIL = """ + + + + +""" + +# get acl advance rule +CE_GET_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + +""" +CE_GET_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" +# merge acl advance rule +CE_MERGE_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_MERGE_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" +# delete acl advance rule +CE_DELETE_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_DELETE_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" + + +PROTOCOL_NUM = {"ip": "0", + "icmp": "1", + "igmp": "2", + "ipinip": "4", + "tcp": "6", + "udp": "17", + "gre": "47", + "ospf": "89"} + +IGMP_TYPE_NUM = {"host-query": "17", + "mrouter-adver": "48", + "mrouter-solic": "49", + "mrouter-termi": "50", + "mtrace-resp": "30", + "mtrace-route": "31", + "v1host-report": "18", + "v2host-report": "22", + "v2leave-group": "23", + "v3host-report": "34"} + + +def get_wildcard_mask(mask): + """ convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255 """ + + mask_int = ["255"] * 4 + value = int(mask) + + if value > 32: + return None + if value < 8: + mask_int[0] = str(int(~(0xFF << (8 - value % 8)) & 0xFF)) + if value >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (value % 16))) & 0xFF)) + if value >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (value % 24))) & 0xFF)) + if value >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (value % 32))) & 0xFF)) + if value == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + +class AdvanceAcl(object): + """ Manages advance acl configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] or None + self.acl_num = self.module.params['acl_num'] or None + self.acl_type = None + self.acl_step = self.module.params['acl_step'] or None + self.acl_description = self.module.params['acl_description'] or None + self.rule_name = self.module.params['rule_name'] or None + self.rule_id = self.module.params['rule_id'] or None + self.rule_action = self.module.params['rule_action'] or None + self.protocol = self.module.params['protocol'] or None + self.protocol_num = None + self.source_ip = self.module.params['source_ip'] or None + self.src_mask = self.module.params['src_mask'] or None + self.src_wild = None + self.src_pool_name = self.module.params['src_pool_name'] or None + self.dest_ip = self.module.params['dest_ip'] or None + self.dest_mask = self.module.params['dest_mask'] or None + self.dest_wild = None + self.dest_pool_name = self.module.params['dest_pool_name'] or None + self.src_port_op = self.module.params['src_port_op'] or None + self.src_port_begin = self.module.params['src_port_begin'] or None + self.src_port_end = self.module.params['src_port_end'] or None + self.src_port_pool_name = self.module.params[ + 'src_port_pool_name'] or None + self.dest_port_op = self.module.params['dest_port_op'] or None + self.dest_port_begin = self.module.params['dest_port_begin'] or None + self.dest_port_end = self.module.params['dest_port_end'] or None + self.dest_port_pool_name = self.module.params[ + 'dest_port_pool_name'] or None + self.frag_type = self.module.params['frag_type'] or None + self.precedence = self.module.params['precedence'] or None + self.tos = self.module.params['tos'] or None + self.dscp = self.module.params['dscp'] or None + self.icmp_name = self.module.params['icmp_name'] or None + self.icmp_type = self.module.params['icmp_type'] or None + self.icmp_code = self.module.params['icmp_code'] or None + self.ttl_expired = self.module.params['ttl_expired'] + self.vrf_name = self.module.params['vrf_name'] or None + self.syn_flag = self.module.params['syn_flag'] or None + self.tcp_flag_mask = self.module.params['tcp_flag_mask'] or None + self.established = self.module.params['established'] + self.time_range = self.module.params['time_range'] or None + self.rule_description = self.module.params['rule_description'] or None + self.igmp_type = self.module.params['igmp_type'] or None + self.igmp_type_num = None + self.log_flag = self.module.params['log_flag'] + + self.precedence_name = dict() + self.precedence_name["0"] = "routine" + self.precedence_name["1"] = "priority" + self.precedence_name["2"] = "immediate" + self.precedence_name["3"] = "flash" + self.precedence_name["4"] = "flash-override" + self.precedence_name["5"] = "critical" + self.precedence_name["6"] = "internet" + self.precedence_name["7"] = "network" + + # cur config + self.cur_acl_cfg = dict() + self.cur_advance_rule_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def get_protocol_num(self): + """ Get protocol num by name """ + + if self.protocol: + self.protocol_num = PROTOCOL_NUM.get(self.protocol) + + def get_igmp_type_num(self): + """ Get igmp type num by type """ + + if self.igmp_type: + self.igmp_type_num = IGMP_TYPE_NUM.get(self.igmp_type) + + def check_acl_args(self): + """ Check acl invalid args """ + + need_cfg = False + find_flag = False + self.cur_acl_cfg["acl_info"] = [] + + if self.acl_name: + + if self.acl_name.isdigit(): + if int(self.acl_name) < 3000 or int(self.acl_name) > 3999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [3000-3999] for advance ACL.') + + if self.acl_num: + self.module.fail_json( + msg='Error: The acl_name is digit, so should not input acl_num at the same time.') + else: + + self.acl_type = "Advance" + + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.state == "present": + if not self.acl_num and not self.acl_type and not self.rule_name: + self.module.fail_json( + msg='Error: Please input acl_num or acl_type when config ACL.') + + if self.acl_num: + if self.acl_num.isdigit(): + if int(self.acl_num) < 3000 or int(self.acl_num) > 3999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [3000-3999] for advance ACL.') + else: + self.module.fail_json( + msg='Error: The acl_num is not digit.') + + if self.acl_step: + if self.acl_step.isdigit(): + if int(self.acl_step) < 1 or int(self.acl_step) > 20: + self.module.fail_json( + msg='Error: The value of acl_step is out of [1 - 20].') + else: + self.module.fail_json( + msg='Error: The acl_step is not digit.') + + if self.acl_description: + if len(self.acl_description) < 1 or len(self.acl_description) > 127: + self.module.fail_json( + msg='Error: The len of acl_description is out of [1 - 127].') + + conf_str = CE_GET_ACL_HEADER + + if self.acl_type: + conf_str += "" + if self.acl_num or self.acl_name.isdigit(): + conf_str += "" + if self.acl_step: + conf_str += "" + if self.acl_description: + conf_str += "" + + conf_str += CE_GET_ACL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse acl + acl_info = root.findall( + "acl/aclGroups/aclGroup") + if acl_info: + for tmp in acl_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclNumOrName", "aclType", "aclNumber", "aclStep", "aclDescription"]: + tmp_dict[site.tag] = site.text + + self.cur_acl_cfg["acl_info"].append(tmp_dict) + + if self.cur_acl_cfg["acl_info"]: + find_list = list() + for tmp in self.cur_acl_cfg["acl_info"]: + cur_cfg_dict = dict() + exist_cfg_dict = dict() + + if self.acl_name: + if self.acl_name.isdigit() and tmp.get("aclNumber"): + cur_cfg_dict["aclNumber"] = self.acl_name + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + else: + cur_cfg_dict["aclNumOrName"] = self.acl_name + exist_cfg_dict["aclNumOrName"] = tmp.get("aclNumOrName") + if self.acl_type: + cur_cfg_dict["aclType"] = self.acl_type + exist_cfg_dict["aclType"] = tmp.get("aclType") + if self.acl_num: + cur_cfg_dict["aclNumber"] = self.acl_num + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + if self.acl_step: + cur_cfg_dict["aclStep"] = self.acl_step + exist_cfg_dict["aclStep"] = tmp.get("aclStep") + if self.acl_description: + cur_cfg_dict["aclDescription"] = self.acl_description + exist_cfg_dict["aclDescription"] = tmp.get("aclDescription") + + if cur_cfg_dict == exist_cfg_dict: + find_bool = True + else: + find_bool = False + find_list.append(find_bool) + for mem in find_list: + if mem: + find_flag = True + break + else: + find_flag = False + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "delete_acl": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_acl_cfg["need_cfg"] = need_cfg + + def check_advance_rule_args(self): + """ Check advance rule invalid args """ + + need_cfg = False + find_flag = False + self.cur_advance_rule_cfg["adv_rule_info"] = [] + + if self.acl_name: + + if self.state == "absent": + if not self.rule_name: + self.module.fail_json( + msg='Error: Please input rule_name when state is absent.') + + # config rule + if self.rule_name: + if len(self.rule_name) < 1 or len(self.rule_name) > 32: + self.module.fail_json( + msg='Error: The len of rule_name is out of [1 - 32].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + if self.rule_id: + if self.rule_id.isdigit(): + if int(self.rule_id) < 0 or int(self.rule_id) > 4294967294: + self.module.fail_json( + msg='Error: The value of rule_id is out of [0 - 4294967294].') + else: + self.module.fail_json( + msg='Error: The rule_id is not digit.') + + if self.rule_action and not self.protocol: + self.module.fail_json( + msg='Error: The rule_action and the protocol must input at the same time.') + + if not self.rule_action and self.protocol: + self.module.fail_json( + msg='Error: The rule_action and the protocol must input at the same time.') + + if self.protocol: + self.get_protocol_num() + + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The source_ip %s is invalid.' % self.source_ip) + if not self.src_mask: + self.module.fail_json( + msg='Error: Please input src_mask.') + + if self.src_mask: + if self.src_mask.isdigit(): + if int(self.src_mask) < 1 or int(self.src_mask) > 32: + self.module.fail_json( + msg='Error: The value of src_mask is out of [1 - 32].') + self.src_wild = get_wildcard_mask(self.src_mask) + else: + self.module.fail_json( + msg='Error: The src_mask is not digit.') + + if self.src_pool_name: + if len(self.src_pool_name) < 1 or len(self.src_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of src_pool_name is out of [1 - 32].') + + if self.dest_ip: + if not check_ip_addr(self.dest_ip): + self.module.fail_json( + msg='Error: The dest_ip %s is invalid.' % self.dest_ip) + if not self.dest_mask: + self.module.fail_json( + msg='Error: Please input dest_mask.') + + if self.dest_mask: + if self.dest_mask.isdigit(): + if int(self.dest_mask) < 1 or int(self.dest_mask) > 32: + self.module.fail_json( + msg='Error: The value of dest_mask is out of [1 - 32].') + self.dest_wild = get_wildcard_mask(self.dest_mask) + else: + self.module.fail_json( + msg='Error: The dest_mask is not digit.') + + if self.dest_pool_name: + if len(self.dest_pool_name) < 1 or len(self.dest_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of dest_pool_name is out of [1 - 32].') + + if self.src_port_op: + if self.src_port_op == "lt": + if not self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_end must input.') + if self.src_port_begin: + self.module.fail_json( + msg='Error: The src_port_begin should not input.') + if self.src_port_op == "eq" or self.src_port_op == "gt": + if not self.src_port_begin: + self.module.fail_json( + msg='Error: The src_port_begin must input.') + if self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_end should not input.') + if self.src_port_op == "range": + if not self.src_port_begin or not self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_begin and src_port_end must input.') + + if self.src_port_begin: + if self.src_port_begin.isdigit(): + if int(self.src_port_begin) < 0 or int(self.src_port_begin) > 65535: + self.module.fail_json( + msg='Error: The value of src_port_begin is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The src_port_begin is not digit.') + + if self.src_port_end: + if self.src_port_end.isdigit(): + if int(self.src_port_end) < 0 or int(self.src_port_end) > 65535: + self.module.fail_json( + msg='Error: The value of src_port_end is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The src_port_end is not digit.') + + if self.src_port_pool_name: + if len(self.src_port_pool_name) < 1 or len(self.src_port_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of src_port_pool_name is out of [1 - 32].') + + if self.dest_port_op: + if self.dest_port_op == "lt": + if not self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_end must input.') + if self.dest_port_begin: + self.module.fail_json( + msg='Error: The dest_port_begin should not input.') + if self.dest_port_op == "eq" or self.dest_port_op == "gt": + if not self.dest_port_begin: + self.module.fail_json( + msg='Error: The dest_port_begin must input.') + if self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_end should not input.') + if self.dest_port_op == "range": + if not self.dest_port_begin or not self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_begin and dest_port_end must input.') + + if self.dest_port_begin: + if self.dest_port_begin.isdigit(): + if int(self.dest_port_begin) < 0 or int(self.dest_port_begin) > 65535: + self.module.fail_json( + msg='Error: The value of dest_port_begin is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The dest_port_begin is not digit.') + + if self.dest_port_end: + if self.dest_port_end.isdigit(): + if int(self.dest_port_end) < 0 or int(self.dest_port_end) > 65535: + self.module.fail_json( + msg='Error: The value of dest_port_end is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The dest_port_end is not digit.') + + if self.dest_port_pool_name: + if len(self.dest_port_pool_name) < 1 or len(self.dest_port_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of dest_port_pool_name is out of [1 - 32].') + + if self.precedence: + if self.precedence.isdigit(): + if int(self.precedence) < 0 or int(self.precedence) > 7: + self.module.fail_json( + msg='Error: The value of precedence is out of [0 - 7].') + else: + self.module.fail_json( + msg='Error: The precedence is not digit.') + + if self.tos: + if self.tos.isdigit(): + if int(self.tos) < 0 or int(self.tos) > 15: + self.module.fail_json( + msg='Error: The value of tos is out of [0 - 15].') + else: + self.module.fail_json( + msg='Error: The tos is not digit.') + + if self.dscp: + if self.dscp.isdigit(): + if int(self.dscp) < 0 or int(self.dscp) > 63: + self.module.fail_json( + msg='Error: The value of dscp is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The dscp is not digit.') + + if self.icmp_type: + if self.icmp_type.isdigit(): + if int(self.icmp_type) < 0 or int(self.icmp_type) > 255: + self.module.fail_json( + msg='Error: The value of icmp_type is out of [0 - 255].') + else: + self.module.fail_json( + msg='Error: The icmp_type is not digit.') + + if self.icmp_code: + if self.icmp_code.isdigit(): + if int(self.icmp_code) < 0 or int(self.icmp_code) > 255: + self.module.fail_json( + msg='Error: The value of icmp_code is out of [0 - 255].') + else: + self.module.fail_json( + msg='Error: The icmp_code is not digit.') + + if self.vrf_name: + if len(self.vrf_name) < 1 or len(self.vrf_name) > 31: + self.module.fail_json( + msg='Error: The len of vrf_name is out of [1 - 31].') + + if self.syn_flag: + if self.syn_flag.isdigit(): + if int(self.syn_flag) < 0 or int(self.syn_flag) > 63: + self.module.fail_json( + msg='Error: The value of syn_flag is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The syn_flag is not digit.') + + if self.tcp_flag_mask: + if self.tcp_flag_mask.isdigit(): + if int(self.tcp_flag_mask) < 0 or int(self.tcp_flag_mask) > 63: + self.module.fail_json( + msg='Error: The value of tcp_flag_mask is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The tcp_flag_mask is not digit.') + + if self.time_range: + if len(self.time_range) < 1 or len(self.time_range) > 32: + self.module.fail_json( + msg='Error: The len of time_range is out of [1 - 32].') + + if self.rule_description: + if len(self.rule_description) < 1 or len(self.rule_description) > 127: + self.module.fail_json( + msg='Error: The len of rule_description is out of [1 - 127].') + + if self.igmp_type: + self.get_igmp_type_num() + + conf_str = CE_GET_ACL_ADVANCE_RULE_HEADER % self.acl_name + + if self.rule_id: + conf_str += "" + if self.rule_action: + conf_str += "" + if self.protocol: + conf_str += "" + if self.source_ip: + conf_str += "" + if self.src_wild: + conf_str += "" + if self.src_pool_name: + conf_str += "" + if self.dest_ip: + conf_str += "" + if self.dest_wild: + conf_str += "" + if self.dest_pool_name: + conf_str += "" + if self.src_port_op: + conf_str += "" + if self.src_port_begin: + conf_str += "" + if self.src_port_end: + conf_str += "" + if self.src_port_pool_name: + conf_str += "" + if self.dest_port_op: + conf_str += "" + if self.dest_port_begin: + conf_str += "" + if self.dest_port_end: + conf_str += "" + if self.dest_port_pool_name: + conf_str += "" + if self.frag_type: + conf_str += "" + if self.precedence: + conf_str += "" + if self.tos: + conf_str += "" + if self.dscp: + conf_str += "" + if self.icmp_name: + conf_str += "" + if self.icmp_type: + conf_str += "" + if self.icmp_code: + conf_str += "" + conf_str += "" + if self.vrf_name: + conf_str += "" + if self.syn_flag: + conf_str += "" + if self.tcp_flag_mask: + conf_str += "" + conf_str += "" + if self.time_range: + conf_str += "" + if self.rule_description: + conf_str += "" + if self.igmp_type: + conf_str += "" + conf_str += "" + + conf_str += CE_GET_ACL_ADVANCE_RULE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse advance rule + adv_rule_info = root.findall( + "acl/aclGroups/aclGroup/aclRuleAdv4s/aclRuleAdv4") + if adv_rule_info: + for tmp in adv_rule_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclRuleName", "aclRuleID", "aclAction", "aclProtocol", "aclSourceIp", + "aclSrcWild", "aclSPoolName", "aclDestIp", "aclDestWild", + "aclDPoolName", "aclSrcPortOp", "aclSrcPortBegin", "aclSrcPortEnd", + "aclSPortPoolName", "aclDestPortOp", "aclDestPortB", "aclDestPortE", + "aclDPortPoolName", "aclFragType", "aclPrecedence", "aclTos", + "aclDscp", "aclIcmpName", "aclIcmpType", "aclIcmpCode", "aclTtlExpired", + "vrfName", "aclSynFlag", "aclTcpFlagMask", "aclEstablished", + "aclTimeName", "aclRuleDescription", "aclIgmpType", "aclLogFlag"]: + tmp_dict[site.tag] = site.text + + self.cur_advance_rule_cfg[ + "adv_rule_info"].append(tmp_dict) + + if self.cur_advance_rule_cfg["adv_rule_info"]: + for tmp in self.cur_advance_rule_cfg["adv_rule_info"]: + find_flag = True + + if self.rule_name and tmp.get("aclRuleName") != self.rule_name: + find_flag = False + if self.rule_id and tmp.get("aclRuleID") != self.rule_id: + find_flag = False + if self.rule_action and tmp.get("aclAction") != self.rule_action: + find_flag = False + if self.protocol and tmp.get("aclProtocol") != self.protocol_num: + find_flag = False + if self.source_ip: + tmp_src_ip = self.source_ip.split(".") + tmp_src_wild = self.src_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclSourceIp"): + find_flag = False + if self.src_wild and tmp.get("aclSrcWild") != self.src_wild: + find_flag = False + if self.src_pool_name and tmp.get("aclSPoolName") != self.src_pool_name: + find_flag = False + if self.dest_ip: + tmp_src_ip = self.dest_ip.split(".") + tmp_src_wild = self.dest_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclDestIp"): + find_flag = False + if self.dest_wild and tmp.get("aclDestWild") != self.dest_wild: + find_flag = False + if self.dest_pool_name and tmp.get("aclDPoolName") != self.dest_pool_name: + find_flag = False + if self.src_port_op and tmp.get("aclSrcPortOp") != self.src_port_op: + find_flag = False + if self.src_port_begin and tmp.get("aclSrcPortBegin") != self.src_port_begin: + find_flag = False + if self.src_port_end and tmp.get("aclSrcPortEnd") != self.src_port_end: + find_flag = False + if self.src_port_pool_name and tmp.get("aclSPortPoolName") != self.src_port_pool_name: + find_flag = False + if self.dest_port_op and tmp.get("aclDestPortOp") != self.dest_port_op: + find_flag = False + if self.dest_port_begin and tmp.get("aclDestPortB") != self.dest_port_begin: + find_flag = False + if self.dest_port_end and tmp.get("aclDestPortE") != self.dest_port_end: + find_flag = False + if self.dest_port_pool_name and tmp.get("aclDPortPoolName") != self.dest_port_pool_name: + find_flag = False + frag_type = "clear_fragment" if tmp.get("aclFragType") is None else tmp.get("aclFragType") + if self.frag_type and frag_type != self.frag_type: + find_flag = False + if self.precedence and tmp.get("aclPrecedence") != self.precedence: + find_flag = False + if self.tos and tmp.get("aclTos") != self.tos: + find_flag = False + if self.dscp and tmp.get("aclDscp") != self.dscp: + find_flag = False + if self.icmp_name and tmp.get("aclIcmpName") != self.icmp_name: + find_flag = False + if self.icmp_type and tmp.get("aclIcmpType") != self.icmp_type: + find_flag = False + if self.icmp_code and tmp.get("aclIcmpCode") != self.icmp_code: + find_flag = False + if tmp.get("aclTtlExpired").lower() != str(self.ttl_expired).lower(): + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.syn_flag and tmp.get("aclSynFlag") != self.syn_flag: + find_flag = False + if self.tcp_flag_mask and tmp.get("aclTcpFlagMask") != self.tcp_flag_mask: + find_flag = False + if self.protocol == "tcp" and \ + tmp.get("aclEstablished").lower() != str(self.established).lower(): + find_flag = False + if self.time_range and tmp.get("aclTimeName") != self.time_range: + find_flag = False + if self.rule_description and tmp.get("aclRuleDescription") != self.rule_description: + find_flag = False + if self.igmp_type and tmp.get("aclIgmpType") != self.igmp_type_num: + find_flag = False + if tmp.get("aclLogFlag").lower() != str(self.log_flag).lower(): + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_advance_rule_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + if self.acl_num: + self.proposed["acl_num"] = self.acl_num + if self.acl_step: + self.proposed["acl_step"] = self.acl_step + if self.acl_description: + self.proposed["acl_description"] = self.acl_description + if self.rule_name: + self.proposed["rule_name"] = self.rule_name + if self.rule_id: + self.proposed["rule_id"] = self.rule_id + if self.rule_action: + self.proposed["rule_action"] = self.rule_action + if self.protocol: + self.proposed["protocol"] = self.protocol + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.src_mask: + self.proposed["src_mask"] = self.src_mask + if self.src_pool_name: + self.proposed["src_pool_name"] = self.src_pool_name + if self.dest_ip: + self.proposed["dest_ip"] = self.dest_ip + if self.dest_mask: + self.proposed["dest_mask"] = self.dest_mask + if self.dest_pool_name: + self.proposed["dest_pool_name"] = self.dest_pool_name + if self.src_port_op: + self.proposed["src_port_op"] = self.src_port_op + if self.src_port_begin: + self.proposed["src_port_begin"] = self.src_port_begin + if self.src_port_end: + self.proposed["src_port_end"] = self.src_port_end + if self.src_port_pool_name: + self.proposed["src_port_pool_name"] = self.src_port_pool_name + if self.dest_port_op: + self.proposed["dest_port_op"] = self.dest_port_op + if self.dest_port_begin: + self.proposed["dest_port_begin"] = self.dest_port_begin + if self.dest_port_end: + self.proposed["dest_port_end"] = self.dest_port_end + if self.dest_port_pool_name: + self.proposed["dest_port_pool_name"] = self.dest_port_pool_name + if self.frag_type: + self.proposed["frag_type"] = self.frag_type + if self.precedence: + self.proposed["precedence"] = self.precedence + if self.tos: + self.proposed["tos"] = self.tos + if self.dscp: + self.proposed["dscp"] = self.dscp + if self.icmp_name: + self.proposed["icmp_name"] = self.icmp_name + if self.icmp_type: + self.proposed["icmp_type"] = self.icmp_type + if self.icmp_code: + self.proposed["icmp_code"] = self.icmp_code + if self.ttl_expired: + self.proposed["ttl_expired"] = self.ttl_expired + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.syn_flag: + self.proposed["syn_flag"] = self.syn_flag + if self.tcp_flag_mask: + self.proposed["tcp_flag_mask"] = self.tcp_flag_mask + self.proposed["established"] = self.established + if self.time_range: + self.proposed["time_range"] = self.time_range + if self.rule_description: + self.proposed["rule_description"] = self.rule_description + if self.igmp_type: + self.proposed["igmp_type"] = self.igmp_type + self.proposed["log_flag"] = self.log_flag + + def get_existing(self): + """ Get existing state """ + + self.existing["acl_info"] = self.cur_acl_cfg["acl_info"] + self.existing["adv_rule_info"] = self.cur_advance_rule_cfg[ + "adv_rule_info"] + + def get_end_state(self): + """ Get end state """ + + self.check_acl_args() + self.end_state["acl_info"] = self.cur_acl_cfg["acl_info"] + + self.check_advance_rule_args() + self.end_state["adv_rule_info"] = self.cur_advance_rule_cfg[ + "adv_rule_info"] + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def merge_acl(self): + """ Merge acl operation """ + + conf_str = CE_MERGE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_MERGE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl failed.') + + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + if self.acl_type and not self.acl_num: + cmd = "acl name %s %s" % (self.acl_name, self.acl_type.lower()) + elif self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + elif not self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + self.updates_cmd.append(cmd) + + if self.acl_description: + cmd = "description %s" % self.acl_description + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "step %s" % self.acl_step + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_acl(self): + """ Delete acl operation """ + + conf_str = CE_DELETE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_DELETE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl failed.') + + if self.acl_description: + cmd = "undo description" + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "undo step" + self.updates_cmd.append(cmd) + + if self.acl_name.isdigit(): + cmd = "undo acl number %s" % self.acl_name + else: + cmd = "undo acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_adv_rule(self): + """ Merge advance rule operation """ + + conf_str = CE_MERGE_ACL_ADVANCE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.protocol: + conf_str += "%s" % self.protocol_num + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.src_pool_name: + conf_str += "%s" % self.src_pool_name + if self.dest_ip: + conf_str += "%s" % self.dest_ip + if self.dest_wild: + conf_str += "%s" % self.dest_wild + if self.dest_pool_name: + conf_str += "%s" % self.dest_pool_name + if self.src_port_op: + conf_str += "%s" % self.src_port_op + if self.src_port_begin: + conf_str += "%s" % self.src_port_begin + if self.src_port_end: + conf_str += "%s" % self.src_port_end + if self.src_port_pool_name: + conf_str += "%s" % self.src_port_pool_name + if self.dest_port_op: + conf_str += "%s" % self.dest_port_op + if self.dest_port_begin: + conf_str += "%s" % self.dest_port_begin + if self.dest_port_end: + conf_str += "%s" % self.dest_port_end + if self.dest_port_pool_name: + conf_str += "%s" % self.dest_port_pool_name + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.precedence: + conf_str += "%s" % self.precedence + if self.tos: + conf_str += "%s" % self.tos + if self.dscp: + conf_str += "%s" % self.dscp + if self.icmp_name: + conf_str += "%s" % self.icmp_name + if self.icmp_type: + conf_str += "%s" % self.icmp_type + if self.icmp_code: + conf_str += "%s" % self.icmp_code + conf_str += "%s" % str(self.ttl_expired).lower() + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.syn_flag: + conf_str += "%s" % self.syn_flag + if self.tcp_flag_mask: + conf_str += "%s" % self.tcp_flag_mask + if self.protocol == "tcp": + conf_str += "%s" % str(self.established).lower() + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + if self.igmp_type: + conf_str += "%s" % self.igmp_type_num + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_MERGE_ACL_ADVANCE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl base rule failed.') + + if self.rule_action and self.protocol: + cmd = "rule" + if self.rule_id: + cmd += " %s" % self.rule_id + cmd += " %s" % self.rule_action + cmd += " %s" % self.protocol + if self.dscp: + cmd += " dscp %s" % self.dscp + if self.tos: + cmd += " tos %s" % self.tos + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.src_pool_name: + cmd += " source-pool %s" % self.src_pool_name + if self.src_port_op: + cmd += " source-port" + if self.src_port_op == "lt": + cmd += " lt %s" % self.src_port_end + elif self.src_port_op == "eq": + cmd += " eq %s" % self.src_port_begin + elif self.src_port_op == "gt": + cmd += " gt %s" % self.src_port_begin + elif self.src_port_op == "range": + cmd += " range %s %s" % (self.src_port_begin, + self.src_port_end) + if self.src_port_pool_name: + cmd += " source-port-pool %s" % self.src_port_pool_name + if self.dest_ip and self.dest_wild: + cmd += " destination %s %s" % (self.dest_ip, self.dest_wild) + if self.dest_pool_name: + cmd += " destination-pool %s" % self.dest_pool_name + if self.dest_port_op: + cmd += " destination-port" + if self.dest_port_op == "lt": + cmd += " lt %s" % self.dest_port_end + elif self.dest_port_op == "eq": + cmd += " eq %s" % self.dest_port_begin + elif self.dest_port_op == "gt": + cmd += " gt %s" % self.dest_port_begin + elif self.dest_port_op == "range": + cmd += " range %s %s" % (self.dest_port_begin, + self.dest_port_end) + if self.dest_port_pool_name: + cmd += " destination-port-pool %s" % self.dest_port_pool_name + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.precedence: + cmd += " precedence %s" % self.precedence_name[self.precedence] + + if self.protocol == "icmp": + if self.icmp_name: + cmd += " icmp-type %s" % self.icmp_name + elif self.icmp_type and self.icmp_code: + cmd += " icmp-type %s %s" % (self.icmp_type, self.icmp_code) + elif self.icmp_type: + cmd += " icmp-type %s" % self.icmp_type + if self.protocol == "tcp": + if self.syn_flag: + cmd += " tcp-flag %s" % self.syn_flag + if self.tcp_flag_mask: + cmd += " mask %s" % self.tcp_flag_mask + if self.established: + cmd += " established" + if self.protocol == "igmp": + if self.igmp_type: + cmd += " igmp-type %s" % self.igmp_type + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.ttl_expired: + cmd += " ttl-expired" + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + if self.rule_description: + cmd = "rule %s description %s" % ( + self.rule_id, self.rule_description) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_adv_rule(self): + """ Delete advance rule operation """ + + conf_str = CE_DELETE_ACL_ADVANCE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.protocol: + conf_str += "%s" % self.protocol_num + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.src_pool_name: + conf_str += "%s" % self.src_pool_name + if self.dest_ip: + conf_str += "%s" % self.dest_ip + if self.dest_wild: + conf_str += "%s" % self.dest_wild + if self.dest_pool_name: + conf_str += "%s" % self.dest_pool_name + if self.src_port_op: + conf_str += "%s" % self.src_port_op + if self.src_port_begin: + conf_str += "%s" % self.src_port_begin + if self.src_port_end: + conf_str += "%s" % self.src_port_end + if self.src_port_pool_name: + conf_str += "%s" % self.src_port_pool_name + if self.dest_port_op: + conf_str += "%s" % self.dest_port_op + if self.dest_port_begin: + conf_str += "%s" % self.dest_port_begin + if self.dest_port_end: + conf_str += "%s" % self.dest_port_end + if self.dest_port_pool_name: + conf_str += "%s" % self.dest_port_pool_name + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.precedence: + conf_str += "%s" % self.precedence + if self.tos: + conf_str += "%s" % self.tos + if self.dscp: + conf_str += "%s" % self.dscp + if self.icmp_name: + conf_str += "%s" % self.icmp_name + if self.icmp_type: + conf_str += "%s" % self.icmp_type + if self.icmp_code: + conf_str += "%s" % self.icmp_code + conf_str += "%s" % str(self.ttl_expired).lower() + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.syn_flag: + conf_str += "%s" % self.syn_flag + if self.tcp_flag_mask: + conf_str += "%s" % self.tcp_flag_mask + if self.protocol == "tcp": + conf_str += "%s" % str(self.established).lower() + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + if self.igmp_type: + conf_str += "%s" % self.igmp_type + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_DELETE_ACL_ADVANCE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl base rule failed.') + + if self.rule_description: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s description" % self.rule_id + self.updates_cmd.append(cmd) + + if self.rule_id: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s" % self.rule_id + self.updates_cmd.append(cmd) + elif self.rule_action and self.protocol: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule" + cmd += " %s" % self.rule_action + cmd += " %s" % self.protocol + if self.dscp: + cmd += " dscp %s" % self.dscp + if self.tos: + cmd += " tos %s" % self.tos + if self.source_ip and self.src_mask: + cmd += " source %s %s" % (self.source_ip, self.src_mask) + if self.src_pool_name: + cmd += " source-pool %s" % self.src_pool_name + if self.src_port_op: + cmd += " source-port" + if self.src_port_op == "lt": + cmd += " lt %s" % self.src_port_end + elif self.src_port_op == "eq": + cmd += " eq %s" % self.src_port_begin + elif self.src_port_op == "gt": + cmd += " gt %s" % self.src_port_begin + elif self.src_port_op == "range": + cmd += " range %s %s" % (self.src_port_begin, + self.src_port_end) + if self.src_port_pool_name: + cmd += " source-port-pool %s" % self.src_port_pool_name + if self.dest_ip and self.dest_mask: + cmd += " destination %s %s" % (self.dest_ip, self.dest_mask) + if self.dest_pool_name: + cmd += " destination-pool %s" % self.dest_pool_name + if self.dest_port_op: + cmd += " destination-port" + if self.dest_port_op == "lt": + cmd += " lt %s" % self.dest_port_end + elif self.dest_port_op == "eq": + cmd += " eq %s" % self.dest_port_begin + elif self.dest_port_op == "gt": + cmd += " gt %s" % self.dest_port_begin + elif self.dest_port_op == "range": + cmd += " range %s %s" % (self.dest_port_begin, + self.dest_port_end) + if self.dest_port_pool_name: + cmd += " destination-port-pool %s" % self.dest_port_pool_name + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.precedence: + cmd += " precedence %s" % self.precedence_name[self.precedence] + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.ttl_expired: + cmd += " ttl-expired" + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_acl_args() + self.check_advance_rule_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_acl_cfg["need_cfg"]: + self.merge_acl() + if self.cur_advance_rule_cfg["need_cfg"]: + self.merge_adv_rule() + + elif self.state == "absent": + if self.cur_advance_rule_cfg["need_cfg"]: + self.delete_adv_rule() + + elif self.state == "delete_acl": + if self.cur_acl_cfg["need_cfg"]: + self.delete_acl() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent', + 'delete_acl'], default='present'), + acl_name=dict(type='str', required=True), + acl_num=dict(type='str'), + acl_step=dict(type='str'), + acl_description=dict(type='str'), + rule_name=dict(type='str'), + rule_id=dict(type='str'), + rule_action=dict(choices=['permit', 'deny']), + protocol=dict(choices=['ip', 'icmp', 'igmp', + 'ipinip', 'tcp', 'udp', 'gre', 'ospf']), + source_ip=dict(type='str'), + src_mask=dict(type='str'), + src_pool_name=dict(type='str'), + dest_ip=dict(type='str'), + dest_mask=dict(type='str'), + dest_pool_name=dict(type='str'), + src_port_op=dict(choices=['lt', 'eq', 'gt', 'range']), + src_port_begin=dict(type='str'), + src_port_end=dict(type='str'), + src_port_pool_name=dict(type='str'), + dest_port_op=dict(choices=['lt', 'eq', 'gt', 'range']), + dest_port_begin=dict(type='str'), + dest_port_end=dict(type='str'), + dest_port_pool_name=dict(type='str'), + frag_type=dict(choices=['fragment', 'clear_fragment']), + precedence=dict(type='str'), + tos=dict(type='str'), + dscp=dict(type='str'), + icmp_name=dict(choices=['unconfiged', 'echo', 'echo-reply', 'fragmentneed-DFset', 'host-redirect', + 'host-tos-redirect', 'host-unreachable', 'information-reply', 'information-request', + 'net-redirect', 'net-tos-redirect', 'net-unreachable', 'parameter-problem', + 'port-unreachable', 'protocol-unreachable', 'reassembly-timeout', 'source-quench', + 'source-route-failed', 'timestamp-reply', 'timestamp-request', 'ttl-exceeded', + 'address-mask-reply', 'address-mask-request', 'custom']), + icmp_type=dict(type='str'), + icmp_code=dict(type='str'), + ttl_expired=dict(required=False, default=False, type='bool'), + vrf_name=dict(type='str'), + syn_flag=dict(type='str'), + tcp_flag_mask=dict(type='str'), + established=dict(required=False, default=False, type='bool'), + time_range=dict(type='str'), + rule_description=dict(type='str'), + igmp_type=dict(choices=['host-query', 'mrouter-adver', 'mrouter-solic', 'mrouter-termi', 'mtrace-resp', + 'mtrace-route', 'v1host-report', 'v2host-report', 'v2leave-group', 'v3host-report']), + log_flag=dict(required=False, default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = AdvanceAcl(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_interface.py new file mode 100644 index 00000000..89f549e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_acl_interface.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl_interface +short_description: Manages applying ACLs to interfaces on HUAWEI CloudEngine switches. +description: + - Manages applying ACLs to interfaces on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 2000 to 4999. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + interface: + description: + - Interface name. + Only support interface full name, such as "40GE2/0/1". + required: true + direction: + description: + - Direction ACL to be applied in on the interface. + required: true + choices: ['inbound', 'outbound'] + state: + description: + - Determines whether the config should be present or not on the device. + required: false + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine acl interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Apply acl to interface" + community.network.ce_acl_interface: + state: present + acl_name: 2000 + interface: 40GE1/0/1 + direction: outbound + provider: "{{ cli }}" + + - name: "Undo acl from interface" + community.network.ce_acl_interface: + state: absent + acl_name: 2000 + interface: 40GE1/0/1 + direction: outbound + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "2000", + "direction": "outbound", + "interface": "40GE2/0/1", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"acl interface": "traffic-filter acl lb inbound"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"acl interface": ["traffic-filter acl lb inbound", "traffic-filter acl 2000 outbound"]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface 40ge2/0/1", + "traffic-filter acl 2000 outbound"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_config, exec_command, cli_err_msg +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class AclInterface(object): + """ Manages acl interface configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + self.cur_cfg["acl interface"] = [] + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] + self.interface = self.module.params['interface'] + self.direction = self.module.params['direction'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check args """ + + if self.acl_name: + if self.acl_name.isdigit(): + if int(self.acl_name) < 2000 or int(self.acl_name) > 4999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000 - 4999].') + else: + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.interface: + cmd = "display current-configuration | ignore-case section include interface %s" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + if not tmp: + self.module.fail_json( + msg='Error: The interface %s is not in the device.' % self.interface) + + def get_proposed(self): + """ Get proposed config """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + + if self.interface: + self.proposed["interface"] = self.interface + + if self.direction: + self.proposed["direction"] = self.direction + + def get_existing(self): + """ Get existing config """ + + cmd = "display current-configuration | ignore-case section include interface %s | include traffic-filter" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + end = [] + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + for item in tmp: + end.append(item.strip()) + self.cur_cfg["acl interface"] = end + self.existing["acl interface"] = end + + def get_end_state(self): + """ Get config end state """ + + cmd = "display current-configuration | ignore-case section include interface %s | include traffic-filter" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + end = [] + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + for item in tmp: + end.append(item.strip()) + self.end_state["acl interface"] = end + + def load_config(self, config): + """Sends configuration commands to the remote device""" + + rc, out, err = exec_command(self.module, 'mmi-mode enable') + if rc != 0: + self.module.fail_json(msg='unable to set mmi-mode enable', output=err) + rc, out, err = exec_command(self.module, 'system-view immediately') + if rc != 0: + self.module.fail_json(msg='unable to enter system-view', output=err) + + for cmd in config: + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + if "unrecognized command found" in err.lower(): + self.module.fail_json(msg="Error:The parameter is incorrect or the interface does not support this parameter.") + else: + self.module.fail_json(msg=cli_err_msg(cmd.strip(), err)) + + exec_command(self.module, 'return') + + def cli_load_config(self, commands): + """ Cli method to load config """ + + if not self.module.check_mode: + self.load_config(commands) + + def work(self): + """ Work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + cmds = list() + tmp_cmd = "traffic-filter acl %s %s" % (self.acl_name, self.direction) + undo_tmp_cmd = "undo traffic-filter acl %s %s" % ( + self.acl_name, self.direction) + + if self.state == "present": + if tmp_cmd not in self.cur_cfg["acl interface"]: + interface_cmd = "interface %s" % self.interface.lower() + cmds.append(interface_cmd) + cmds.append(tmp_cmd) + + self.cli_load_config(cmds) + + self.changed = True + self.updates_cmd.append(interface_cmd) + self.updates_cmd.append(tmp_cmd) + + else: + if tmp_cmd in self.cur_cfg["acl interface"]: + interface_cmd = "interface %s" % self.interface + cmds.append(interface_cmd) + cmds.append(undo_tmp_cmd) + self.cli_load_config(cmds) + + self.changed = True + self.updates_cmd.append(interface_cmd) + self.updates_cmd.append(undo_tmp_cmd) + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_name=dict(type='str', required=True), + interface=dict(type='str', required=True), + direction=dict(choices=['inbound', 'outbound'], required=True) + ) + + argument_spec.update(ce_argument_spec) + module = AclInterface(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_global.py new file mode 100644 index 00000000..7e3b2695 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_global.py @@ -0,0 +1,554 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_global +short_description: Manages BFD global configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD global configuration on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bfd_enable: + description: + - Enables the global Bidirectional Forwarding Detection (BFD) function. + choices: ['enable', 'disable'] + default_ip: + description: + - Specifies the default multicast IP address. + The value ranges from 224.0.0.107 to 224.0.0.250. + tos_exp_dynamic: + description: + - Indicates the priority of BFD control packets for dynamic BFD sessions. + The value is an integer ranging from 0 to 7. + The default priority is 7, which is the highest priority of BFD control packets. + tos_exp_static: + description: + - Indicates the priority of BFD control packets for static BFD sessions. + The value is an integer ranging from 0 to 7. + The default priority is 7, which is the highest priority of BFD control packets. + damp_init_wait_time: + description: + - Specifies an initial flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 2000. + damp_max_wait_time: + description: + - Specifies a maximum flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 15000. + damp_second_wait_time: + description: + - Specifies a secondary flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 5000. + delay_up_time: + description: + - Specifies the delay before a BFD session becomes Up. + The value is an integer ranging from 1 to 600, in seconds. + The default value is 0, indicating that a BFD session immediately becomes Up. + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Bfd global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Enable the global BFD function + community.network.ce_bfd_global: + bfd_enable: enable + provider: '{{ cli }}' + + - name: Set the default multicast IP address to 224.0.0.150 + community.network.ce_bfd_global: + bfd_enable: enable + default_ip: 224.0.0.150 + state: present + provider: '{{ cli }}' + + - name: Set the priority of BFD control packets for dynamic and static BFD sessions + community.network.ce_bfd_global: + bfd_enable: enable + tos_exp_dynamic: 5 + tos_exp_static: 6 + state: present + provider: '{{ cli }}' + + - name: Disable the global BFD function + community.network.ce_bfd_global: + bfd_enable: disable + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "bfd_enalbe": "enable", + "damp_init_wait_time": null, + "damp_max_wait_time": null, + "damp_second_wait_time": null, + "default_ip": null, + "delayUpTimer": null, + "state": "present", + "tos_exp_dynamic": null, + "tos_exp_static": null + } +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: { + "global": { + "bfdEnable": "false", + "dampInitWaitTime": "2000", + "dampMaxWaitTime": "12000", + "dampSecondWaitTime": "5000", + "defaultIp": "224.0.0.184", + "delayUpTimer": null, + "tosExp": "7", + "tosExpStatic": "7" + } + } +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: { + "global": { + "bfdEnable": "true", + "dampInitWaitTime": "2000", + "dampMaxWaitTime": "12000", + "dampSecondWaitTime": "5000", + "defaultIp": "224.0.0.184", + "delayUpTimer": null, + "tosExp": "7", + "tosExpStatic": "7" + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ "bfd" ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +CE_NC_GET_BFD = """ + + + %s + + +""" + +CE_NC_GET_BFD_GLB = """ + + + + + + + + + + +""" + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +class BfdGlobal(object): + """Manages BFD Global""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.bfd_enable = self.module.params['bfd_enable'] + self.default_ip = self.module.params['default_ip'] + self.tos_exp_dynamic = self.module.params['tos_exp_dynamic'] + self.tos_exp_static = self.module.params['tos_exp_static'] + self.damp_init_wait_time = self.module.params['damp_init_wait_time'] + self.damp_max_wait_time = self.module.params['damp_max_wait_time'] + self.damp_second_wait_time = self.module.params['damp_second_wait_time'] + self.delay_up_time = self.module.params['delay_up_time'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [('damp_init_wait_time', 'damp_max_wait_time', 'damp_second_wait_time')] + self.module = AnsibleModule(argument_spec=self.spec, + required_together=required_together, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + conf_str = CE_NC_GET_BFD % CE_NC_GET_BFD_GLB + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + if attr.text is not None: + bfd_dict["global"][attr.tag] = attr.text + + return bfd_dict + + def config_global(self): + """configures bfd global params""" + + xml_str = "" + damp_chg = False + + # bfd_enable + if self.bfd_enable: + if bool(self.bfd_dict["global"].get("bfdEnable", "false") == "true") != bool(self.bfd_enable == "enable"): + if self.bfd_enable == "enable": + xml_str = "true" + self.updates_cmd.append("bfd") + else: + xml_str = "false" + self.updates_cmd.append("undo bfd") + + # get bfd end state + bfd_state = "disable" + if self.bfd_enable: + bfd_state = self.bfd_enable + elif self.bfd_dict["global"].get("bfdEnable", "false") == "true": + bfd_state = "enable" + + # default_ip + if self.default_ip: + if bfd_state == "enable": + if self.state == "present" and self.default_ip != self.bfd_dict["global"].get("defaultIp"): + xml_str += "%s" % self.default_ip + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("default-ip-address %s" % self.default_ip) + elif self.state == "absent" and self.default_ip == self.bfd_dict["global"].get("defaultIp"): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo default-ip-address") + + # tos_exp_dynamic + if self.tos_exp_dynamic is not None: + if bfd_state == "enable": + if self.state == "present" and self.tos_exp_dynamic != int(self.bfd_dict["global"].get("tosExp", "7")): + xml_str += "%s" % self.tos_exp_dynamic + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("tos-exp %s dynamic" % self.tos_exp_dynamic) + elif self.state == "absent" and self.tos_exp_dynamic == int(self.bfd_dict["global"].get("tosExp", "7")): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo tos-exp dynamic") + + # tos_exp_static + if self.tos_exp_static is not None: + if bfd_state == "enable": + if self.state == "present" \ + and self.tos_exp_static != int(self.bfd_dict["global"].get("tosExpStatic", "7")): + xml_str += "%s" % self.tos_exp_static + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("tos-exp %s static" % self.tos_exp_static) + elif self.state == "absent" \ + and self.tos_exp_static == int(self.bfd_dict["global"].get("tosExpStatic", "7")): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo tos-exp static") + + # delay_up_time + if self.delay_up_time is not None: + if bfd_state == "enable": + delay_time = self.bfd_dict["global"].get("delayUpTimer", "0") + if not delay_time or not delay_time.isdigit(): + delay_time = "0" + if self.state == "present" \ + and self.delay_up_time != int(delay_time): + xml_str += "%s" % self.delay_up_time + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("delay-up %s" % self.delay_up_time) + elif self.state == "absent" \ + and self.delay_up_time == int(delay_time): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo delay-up") + + # damp_init_wait_time damp_max_wait_time damp_second_wait_time + if self.damp_init_wait_time is not None and self.damp_second_wait_time is not None \ + and self.damp_second_wait_time is not None: + if bfd_state == "enable": + if self.state == "present": + if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): + xml_str += "%s" % self.damp_max_wait_time + damp_chg = True + if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): + xml_str += "%s" % self.damp_init_wait_time + damp_chg = True + if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): + xml_str += "%s" % self.damp_second_wait_time + damp_chg = True + if damp_chg: + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("dampening timer-interval maximum %s initial %s secondary %s" % ( + self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) + else: + damp_chg = True + if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): + damp_chg = False + if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): + damp_chg = False + if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): + damp_chg = False + + if damp_chg: + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo dampening timer-interval maximum %s initial %s secondary %s" % ( + self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) + if xml_str: + return '' + xml_str + '' + else: + return "" + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check default_ip + if self.default_ip: + if not check_default_ip(self.default_ip): + self.module.fail_json(msg="Error: Default ip is invalid.") + + # check tos_exp_dynamic + if self.tos_exp_dynamic is not None: + if self.tos_exp_dynamic < 0 or self.tos_exp_dynamic > 7: + self.module.fail_json(msg="Error: Session tos_exp_dynamic is not ranges from 0 to 7.") + + # check tos_exp_static + if self.tos_exp_static is not None: + if self.tos_exp_static < 0 or self.tos_exp_static > 7: + self.module.fail_json(msg="Error: Session tos_exp_static is not ranges from 0 to 7.") + + # check damp_init_wait_time + if self.damp_init_wait_time is not None: + if self.damp_init_wait_time < 1 or self.damp_init_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_init_wait_time is not ranges from 1 to 3600000.") + + # check damp_max_wait_time + if self.damp_max_wait_time is not None: + if self.damp_max_wait_time < 1 or self.damp_max_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_max_wait_time is not ranges from 1 to 3600000.") + + # check damp_second_wait_time + if self.damp_second_wait_time is not None: + if self.damp_second_wait_time < 1 or self.damp_second_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_second_wait_time is not ranges from 1 to 3600000.") + + # check delay_up_time + if self.delay_up_time is not None: + if self.delay_up_time < 1 or self.delay_up_time > 600: + self.module.fail_json(msg="Error: Session delay_up_time is not ranges from 1 to 600.") + + def get_proposed(self): + """get proposed info""" + + self.proposed["bfd_enalbe"] = self.bfd_enable + self.proposed["default_ip"] = self.default_ip + self.proposed["tos_exp_dynamic"] = self.tos_exp_dynamic + self.proposed["tos_exp_static"] = self.tos_exp_static + self.proposed["damp_init_wait_time"] = self.damp_init_wait_time + self.proposed["damp_max_wait_time"] = self.damp_max_wait_time + self.proposed["damp_second_wait_time"] = self.damp_second_wait_time + self.proposed["delay_up_time"] = self.delay_up_time + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["global"] = self.bfd_dict.get("global") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["global"] = bfd_dict.get("global") + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = self.config_global() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bfd_enable=dict(required=False, type='str', choices=['enable', 'disable']), + default_ip=dict(required=False, type='str'), + tos_exp_dynamic=dict(required=False, type='int'), + tos_exp_static=dict(required=False, type='int'), + damp_init_wait_time=dict(required=False, type='int'), + damp_max_wait_time=dict(required=False, type='int'), + damp_second_wait_time=dict(required=False, type='int'), + delay_up_time=dict(required=False, type='int'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = BfdGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_session.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_session.py new file mode 100644 index 00000000..ae175e38 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_session.py @@ -0,0 +1,654 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_session +short_description: Manages BFD session configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD session configuration, creates a BFD session or deletes a specified BFD session + on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + session_name: + description: + - Specifies the name of a BFD session. + The value is a string of 1 to 15 case-sensitive characters without spaces. + required: true + create_type: + description: + - BFD session creation mode, the currently created BFD session + only supports static or static auto-negotiation mode. + choices: ['static', 'auto'] + default: static + addr_type: + description: + - Specifies the peer IP address type. + choices: ['ipv4'] + out_if_name: + description: + - Specifies the type and number of the interface bound to the BFD session. + dest_addr: + description: + - Specifies the peer IP address bound to the BFD session. + src_addr: + description: + - Indicates the source IP address carried in BFD packets. + local_discr: + description: + - The BFD session local identifier does not need to be configured when the mode is auto. + remote_discr: + description: + - The BFD session remote identifier does not need to be configured when the mode is auto. + vrf_name: + description: + - Specifies the name of a Virtual Private Network (VPN) instance that is bound to a BFD session. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value _public_ is reserved and cannot be used as the VPN instance name. + use_default_ip: + description: + - Indicates the default multicast IP address that is bound to a BFD session. + By default, BFD uses the multicast IP address 224.0.0.184. + You can set the multicast IP address by running the default-ip-address command. + The value is a bool type. + type: bool + default: 'no' + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Bfd session module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Configuring Single-hop BFD for Detecting Faults on a Layer 2 Link + community.network.ce_bfd_session: + session_name: bfd_l2link + use_default_ip: true + out_if_name: 10GE1/0/1 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' + + - name: Configuring Single-Hop BFD on a VLANIF Interface + community.network.ce_bfd_session: + session_name: bfd_vlanif + dest_addr: 10.1.1.6 + out_if_name: Vlanif100 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' + + - name: Configuring Multi-Hop BFD + community.network.ce_bfd_session: + session_name: bfd_multi_hop + dest_addr: 10.1.1.1 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "addr_type": null, + "create_type": null, + "dest_addr": null, + "out_if_name": "10GE1/0/1", + "session_name": "bfd_l2link", + "src_addr": null, + "state": "present", + "use_default_ip": true, + "vrf_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +CE_NC_GET_BFD = """ + + + + + + + + + %s + + + + + + + + + + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class BfdSession(object): + """Manages BFD Session""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.session_name = self.module.params['session_name'] + self.create_type = self.module.params['create_type'] + self.addr_type = self.module.params['addr_type'] + self.out_if_name = self.module.params['out_if_name'] + self.dest_addr = self.module.params['dest_addr'] + self.src_addr = self.module.params['src_addr'] + self.vrf_name = self.module.params['vrf_name'] + self.use_default_ip = self.module.params['use_default_ip'] + self.state = self.module.params['state'] + self.local_discr = self.module.params['local_discr'] + self.remote_discr = self.module.params['remote_discr'] + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + mutually_exclusive = [('use_default_ip', 'dest_addr')] + self.module = AnsibleModule(argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + bfd_dict["session"] = dict() + conf_str = CE_NC_GET_BFD % self.session_name + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + bfd_dict["global"][attr.tag] = attr.text + + # get bfd session info + sess = root.find("bfd/bfdCfgSessions/bfdCfgSession") + if sess: + for attr in sess: + bfd_dict["session"][attr.tag] = attr.text + + return bfd_dict + + def is_session_match(self): + """is bfd session match""" + + if not self.bfd_dict["session"] or not self.session_name: + return False + + session = self.bfd_dict["session"] + if self.session_name != session.get("sessName", ""): + return False + + if self.create_type and self.create_type.upper() not in session.get("createType", "").upper(): + return False + + if self.addr_type and self.addr_type != session.get("addrType").lower(): + return False + + if self.dest_addr and self.dest_addr != session.get("destAddr"): + return False + + if self.src_addr and self.src_addr != session.get("srcAddr"): + return False + + if self.out_if_name: + if not session.get("outIfName"): + return False + if self.out_if_name.replace(" ", "").lower() != session.get("outIfName").replace(" ", "").lower(): + return False + + if self.vrf_name and self.vrf_name != session.get("vrfName"): + return False + + if str(self.use_default_ip).lower() != session.get("useDefaultIp"): + return False + + if self.create_type == "static" and self.state == "present": + if str(self.local_discr).lower() != session.get("localDiscr", ""): + return False + if str(self.remote_discr).lower() != session.get("remoteDiscr", ""): + return False + + return True + + def config_session(self): + """configures bfd session""" + + xml_str = "" + cmd_list = list() + discr = list() + + if not self.session_name: + return xml_str + + if self.bfd_dict["global"].get("bfdEnable", "false") != "true": + self.module.fail_json(msg="Error: Please enable BFD globally first.") + + xml_str = "%s" % self.session_name + cmd_session = "bfd %s" % self.session_name + + if self.state == "present": + if not self.bfd_dict["session"]: + # Parameter check + if not self.dest_addr and not self.use_default_ip: + self.module.fail_json( + msg="Error: dest_addr or use_default_ip must be set when bfd session is creating.") + + # Creates a BFD session + if self.create_type == "auto": + xml_str += "SESS_%s" % self.create_type.upper() + else: + xml_str += "SESS_STATIC" + xml_str += "IP" + cmd_session += " bind" + if self.addr_type: + xml_str += "%s" % self.addr_type.upper() + else: + xml_str += "IPV4" + if self.dest_addr: + xml_str += "%s" % self.dest_addr + cmd_session += " peer-%s %s" % ("ipv6" if self.addr_type == "ipv6" else "ip", self.dest_addr) + if self.use_default_ip: + xml_str += "%s" % str(self.use_default_ip).lower() + cmd_session += " peer-ip default-ip" + if self.vrf_name: + xml_str += "%s" % self.vrf_name + cmd_session += " vpn-instance %s" % self.vrf_name + if self.out_if_name: + xml_str += "%s" % self.out_if_name + cmd_session += " interface %s" % self.out_if_name.lower() + if self.src_addr: + xml_str += "%s" % self.src_addr + cmd_session += " source-%s %s" % ("ipv6" if self.addr_type == "ipv6" else "ip", self.src_addr) + + if self.create_type == "auto": + cmd_session += " auto" + else: + xml_str += "%s" % self.local_discr + discr.append("discriminator local %s" % self.local_discr) + xml_str += "%s" % self.remote_discr + discr.append("discriminator remote %s" % self.remote_discr) + + elif not self.is_session_match(): + # Bfd session is not match + self.module.fail_json(msg="Error: The specified BFD configuration view has been created.") + else: + pass + else: # absent + if not self.bfd_dict["session"]: + self.module.fail_json(msg="Error: BFD session is not exist.") + if not self.is_session_match(): + self.module.fail_json(msg="Error: BFD session parameter is invalid.") + + if self.state == "present": + if xml_str.endswith(""): + # no config update + return "" + else: + cmd_list.insert(0, cmd_session) + cmd_list.extend(discr) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + else: # absent + cmd_list.append("undo " + cmd_session) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check session_name + if not self.session_name: + self.module.fail_json(msg="Error: Missing required arguments: session_name.") + + if self.session_name: + if len(self.session_name) < 1 or len(self.session_name) > 15: + self.module.fail_json(msg="Error: Session name is invalid.") + + # check local_discr + # check remote_discr + + if self.local_discr: + if self.local_discr < 1 or self.local_discr > 16384: + self.module.fail_json(msg="Error: Session local_discr is not ranges from 1 to 16384.") + if self.remote_discr: + if self.remote_discr < 1 or self.remote_discr > 4294967295: + self.module.fail_json(msg="Error: Session remote_discr is not ranges from 1 to 4294967295.") + + if self.state == "present" and self.create_type == "static": + if not self.local_discr: + self.module.fail_json(msg="Error: Missing required arguments: local_discr.") + if not self.remote_discr: + self.module.fail_json(msg="Error: Missing required arguments: remote_discr.") + + # check out_if_name + if self.out_if_name: + if not get_interface_type(self.out_if_name): + self.module.fail_json(msg="Error: Session out_if_name is invalid.") + + # check dest_addr + if self.dest_addr: + if not check_ip_addr(self.dest_addr): + self.module.fail_json(msg="Error: Session dest_addr is invalid.") + + # check src_addr + if self.src_addr: + if not check_ip_addr(self.src_addr): + self.module.fail_json(msg="Error: Session src_addr is invalid.") + + # check vrf_name + if self.vrf_name: + if not is_valid_ip_vpn(self.vrf_name): + self.module.fail_json(msg="Error: Session vrf_name is invalid.") + if not self.dest_addr: + self.module.fail_json(msg="Error: vrf_name and dest_addr must set at the same time.") + + # check use_default_ip + if self.use_default_ip and not self.out_if_name: + self.module.fail_json(msg="Error: use_default_ip and out_if_name must set at the same time.") + + def get_proposed(self): + """get proposed info""" + + # base config + self.proposed["session_name"] = self.session_name + self.proposed["create_type"] = self.create_type + self.proposed["addr_type"] = self.addr_type + self.proposed["out_if_name"] = self.out_if_name + self.proposed["dest_addr"] = self.dest_addr + self.proposed["src_addr"] = self.src_addr + self.proposed["vrf_name"] = self.vrf_name + self.proposed["use_default_ip"] = self.use_default_ip + self.proposed["state"] = self.state + self.proposed["local_discr"] = self.local_discr + self.proposed["remote_discr"] = self.remote_discr + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["session"] = self.bfd_dict.get("session") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["session"] = bfd_dict.get("session") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.session_name: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + session_name=dict(required=True, type='str'), + create_type=dict(required=False, default='static', type='str', choices=['static', 'auto']), + addr_type=dict(required=False, type='str', choices=['ipv4']), + out_if_name=dict(required=False, type='str'), + dest_addr=dict(required=False, type='str'), + src_addr=dict(required=False, type='str'), + vrf_name=dict(required=False, type='str'), + use_default_ip=dict(required=False, type='bool', default=False), + state=dict(required=False, default='present', choices=['present', 'absent']), + local_discr=dict(required=False, type='int'), + remote_discr=dict(required=False, type='int') + ) + + argument_spec.update(ce_argument_spec) + module = BfdSession(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_view.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_view.py new file mode 100644 index 00000000..9a170570 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bfd_view.py @@ -0,0 +1,561 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_view +short_description: Manages BFD session view configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD session view configuration on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + session_name: + description: + - Specifies the name of a BFD session. + The value is a string of 1 to 15 case-sensitive characters without spaces. + required: true + local_discr: + description: + - Specifies the local discriminator of a BFD session. + The value is an integer that ranges from 1 to 16384. + remote_discr: + description: + - Specifies the remote discriminator of a BFD session. + The value is an integer that ranges from 1 to 4294967295. + min_tx_interval: + description: + - Specifies the minimum interval for receiving BFD packets. + The value is an integer that ranges from 50 to 1000, in milliseconds. + min_rx_interval: + description: + - Specifies the minimum interval for sending BFD packets. + The value is an integer that ranges from 50 to 1000, in milliseconds. + detect_multi: + description: + - Specifies the local detection multiplier of a BFD session. + The value is an integer that ranges from 3 to 50. + wtr_interval: + description: + - Specifies the WTR time of a BFD session. + The value is an integer that ranges from 1 to 60, in minutes. + The default value is 0. + tos_exp: + description: + - Specifies a priority for BFD control packets. + The value is an integer ranging from 0 to 7. + The default value is 7, which is the highest priority. + admin_down: + description: + - Enables the BFD session to enter the AdminDown state. + By default, a BFD session is enabled. + The default value is bool type. + type: bool + default: 'no' + description: + description: + - Specifies the description of a BFD session. + The value is a string of 1 to 51 case-sensitive characters with spaces. + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +extends_documentation_fragment: +- community.network.ce + +''' + +EXAMPLES = ''' +- name: Bfd view module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Set the local discriminator of a BFD session to 80 and the remote discriminator to 800 + community.network.ce_bfd_view: + session_name: atob + local_discr: 80 + remote_discr: 800 + state: present + provider: '{{ cli }}' + + - name: Set the minimum interval for receiving BFD packets to 500 ms + community.network.ce_bfd_view: + session_name: atob + min_rx_interval: 500 + state: present + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "admin_down": false, + "description": null, + "detect_multi": null, + "local_discr": 80, + "min_rx_interval": null, + "min_tx_interval": null, + "remote_discr": 800, + "session_name": "atob", + "state": "present", + "tos_exp": null, + "wtr_interval": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": { + "adminDown": "false", + "createType": "SESS_STATIC", + "description": null, + "detectMulti": "3", + "localDiscr": null, + "minRxInt": null, + "minTxInt": null, + "remoteDiscr": null, + "sessName": "atob", + "tosExp": null, + "wtrTimerInt": null + } + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "adminDown": "false", + "createType": "SESS_STATIC", + "description": null, + "detectMulti": "3", + "localDiscr": "80", + "minRxInt": null, + "minTxInt": null, + "remoteDiscr": "800", + "sessName": "atob", + "tosExp": null, + "wtrTimerInt": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd atob", + "discriminator local 80", + "discriminator remote 800" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_BFD = """ + + + %s + + +""" + +CE_NC_GET_BFD_GLB = """ + + + +""" + +CE_NC_GET_BFD_SESSION = """ + + + %s + + + + + + + + + + + + +""" + + +class BfdView(object): + """Manages BFD View""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.session_name = self.module.params['session_name'] + self.local_discr = self.module.params['local_discr'] + self.remote_discr = self.module.params['remote_discr'] + self.min_tx_interval = self.module.params['min_tx_interval'] + self.min_rx_interval = self.module.params['min_rx_interval'] + self.detect_multi = self.module.params['detect_multi'] + self.wtr_interval = self.module.params['wtr_interval'] + self.tos_exp = self.module.params['tos_exp'] + self.admin_down = self.module.params['admin_down'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + bfd_dict["session"] = dict() + conf_str = CE_NC_GET_BFD % (CE_NC_GET_BFD_GLB + (CE_NC_GET_BFD_SESSION % self.session_name)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + bfd_dict["global"][attr.tag] = attr.text + + # get bfd session info + sess = root.find("bfd/bfdCfgSessions/bfdCfgSession") + if sess: + for attr in sess: + bfd_dict["session"][attr.tag] = attr.text + + return bfd_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + cmd_list = list() + cmd_session = "" + + if not self.session_name: + return xml_str + + if self.bfd_dict["global"].get("bfdEnable", "false") != "true": + self.module.fail_json(msg="Error: Please enable BFD globally first.") + + if not self.bfd_dict["session"]: + self.module.fail_json(msg="Error: BFD session is not exist.") + + session = self.bfd_dict["session"] + xml_str = "%s" % self.session_name + cmd_session = "bfd %s" % self.session_name + + # BFD session view + if self.local_discr is not None: + if self.state == "present" and str(self.local_discr) != session.get("localDiscr"): + xml_str += "%s" % self.local_discr + cmd_list.append("discriminator local %s" % self.local_discr) + elif self.state == "absent" and str(self.local_discr) == session.get("localDiscr"): + xml_str += "" + cmd_list.append("undo discriminator local") + + if self.remote_discr is not None: + if self.state == "present" and str(self.remote_discr) != session.get("remoteDiscr"): + xml_str += "%s" % self.remote_discr + cmd_list.append("discriminator remote %s" % self.remote_discr) + elif self.state == "absent" and str(self.remote_discr) == session.get("remoteDiscr"): + xml_str += "" + cmd_list.append("undo discriminator remote") + + if self.min_tx_interval is not None: + if self.state == "present" and str(self.min_tx_interval) != session.get("minTxInt"): + xml_str += "%s" % self.min_tx_interval + cmd_list.append("min-tx-interval %s" % self.min_tx_interval) + elif self.state == "absent" and str(self.min_tx_interval) == session.get("minTxInt"): + xml_str += "" + cmd_list.append("undo min-tx-interval") + + if self.min_rx_interval is not None: + if self.state == "present" and str(self.min_rx_interval) != session.get("minRxInt"): + xml_str += "%s" % self.min_rx_interval + cmd_list.append("min-rx-interval %s" % self.min_rx_interval) + elif self.state == "absent" and str(self.min_rx_interval) == session.get("minRxInt"): + xml_str += "" + cmd_list.append("undo min-rx-interval") + + if self.detect_multi is not None: + if self.state == "present" and str(self.detect_multi) != session.get("detectMulti"): + xml_str += " %s" % self.detect_multi + cmd_list.append("detect-multiplier %s" % self.detect_multi) + elif self.state == "absent" and str(self.detect_multi) == session.get("detectMulti"): + xml_str += " " + cmd_list.append("undo detect-multiplier") + + if self.wtr_interval is not None: + if self.state == "present" and str(self.wtr_interval) != session.get("wtrTimerInt"): + xml_str += " %s" % self.wtr_interval + cmd_list.append("wtr %s" % self.wtr_interval) + elif self.state == "absent" and str(self.wtr_interval) == session.get("wtrTimerInt"): + xml_str += " " + cmd_list.append("undo wtr") + + if self.tos_exp is not None: + if self.state == "present" and str(self.tos_exp) != session.get("tosExp"): + xml_str += " %s" % self.tos_exp + cmd_list.append("tos-exp %s" % self.tos_exp) + elif self.state == "absent" and str(self.tos_exp) == session.get("tosExp"): + xml_str += " " + cmd_list.append("undo tos-exp") + + if self.admin_down and session.get("adminDown", "false") == "false": + xml_str += " true" + cmd_list.append("shutdown") + elif not self.admin_down and session.get("adminDown", "false") == "true": + xml_str += " false" + cmd_list.append("undo shutdown") + + if self.description: + if self.state == "present" and self.description != session.get("description"): + xml_str += "%s" % self.description + cmd_list.append("description %s" % self.description) + elif self.state == "absent" and self.description == session.get("description"): + xml_str += "" + cmd_list.append("undo description") + + if xml_str.endswith(""): + # no config update + return "" + else: + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + set_nc_config(self.min_rx_interval, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check session_name + if not self.session_name: + self.module.fail_json(msg="Error: Missing required arguments: session_name.") + + if self.session_name: + if len(self.session_name) < 1 or len(self.session_name) > 15: + self.module.fail_json(msg="Error: Session name is invalid.") + + # check local_discr + if self.local_discr is not None: + if self.local_discr < 1 or self.local_discr > 16384: + self.module.fail_json(msg="Error: Session local_discr is not ranges from 1 to 16384.") + + # check remote_discr + if self.remote_discr is not None: + if self.remote_discr < 1 or self.remote_discr > 4294967295: + self.module.fail_json(msg="Error: Session remote_discr is not ranges from 1 to 4294967295.") + + # check min_tx_interval + if self.min_tx_interval is not None: + if self.min_tx_interval < 50 or self.min_tx_interval > 1000: + self.module.fail_json(msg="Error: Session min_tx_interval is not ranges from 50 to 1000.") + + # check min_rx_interval + if self.min_rx_interval is not None: + if self.min_rx_interval < 50 or self.min_rx_interval > 1000: + self.module.fail_json(msg="Error: Session min_rx_interval is not ranges from 50 to 1000.") + + # check detect_multi + if self.detect_multi is not None: + if self.detect_multi < 3 or self.detect_multi > 50: + self.module.fail_json(msg="Error: Session detect_multi is not ranges from 3 to 50.") + + # check wtr_interval + if self.wtr_interval is not None: + if self.wtr_interval < 1 or self.wtr_interval > 60: + self.module.fail_json(msg="Error: Session wtr_interval is not ranges from 1 to 60.") + + # check tos_exp + if self.tos_exp is not None: + if self.tos_exp < 0 or self.tos_exp > 7: + self.module.fail_json(msg="Error: Session tos_exp is not ranges from 0 to 7.") + + # check description + if self.description: + if len(self.description) < 1 or len(self.description) > 51: + self.module.fail_json(msg="Error: Session description is invalid.") + + def get_proposed(self): + """get proposed info""" + + # base config + self.proposed["session_name"] = self.session_name + self.proposed["local_discr"] = self.local_discr + self.proposed["remote_discr"] = self.remote_discr + self.proposed["min_tx_interval"] = self.min_tx_interval + self.proposed["min_rx_interval"] = self.min_rx_interval + self.proposed["detect_multi"] = self.detect_multi + self.proposed["wtr_interval"] = self.wtr_interval + self.proposed["tos_exp"] = self.tos_exp + self.proposed["admin_down"] = self.admin_down + self.proposed["description"] = self.description + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["session"] = self.bfd_dict.get("session") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["session"] = bfd_dict.get("session") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.session_name: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + session_name=dict(required=True, type='str'), + local_discr=dict(required=False, type='int'), + remote_discr=dict(required=False, type='int'), + min_tx_interval=dict(required=False, type='int'), + min_rx_interval=dict(required=False, type='int'), + detect_multi=dict(required=False, type='int'), + wtr_interval=dict(required=False, type='int'), + tos_exp=dict(required=False, type='int'), + admin_down=dict(required=False, type='bool', default=False), + description=dict(required=False, type='str'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = BfdView(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp.py new file mode 100644 index 00000000..0ff7c57d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp.py @@ -0,0 +1,2327 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp +short_description: Manages BGP configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + as_number: + description: + - Local AS number. + The value is a string of 1 to 11 characters. + graceful_restart: + description: + - Enable GR of the BGP speaker in the specified address family, peer address, or peer group. + default: no_use + choices: ['no_use','true','false'] + time_wait_for_rib: + description: + - Period of waiting for the End-Of-RIB flag. + The value is an integer ranging from 3 to 3000. The default value is 600. + as_path_limit: + description: + - Maximum number of AS numbers in the AS_Path attribute. The default value is 255. + check_first_as: + description: + - Check the first AS in the AS_Path of the update messages from EBGP peers. + default: no_use + choices: ['no_use','true','false'] + confed_id_number: + description: + - Confederation ID. + The value is a string of 1 to 11 characters. + confed_nonstanded: + description: + - Configure the device to be compatible with devices in a nonstandard confederation. + default: no_use + choices: ['no_use','true','false'] + bgp_rid_auto_sel: + description: + - The function to automatically select router IDs for all VPN BGP instances is enabled. + default: no_use + choices: ['no_use','true','false'] + keep_all_routes: + description: + - If the value is true, the system stores all route update messages received from all peers (groups) after + BGP connection setup. + If the value is false, the system stores only BGP update messages that are received from peers and pass + the configured import policy. + default: no_use + choices: ['no_use','true','false'] + memory_limit: + description: + - Support BGP RIB memory protection. + default: no_use + choices: ['no_use','true','false'] + gr_peer_reset: + description: + - Peer disconnection through GR. + default: no_use + choices: ['no_use','true','false'] + is_shutdown: + description: + - Interrupt BGP all neighbor. + default: no_use + choices: ['no_use','true','false'] + suppress_interval: + description: + - Suppress interval. + hold_interval: + description: + - Hold interval. + clear_interval: + description: + - Clear interval. + confed_peer_as_num: + description: + - Confederation AS number, in two-byte or four-byte format. + The value is a string of 1 to 11 characters. + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + vrf_rid_auto_sel: + description: + - If the value is true, VPN BGP instances are enabled to automatically select router IDs. + If the value is false, VPN BGP instances are disabled from automatically selecting router IDs. + default: no_use + choices: ['no_use','true','false'] + router_id: + description: + - ID of a router that is in IPv4 address format. + keepalive_time: + description: + - If the value of a timer changes, the BGP peer relationship between the routers is disconnected. + The value is an integer ranging from 0 to 21845. The default value is 60. + hold_time: + description: + - Hold time, in seconds. The value of the hold time can be 0 or range from 3 to 65535. + min_hold_time: + description: + - Min hold time, in seconds. The value of the hold time can be 0 or range from 20 to 65535. + conn_retry_time: + description: + - ConnectRetry interval. The value is an integer, in seconds. The default value is 32s. + ebgp_if_sensitive: + description: + - If the value is true, After the fast EBGP interface awareness function is enabled, EBGP sessions on + an interface are deleted immediately when the interface goes Down. + If the value is false, After the fast EBGP interface awareness function is enabled, EBGP sessions + on an interface are not deleted immediately when the interface goes Down. + default: no_use + choices: ['no_use','true','false'] + default_af_type: + description: + - Type of a created address family, which can be IPv4 unicast or IPv6 unicast. + The default type is IPv4 unicast. + choices: ['ipv4uni','ipv6uni'] +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Enable BGP" + community.network.ce_bgp: + state: present + as_number: 100 + confed_id_number: 250 + provider: "{{ cli }}" + + - name: "Disable BGP" + community.network.ce_bgp: + state: absent + as_number: 100 + confed_id_number: 250 + provider: "{{ cli }}" + + - name: "Create confederation peer AS num" + community.network.ce_bgp: + state: present + confed_peer_as_num: 260 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"as_number": "100", state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp_enable": [["100"], ["true"]]} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp_enable": [["100"], ["true"]]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["bgp 100"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +SUCCESS = """success""" +FAILED = """failed""" + + +# get bgp enable +CE_GET_BGP_ENABLE = """ + + + + + + + + + + +""" + +CE_GET_BGP_ENABLE_HEADER = """ + + + + +""" + +CE_GET_BGP_ENABLE_TAIL = """ + + + + +""" + +# merge bgp enable +CE_MERGE_BGP_ENABLE_HEADER = """ + + + + +""" +CE_MERGE_BGP_ENABLE_TAIL = """ + + + + +""" + +# get bgp confederation peer as +CE_GET_BGP_CONFED_PEER_AS = """ + + + + + + + + + + + +""" + +# merge bgp confederation peer as +CE_MERGE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# create bgp confederation peer as +CE_CREATE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# delete bgp confederation peer as +CE_DELETE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# get bgp instance +CE_GET_BGP_INSTANCE = """ + + + + + + + + + + + +""" + +# get bgp instance +CE_GET_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_GET_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# merge bgp instance +CE_MERGE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_MERGE_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# create bgp instance +CE_CREATE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_CREATE_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# delete bgp instance +CE_DELETE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_DELETE_BGP_INSTANCE_TAIL = """ + + + + + +""" + + +def check_ip_addr(**kwargs): + """ check_ip_addr """ + + ipaddr = kwargs["ipaddr"] + + addr = ipaddr.strip().split('.') + + if len(addr) != 4: + return FAILED + + for i in range(4): + addr[i] = int(addr[i]) + + if addr[i] <= 255 and addr[i] >= 0: + pass + else: + return FAILED + return SUCCESS + + +def check_bgp_enable_args(**kwargs): + """ check_bgp_enable_args """ + + module = kwargs["module"] + + need_cfg = False + + as_number = module.params['as_number'] + if as_number: + if len(as_number) > 11 or len(as_number) == 0: + module.fail_json( + msg='Error: The len of as_number %s is out of [1 - 11].' % as_number) + else: + need_cfg = True + + return need_cfg + + +def check_bgp_confed_args(**kwargs): + """ check_bgp_confed_args """ + + module = kwargs["module"] + + need_cfg = False + + confed_peer_as_num = module.params['confed_peer_as_num'] + if confed_peer_as_num: + if len(confed_peer_as_num) > 11 or len(confed_peer_as_num) == 0: + module.fail_json( + msg='Error: The len of confed_peer_as_num %s is out of [1 - 11].' % confed_peer_as_num) + else: + need_cfg = True + + return need_cfg + + +class Bgp(object): + """ Manages BGP configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_enable_other_args(self, **kwargs): + """ check_bgp_enable_other_args """ + + module = kwargs["module"] + state = module.params['state'] + result = dict() + need_cfg = False + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["graceful_restart"] = re_find + if re_find[0] != graceful_restart: + need_cfg = True + else: + need_cfg = True + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + if int(time_wait_for_rib) > 3000 or int(time_wait_for_rib) < 3: + module.fail_json( + msg='Error: The time_wait_for_rib %s is out of [3 - 3000].' % time_wait_for_rib) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["time_wait_for_rib"] = re_find + if re_find[0] != time_wait_for_rib: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["time_wait_for_rib"] = re_find + if re_find[0] == time_wait_for_rib: + need_cfg = True + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + if int(as_path_limit) > 2000 or int(as_path_limit) < 1: + module.fail_json( + msg='Error: The as_path_limit %s is out of [1 - 2000].' % as_path_limit) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_limit"] = re_find + if re_find[0] != as_path_limit: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_limit"] = re_find + if re_find[0] == as_path_limit: + need_cfg = True + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["check_first_as"] = re_find + if re_find[0] != check_first_as: + need_cfg = True + else: + need_cfg = True + + confed_id_number = module.params['confed_id_number'] + if confed_id_number: + if len(confed_id_number) > 11 or len(confed_id_number) == 0: + module.fail_json( + msg='Error: The len of confed_id_number %s is out of [1 - 11].' % confed_id_number) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_id_number"] = re_find + if re_find[0] != confed_id_number: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_id_number"] = re_find + if re_find[0] == confed_id_number: + need_cfg = True + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_nonstanded"] = re_find + if re_find[0] != confed_nonstanded: + need_cfg = True + else: + need_cfg = True + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["bgp_rid_auto_sel"] = re_find + if re_find[0] != bgp_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_all_routes"] = re_find + if re_find[0] != keep_all_routes: + need_cfg = True + else: + need_cfg = True + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["memory_limit"] = re_find + if re_find[0] != memory_limit: + need_cfg = True + else: + need_cfg = True + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["gr_peer_reset"] = re_find + if re_find[0] != gr_peer_reset: + need_cfg = True + else: + need_cfg = True + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_shutdown"] = re_find + if re_find[0] != is_shutdown: + need_cfg = True + else: + need_cfg = True + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + + if not hold_interval or not clear_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(suppress_interval) > 65535 or int(suppress_interval) < 1: + module.fail_json( + msg='Error: The suppress_interval %s is out of [1 - 65535].' % suppress_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["suppress_interval"] = re_find + if re_find[0] != suppress_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["suppress_interval"] = re_find + if re_find[0] == suppress_interval: + need_cfg = True + + if hold_interval: + + if not suppress_interval or not clear_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(hold_interval) > 65535 or int(hold_interval) < 1: + module.fail_json( + msg='Error: The hold_interval %s is out of [1 - 65535].' % hold_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_interval"] = re_find + if re_find[0] != hold_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_interval"] = re_find + if re_find[0] == hold_interval: + need_cfg = True + + if clear_interval: + + if not suppress_interval or not hold_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(clear_interval) > 65535 or int(clear_interval) < 1: + module.fail_json( + msg='Error: The clear_interval %s is out of [1 - 65535].' % clear_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["clear_interval"] = re_find + if re_find[0] != clear_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["clear_interval"] = re_find + if re_find[0] == clear_interval: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_instance_args(self, **kwargs): + """ check_bgp_instance_args """ + + module = kwargs["module"] + state = module.params['state'] + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='the len of vrf_name %s is out of [1 - 31].' % vrf_name) + conf_str = CE_GET_BGP_INSTANCE_HEADER + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + check_vrf_name = vrf_name + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if check_vrf_name not in re_find: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if check_vrf_name in re_find: + need_cfg = True + + return need_cfg + + def check_bgp_instance_other_args(self, **kwargs): + """ check_bgp_instance_other_args """ + + module = kwargs["module"] + state = module.params['state'] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + + router_id = module.params['router_id'] + if router_id: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if check_ip_addr(ipaddr=router_id) == FAILED: + module.fail_json( + msg='Error: The router_id %s is invalid.' % router_id) + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + if re_find[0] == router_id: + need_cfg = True + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vrf_rid_auto_sel"] = re_find + + if re_find[0] != vrf_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(keepalive_time) > 21845 or int(keepalive_time) < 0: + module.fail_json( + msg='keepalive_time %s is out of [0 - 21845].' % keepalive_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keepalive_time"] = re_find + if re_find[0] != keepalive_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keepalive_time"] = re_find + if re_find[0] == keepalive_time: + need_cfg = True + + hold_time = module.params['hold_time'] + if hold_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(hold_time) > 65535 or int(hold_time) < 3: + module.fail_json( + msg='hold_time %s is out of [3 - 65535].' % hold_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] != hold_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] == hold_time: + need_cfg = True + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(min_hold_time) != 0 and (int(min_hold_time) > 65535 or int(min_hold_time) < 20): + module.fail_json( + msg='min_hold_time %s is out of [0, or 20 - 65535].' % min_hold_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] != min_hold_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] == min_hold_time: + need_cfg = True + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(conn_retry_time) > 65535 or int(conn_retry_time) < 1: + module.fail_json( + msg='conn_retry_time %s is out of [1 - 65535].' % conn_retry_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] != conn_retry_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] == conn_retry_time: + need_cfg = True + else: + pass + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + if re_find[0] == ebgp_if_sensitive: + need_cfg = True + else: + pass + + default_af_type = module.params['default_af_type'] + if default_af_type: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_af_type"] = re_find + if re_find[0] != default_af_type: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_af_type"] = re_find + if re_find[0] == default_af_type: + need_cfg = True + else: + pass + + result["need_cfg"] = need_cfg + return result + + def get_bgp_enable(self, **kwargs): + """ get_bgp_enable """ + + module = kwargs["module"] + + conf_str = CE_GET_BGP_ENABLE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_enable(self, **kwargs): + """ merge_bgp_enable """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + state = module.params['state'] + + if state == "present": + conf_str += "true" + else: + conf_str += "false" + + as_number = module.params['as_number'] + if as_number: + conf_str += "%s" % as_number + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp enable failed.') + + cmds = [] + if state == "present": + cmd = "bgp %s" % as_number + else: + cmd = "undo bgp %s" % as_number + cmds.append(cmd) + + return cmds + + def merge_bgp_enable_other(self, **kwargs): + """ merge_bgp_enable_other """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + cmds = [] + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + conf_str += "%s" % graceful_restart + + if graceful_restart == "true": + cmd = "graceful-restart" + else: + cmd = "undo graceful-restart" + cmds.append(cmd) + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + conf_str += "%s" % time_wait_for_rib + + cmd = "graceful-restart timer wait-for-rib %s" % time_wait_for_rib + cmds.append(cmd) + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + conf_str += "%s" % as_path_limit + + cmd = "as-path-limit %s" % as_path_limit + cmds.append(cmd) + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str += "%s" % check_first_as + + if check_first_as == "true": + cmd = "check-first-as" + else: + cmd = "undo check-first-as" + cmds.append(cmd) + + confed_id_number = module.params['confed_id_number'] + if confed_id_number: + conf_str += "%s" % confed_id_number + + cmd = "confederation id %s" % confed_id_number + cmds.append(cmd) + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str += "%s" % confed_nonstanded + + if confed_nonstanded == "true": + cmd = "confederation nonstandard" + else: + cmd = "undo confederation nonstandard" + cmds.append(cmd) + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str += "%s" % bgp_rid_auto_sel + + if bgp_rid_auto_sel == "true": + cmd = "router-id vpn-instance auto-select" + else: + cmd = "undo router-id" + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "keep-all-routes" + else: + cmd = "undo keep-all-routes" + cmds.append(cmd) + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str += "%s" % memory_limit + + if memory_limit == "true": + cmd = "prefix memory-limit" + else: + cmd = "undo prefix memory-limit" + cmds.append(cmd) + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str += "%s" % gr_peer_reset + + if gr_peer_reset == "true": + cmd = "graceful-restart peer-reset" + else: + cmd = "undo graceful-restart peer-reset" + cmds.append(cmd) + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str += "%s" % is_shutdown + + if is_shutdown == "true": + cmd = "shutdown" + else: + cmd = "undo shutdown" + cmds.append(cmd) + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + conf_str += "%s" % suppress_interval + + cmd = "nexthop recursive-lookup restrain suppress-interval %s hold-interval %s " \ + "clear-interval %s" % (suppress_interval, hold_interval, clear_interval) + cmds.append(cmd) + + if hold_interval: + conf_str += "%s" % hold_interval + + if clear_interval: + conf_str += "%s" % clear_interval + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp enable failed.') + + return cmds + + def delete_bgp_enable_other(self, **kwargs): + """ delete bgp enable other args """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + cmds = [] + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + conf_str += "%s" % graceful_restart + + if graceful_restart == "true": + cmd = "graceful-restart" + else: + cmd = "undo graceful-restart" + cmds.append(cmd) + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + conf_str += "600" + + cmd = "undo graceful-restart timer wait-for-rib" + cmds.append(cmd) + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + conf_str += "255" + + cmd = "undo as-path-limit" + cmds.append(cmd) + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str += "%s" % check_first_as + + if check_first_as == "true": + cmd = "check-first-as" + else: + cmd = "undo check-first-as" + cmds.append(cmd) + + confed_id_number = module.params['confed_id_number'] + confed_peer_as_num = module.params['confed_peer_as_num'] + if confed_id_number and not confed_peer_as_num: + conf_str += "" + + cmd = "undo confederation id" + cmds.append(cmd) + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str += "%s" % confed_nonstanded + + if confed_nonstanded == "true": + cmd = "confederation nonstandard" + else: + cmd = "undo confederation nonstandard" + cmds.append(cmd) + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str += "%s" % bgp_rid_auto_sel + + if bgp_rid_auto_sel == "true": + cmd = "router-id vpn-instance auto-select" + else: + cmd = "undo router-id" + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "keep-all-routes" + else: + cmd = "undo keep-all-routes" + cmds.append(cmd) + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str += "%s" % memory_limit + + if memory_limit == "true": + cmd = "prefix memory-limit" + else: + cmd = "undo prefix memory-limit" + cmds.append(cmd) + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str += "%s" % gr_peer_reset + + if gr_peer_reset == "true": + cmd = "graceful-restart peer-reset" + else: + cmd = "undo graceful-restart peer-reset" + cmds.append(cmd) + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str += "%s" % is_shutdown + + if is_shutdown == "true": + cmd = "shutdown" + else: + cmd = "undo shutdown" + cmds.append(cmd) + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + conf_str += "60" + + cmd = "undo nexthop recursive-lookup restrain suppress-interval hold-interval clear-interval" + cmds.append(cmd) + + if hold_interval: + conf_str += "120" + + if clear_interval: + conf_str += "600" + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp enable failed.') + + return cmds + + def get_bgp_confed_peer_as(self, **kwargs): + """ get_bgp_confed_peer_as """ + + module = kwargs["module"] + + conf_str = CE_GET_BGP_CONFED_PEER_AS + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_confed_peer_as(self, **kwargs): + """ merge_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_MERGE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp confed peer as failed.') + + cmds = [] + cmd = "confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def create_bgp_confed_peer_as(self, **kwargs): + """ create_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_CREATE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp confed peer as failed.') + + cmds = [] + cmd = "confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def delete_bgp_confed_peer_as(self, **kwargs): + """ delete_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_DELETE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp confed peer as failed.') + + cmds = [] + cmd = "undo confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def get_bgp_instance(self, **kwargs): + """ get_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_GET_BGP_INSTANCE + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_instance(self, **kwargs): + """ merge_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp instance failed.') + + cmds = [] + + if vrf_name != "_public_": + cmd = "ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def create_bgp_instance(self, **kwargs): + """ create_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_CREATE_BGP_INSTANCE_HEADER + + cmds = [] + + vrf_name = module.params['vrf_name'] + if vrf_name: + if vrf_name == "_public_": + return cmds + conf_str += "%s" % vrf_name + + conf_str += CE_CREATE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp instance failed.') + + if vrf_name != "_public_": + cmd = "ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def delete_bgp_instance(self, **kwargs): + """ delete_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_DELETE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + if vrf_name: + conf_str += "%s" % vrf_name + + conf_str += CE_DELETE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp instance failed.') + + cmds = [] + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def merge_bgp_instance_other(self, **kwargs): + """ merge_bgp_instance_other """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % default_af_type + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + + if vrf_rid_auto_sel == "true": + cmd = "router-id auto-select" + else: + cmd = "undo router-id auto-select" + cmds.append(cmd) + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "router-id %s" % router_id + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % keepalive_time + + cmd = "timer keepalive %s" % keepalive_time + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "timer hold %s" % hold_time + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "timer min-holdtime %s" % min_hold_time + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "timer connect-retry %s" % conn_retry_time + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + if ebgp_if_sensitive == "true": + cmd = "ebgp-interface-sensitive" + else: + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp instance other failed.') + + return cmds + + def delete_bgp_instance_other_comm(self, **kwargs): + """ delete_bgp_instance_other_comm """ + + module = kwargs["module"] + conf_str = CE_DELETE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "undo router-id" + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + + cmd = "undo router-id vpn-instance auto-select" + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % keepalive_time + + cmd = "undo timer keepalive" + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "undo timer hold" + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "undo timer min-holdtime" + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "undo timer connect-retry" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % default_af_type + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + conf_str += CE_DELETE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete common vpn bgp instance other args failed.') + + return cmds + + def delete_instance_other_public(self, **kwargs): + """ delete_instance_other_public """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "" + + cmd = "undo router-id" + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % "false" + + cmd = "undo router-id vpn-instance auto-select" + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % "60" + + cmd = "undo timer keepalive" + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % "180" + + cmd = "undo timer hold" + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % "0" + + cmd = "undo timer min-holdtime" + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % "32" + + cmd = "undo timer connect-retry" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % "true" + + cmd = "ebgp-interface-sensitive" + cmds.append(cmd) + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % "ipv4uni" + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete default vpn bgp instance other args failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + as_number=dict(type='str'), + graceful_restart=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + time_wait_for_rib=dict(type='str'), + as_path_limit=dict(type='str'), + check_first_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + confed_id_number=dict(type='str'), + confed_nonstanded=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + bgp_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + keep_all_routes=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + memory_limit=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + gr_peer_reset=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_shutdown=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + suppress_interval=dict(type='str'), + hold_interval=dict(type='str'), + clear_interval=dict(type='str'), + confed_peer_as_num=dict(type='str'), + vrf_name=dict(type='str'), + vrf_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id=dict(type='str'), + keepalive_time=dict(type='str'), + hold_time=dict(type='str'), + min_hold_time=dict(type='str'), + conn_retry_time=dict(type='str'), + ebgp_if_sensitive=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_af_type=dict(type='str', choices=['ipv4uni', 'ipv6uni']) + ) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + as_number = module.params['as_number'] + graceful_restart = module.params['graceful_restart'] + time_wait_for_rib = module.params['time_wait_for_rib'] + as_path_limit = module.params['as_path_limit'] + check_first_as = module.params['check_first_as'] + confed_id_number = module.params['confed_id_number'] + confed_nonstanded = module.params['confed_nonstanded'] + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + keep_all_routes = module.params['keep_all_routes'] + memory_limit = module.params['memory_limit'] + gr_peer_reset = module.params['gr_peer_reset'] + is_shutdown = module.params['is_shutdown'] + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + confed_peer_as_num = module.params['confed_peer_as_num'] + router_id = module.params['router_id'] + vrf_name = module.params['vrf_name'] + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + keepalive_time = module.params['keepalive_time'] + hold_time = module.params['hold_time'] + min_hold_time = module.params['min_hold_time'] + conn_retry_time = module.params['conn_retry_time'] + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + default_af_type = module.params['default_af_type'] + + ce_bgp_obj = Bgp() + + if not ce_bgp_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if as_number: + proposed["as_number"] = as_number + if graceful_restart != 'no_use': + proposed["graceful_restart"] = graceful_restart + if time_wait_for_rib: + proposed["time_wait_for_rib"] = time_wait_for_rib + if as_path_limit: + proposed["as_path_limit"] = as_path_limit + if check_first_as != 'no_use': + proposed["check_first_as"] = check_first_as + if confed_id_number: + proposed["confed_id_number"] = confed_id_number + if confed_nonstanded != 'no_use': + proposed["confed_nonstanded"] = confed_nonstanded + if bgp_rid_auto_sel != 'no_use': + proposed["bgp_rid_auto_sel"] = bgp_rid_auto_sel + if keep_all_routes != 'no_use': + proposed["keep_all_routes"] = keep_all_routes + if memory_limit != 'no_use': + proposed["memory_limit"] = memory_limit + if gr_peer_reset != 'no_use': + proposed["gr_peer_reset"] = gr_peer_reset + if is_shutdown != 'no_use': + proposed["is_shutdown"] = is_shutdown + if suppress_interval: + proposed["suppress_interval"] = suppress_interval + if hold_interval: + proposed["hold_interval"] = hold_interval + if clear_interval: + proposed["clear_interval"] = clear_interval + if confed_peer_as_num: + proposed["confed_peer_as_num"] = confed_peer_as_num + if router_id: + proposed["router_id"] = router_id + if vrf_name: + proposed["vrf_name"] = vrf_name + if vrf_rid_auto_sel != 'no_use': + proposed["vrf_rid_auto_sel"] = vrf_rid_auto_sel + if keepalive_time: + proposed["keepalive_time"] = keepalive_time + if hold_time: + proposed["hold_time"] = hold_time + if min_hold_time: + proposed["min_hold_time"] = min_hold_time + if conn_retry_time: + proposed["conn_retry_time"] = conn_retry_time + if ebgp_if_sensitive != 'no_use': + proposed["ebgp_if_sensitive"] = ebgp_if_sensitive + if default_af_type: + proposed["default_af_type"] = default_af_type + + need_bgp_enable = check_bgp_enable_args(module=module) + need_bgp_enable_other_rst = ce_bgp_obj.check_bgp_enable_other_args( + module=module) + need_bgp_confed = check_bgp_confed_args(module=module) + need_bgp_instance = ce_bgp_obj.check_bgp_instance_args(module=module) + need_bgp_instance_other_rst = ce_bgp_obj.check_bgp_instance_other_args( + module=module) + + router_id_exist = ce_bgp_obj.get_bgp_instance(module=module) + existing["bgp instance"] = router_id_exist + + # bgp enable/disable + if need_bgp_enable: + + bgp_enable_exist = ce_bgp_obj.get_bgp_enable(module=module) + existing["bgp enable"] = bgp_enable_exist + if bgp_enable_exist: + asnumber_exist = bgp_enable_exist[0][1] + bgpenable_exist = bgp_enable_exist[0][0] + else: + asnumber_exist = None + bgpenable_exist = None + + if state == "present": + bgp_enable_new = ("true", as_number) + + if bgp_enable_new in bgp_enable_exist: + pass + elif bgpenable_exist == "true" and asnumber_exist != as_number: + module.fail_json( + msg='Error: BGP is already running. The AS is %s.' % asnumber_exist) + else: + cmd = ce_bgp_obj.merge_bgp_enable(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if need_bgp_enable_other_rst["need_cfg"] or need_bgp_confed or \ + need_bgp_instance_other_rst["need_cfg"] or need_bgp_instance: + pass + elif bgpenable_exist == "false": + pass + elif bgpenable_exist == "true" and asnumber_exist == as_number: + cmd = ce_bgp_obj.merge_bgp_enable(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + module.fail_json( + msg='Error: BGP is already running. The AS is %s.' % asnumber_exist) + + bgp_enable_end = ce_bgp_obj.get_bgp_enable(module=module) + end_state["bgp enable"] = bgp_enable_end + + # bgp enable/disable other args + exist_tmp = dict() + for item in need_bgp_enable_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_enable_other_rst[item] + + if exist_tmp: + existing["bgp enable other"] = exist_tmp + + if need_bgp_enable_other_rst["need_cfg"]: + if state == "present": + cmd = ce_bgp_obj.merge_bgp_enable_other(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_obj.delete_bgp_enable_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_enable_other_rst = ce_bgp_obj.check_bgp_enable_other_args( + module=module) + + end_tmp = dict() + for item in need_bgp_enable_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_enable_other_rst[item] + + if end_tmp: + end_state["bgp enable other"] = end_tmp + + # bgp confederation peer as + if need_bgp_confed: + confed_exist = ce_bgp_obj.get_bgp_confed_peer_as(module=module) + existing["confederation peer as"] = confed_exist + confed_new = (confed_peer_as_num) + + if state == "present": + if len(confed_exist) == 0: + cmd = ce_bgp_obj.create_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + elif confed_new not in confed_exist: + cmd = ce_bgp_obj.merge_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if len(confed_exist) == 0: + pass + + elif confed_new not in confed_exist: + pass + + else: + cmd = ce_bgp_obj.delete_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + confed_end = ce_bgp_obj.get_bgp_confed_peer_as(module=module) + end_state["confederation peer as"] = confed_end + + # bgp instance + if need_bgp_instance and default_af_type != "ipv6uni": + router_id_new = vrf_name + + if state == "present": + if len(router_id_exist) == 0: + cmd = ce_bgp_obj.create_bgp_instance(module=module) + changed = True + updates.extend(cmd) + elif router_id_new not in router_id_exist: + cmd = ce_bgp_obj.merge_bgp_instance(module=module) + changed = True + updates.extend(cmd) + else: + if not need_bgp_instance_other_rst["need_cfg"]: + if vrf_name != "_public_": + if len(router_id_exist) == 0: + pass + elif router_id_new not in router_id_exist: + pass + else: + cmd = ce_bgp_obj.delete_bgp_instance(module=module) + changed = True + for item in cmd: + updates.append(item) + + # bgp instance other + exist_tmp = dict() + for item in need_bgp_instance_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_instance_other_rst[item] + + if exist_tmp: + existing["bgp instance other"] = exist_tmp + + if need_bgp_instance_other_rst["need_cfg"]: + if state == "present": + cmd = ce_bgp_obj.merge_bgp_instance_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if vrf_name == "_public_": + cmd = ce_bgp_obj.delete_instance_other_public( + module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_obj.delete_bgp_instance_other_comm(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_instance_other_rst = ce_bgp_obj.check_bgp_instance_other_args( + module=module) + + router_id_end = ce_bgp_obj.get_bgp_instance(module=module) + end_state["bgp instance"] = router_id_end + + end_tmp = dict() + for item in need_bgp_instance_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_instance_other_rst[item] + + if end_tmp: + end_state["bgp instance other"] = end_tmp + if end_state == existing: + changed = False + updates = list() + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_af.py new file mode 100644 index 00000000..26ac5cf1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_af.py @@ -0,0 +1,3430 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_af +short_description: Manages BGP Address-family configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP Address-family configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + The value is a string of 1 to 31 case-sensitive characters. + required: true + af_type: + description: + - Address family type of a BGP instance. + required: true + choices: ['ipv4uni','ipv4multi', 'ipv4vpn', 'ipv6uni', 'ipv6vpn', 'evpn'] + max_load_ibgp_num: + description: + - Specify the maximum number of equal-cost IBGP routes. + The value is an integer ranging from 1 to 65535. + ibgp_ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in IBGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself in + IBGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + max_load_ebgp_num: + description: + - Specify the maximum number of equal-cost EBGP routes. + The value is an integer ranging from 1 to 65535. + ebgp_ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in EBGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself in + EBGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + maximum_load_balance: + description: + - Specify the maximum number of equal-cost routes in the BGP routing table. + The value is an integer ranging from 1 to 65535. + ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in BGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself + in BGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + default_local_pref: + description: + - Set the Local-Preference attribute. The value is an integer. + The value is an integer ranging from 0 to 4294967295. + default_med: + description: + - Specify the Multi-Exit-Discriminator (MED) of BGP routes. + The value is an integer ranging from 0 to 4294967295. + default_rt_import_enable: + description: + - If the value is true, importing default routes to the BGP routing table is allowed. + If the value is false, importing default routes to the BGP routing table is not allowed. + choices: ['no_use','true','false'] + default: no_use + router_id: + description: + - ID of a router that is in IPv4 address format. + The value is a string of 0 to 255 characters. + The value is in dotted decimal notation. + vrf_rid_auto_sel: + description: + - If the value is true, VPN BGP instances are enabled to automatically select router IDs. + If the value is false, VPN BGP instances are disabled from automatically selecting router IDs. + choices: ['no_use','true','false'] + default: no_use + nexthop_third_party: + description: + - If the value is true, the third-party next hop function is enabled. + If the value is false, the third-party next hop function is disabled. + choices: ['no_use','true','false'] + default: no_use + summary_automatic: + description: + - If the value is true, automatic aggregation is enabled for locally imported routes. + If the value is false, automatic aggregation is disabled for locally imported routes. + choices: ['no_use','true','false'] + default: no_use + auto_frr_enable: + description: + - If the value is true, BGP auto FRR is enabled. + If the value is false, BGP auto FRR is disabled. + choices: ['no_use','true','false'] + default: no_use + load_balancing_as_path_ignore: + description: + - Load balancing as path ignore. + choices: ['no_use','true','false'] + default: no_use + rib_only_enable: + description: + - If the value is true, BGP routes cannot be advertised to the IP routing table. + If the value is false, Routes preferred by BGP are advertised to the IP routing table. + choices: ['no_use','true','false'] + default: no_use + rib_only_policy_name: + description: + - Specify the name of a routing policy. + The value is a string of 1 to 40 characters. + active_route_advertise: + description: + - If the value is true, BGP is enabled to advertise only optimal routes in the RM to peers. + If the value is false, BGP is not enabled to advertise only optimal routes in the RM to peers. + choices: ['no_use','true','false'] + default: no_use + as_path_neglect: + description: + - If the value is true, the AS path attribute is ignored when BGP selects an optimal route. + If the value is false, the AS path attribute is not ignored when BGP selects an optimal route. + An AS path with a smaller length has a higher priority. + choices: ['no_use','true','false'] + default: no_use + med_none_as_maximum: + description: + - If the value is true, when BGP selects an optimal route, the system uses 4294967295 as the + MED value of a route if the route's attribute does not carry a MED value. + If the value is false, the system uses 0 as the MED value of a route if the route's attribute + does not carry a MED value. + choices: ['no_use','true','false'] + default: no_use + router_id_neglect: + description: + - If the value is true, the router ID attribute is ignored when BGP selects the optimal route. + If the value is false, the router ID attribute is not ignored when BGP selects the optimal route. + choices: ['no_use','true','false'] + default: no_use + igp_metric_ignore: + description: + - If the value is true, the metrics of next-hop IGP routes are not compared when BGP selects + an optimal route. + If the value is false, the metrics of next-hop IGP routes are not compared when BGP selects + an optimal route. + A route with a smaller metric has a higher priority. + choices: ['no_use','true','false'] + default: no_use + always_compare_med: + description: + - If the value is true, the MEDs of routes learned from peers in different autonomous systems + are compared when BGP selects an optimal route. + If the value is false, the MEDs of routes learned from peers in different autonomous systems + are not compared when BGP selects an optimal route. + choices: ['no_use','true','false'] + default: no_use + determin_med: + description: + - If the value is true, BGP deterministic-MED is enabled. + If the value is false, BGP deterministic-MED is disabled. + choices: ['no_use','true','false'] + default: no_use + preference_external: + description: + - Set the protocol priority of EBGP routes. + The value is an integer ranging from 1 to 255. + preference_internal: + description: + - Set the protocol priority of IBGP routes. + The value is an integer ranging from 1 to 255. + preference_local: + description: + - Set the protocol priority of a local BGP route. + The value is an integer ranging from 1 to 255. + prefrence_policy_name: + description: + - Set a routing policy to filter routes so that a configured priority is applied to + the routes that match the specified policy. + The value is a string of 1 to 40 characters. + reflect_between_client: + description: + - If the value is true, route reflection is enabled between clients. + If the value is false, route reflection is disabled between clients. + choices: ['no_use','true','false'] + default: no_use + reflector_cluster_id: + description: + - Set a cluster ID. Configuring multiple RRs in a cluster can enhance the stability of the network. + The value is an integer ranging from 1 to 4294967295. + reflector_cluster_ipv4: + description: + - Set a cluster ipv4 address. The value is expressed in the format of an IPv4 address. + rr_filter_number: + description: + - Set the number of the extended community filter supported by an RR group. + The value is a string of 1 to 51 characters. + policy_vpn_target: + description: + - If the value is true, VPN-Target filtering function is performed for received VPN routes. + If the value is false, VPN-Target filtering function is not performed for received VPN routes. + choices: ['no_use','true','false'] + default: no_use + next_hop_sel_depend_type: + description: + - Next hop select depend type. + choices: ['default','dependTunnel', 'dependIp'] + default: default + nhp_relay_route_policy_name: + description: + - Specify the name of a route-policy for route iteration. + The value is a string of 1 to 40 characters. + ebgp_if_sensitive: + description: + - If the value is true, after the fast EBGP interface awareness function is enabled, + EBGP sessions on an interface are deleted immediately when the interface goes Down. + If the value is false, after the fast EBGP interface awareness function is enabled, + EBGP sessions on an interface are not deleted immediately when the interface goes Down. + choices: ['no_use','true','false'] + default: no_use + reflect_chg_path: + description: + - If the value is true, the route reflector is enabled to modify route path attributes + based on an export policy. + If the value is false, the route reflector is disabled from modifying route path attributes + based on an export policy. + choices: ['no_use','true','false'] + default: no_use + add_path_sel_num: + description: + - Number of Add-Path routes. + The value is an integer ranging from 2 to 64. + route_sel_delay: + description: + - Route selection delay. + The value is an integer ranging from 0 to 3600. + allow_invalid_as: + description: + - Allow routes with BGP origin AS validation result Invalid to be selected. + If the value is true, invalid routes can participate in route selection. + If the value is false, invalid routes cannot participate in route selection. + choices: ['no_use','true','false'] + default: no_use + policy_ext_comm_enable: + description: + - If the value is true, modifying extended community attributes is allowed. + If the value is false, modifying extended community attributes is not allowed. + choices: ['no_use','true','false'] + default: no_use + supernet_uni_adv: + description: + - If the value is true, the function to advertise supernetwork unicast routes is enabled. + If the value is false, the function to advertise supernetwork unicast routes is disabled. + choices: ['no_use','true','false'] + default: no_use + supernet_label_adv: + description: + - If the value is true, the function to advertise supernetwork label is enabled. + If the value is false, the function to advertise supernetwork label is disabled. + choices: ['no_use','true','false'] + default: no_use + ingress_lsp_policy_name: + description: + - Ingress lsp policy name. + originator_prior: + description: + - Originator prior. + choices: ['no_use','true','false'] + default: no_use + lowest_priority: + description: + - If the value is true, enable reduce priority to advertise route. + If the value is false, disable reduce priority to advertise route. + choices: ['no_use','true','false'] + default: no_use + relay_delay_enable: + description: + - If the value is true, relay delay enable. + If the value is false, relay delay disable. + choices: ['no_use','true','false'] + default: no_use + import_protocol: + description: + - Routing protocol from which routes can be imported. + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'ospfv3', 'ripng'] + import_process_id: + description: + - Process ID of an imported routing protocol. + The value is an integer ranging from 0 to 4294967295. + network_address: + description: + - Specify the IP address advertised by BGP. + The value is a string of 0 to 255 characters. + mask_len: + description: + - Specify the mask length of an IP address. + The value is an integer ranging from 0 to 128. +''' + +EXAMPLES = ''' +- name: CloudEngine BGP address family test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + tasks: + - name: "Config BGP Address_Family" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + provider: "{{ cli }}" + - name: "Undo BGP Address_Family" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + provider: "{{ cli }}" + - name: "Config import route" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + import_protocol: ospf + import_process_id: 123 + provider: "{{ cli }}" + - name: "Undo import route" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + import_protocol: ospf + import_process_id: 123 + provider: "{{ cli }}" + - name: "Config network route" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + network_address: 1.1.1.1 + mask_len: 24 + provider: "{{ cli }}" + - name: "Undo network route" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + network_address: 1.1.1.1 + mask_len: 24 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"af_type": "ipv4uni", + "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"af_type": "ipv4uni", "vrf_name": "js"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["ipv4-family vpn-instance js"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get bgp address family +CE_GET_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_GET_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# merge bgp address family +CE_MERGE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_MERGE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# create bgp address family +CE_CREATE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_CREATE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# delete bgp address family +CE_DELETE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_DELETE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# get bgp import route +CE_GET_BGP_IMPORT_AND_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + + + + + + + + + + + + + + + + + +""" + +# merge bgp import route +CE_MERGE_BGP_IMPORT_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + %s + %s +""" +CE_MERGE_BGP_IMPORT_ROUTE_TAIL = """ + + + + + + + + + +""" + +# create bgp import route +CE_CREATE_BGP_IMPORT_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# delete bgp import route +CE_DELETE_BGP_IMPORT_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# get bgp network route +CE_GET_BGP_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + + +""" +CE_GET_BGP_NETWORK_ROUTE_TAIL = """ + + + + + + + + + +""" + +# merge bgp network route +CE_MERGE_BGP_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + %s + %s +""" +CE_MERGE_BGP_NETWORK_ROUTE_TAIL = """ + + + + + + + + + +""" + +# create bgp network route +CE_CREATE_BGP_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# delete bgp network route +CE_DELETE_BGP_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# bgp import and network route header +CE_BGP_IMPORT_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s +""" +CE_BGP_IMPORT_NETWORK_ROUTE_TAIL = """ + + + + + + + +""" +CE_BGP_MERGE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_CREATE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_DELETE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_MERGE_NETWORK_UNIT = """ + + + %s + %s + + +""" +CE_BGP_CREATE_NETWORK_UNIT = """ + + + %s + %s + + +""" +CE_BGP_DELETE_NETWORK_UNIT = """ + + + %s + %s + + +""" + + +class BgpAf(object): + """ Manages BGP Address-family configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_af_args(self, **kwargs): + """ check_bgp_af_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + else: + module.fail_json(msg='Error: Please input vrf_name.') + + state = module.params['state'] + af_type = module.params['af_type'] + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["af_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != af_type: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["af_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] == af_type: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_af_other_can_del(self, **kwargs): + """ check_bgp_af_other_can_del """ + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + router_id = module.params['router_id'] + if router_id: + if len(router_id) > 255: + module.fail_json( + msg='Error: The len of router_id %s is out of [0 - 255].' % router_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == router_id: + need_cfg = True + else: + pass + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != determin_med: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == determin_med: + need_cfg = True + else: + pass + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == ebgp_if_sensitive: + need_cfg = True + else: + pass + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != relay_delay_enable: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == relay_delay_enable: + need_cfg = True + else: + pass + + result["need_cfg"] = need_cfg + return result + + def check_bgp_af_other_args(self, **kwargs): + """ check_bgp_af_other_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + max_load_ibgp_num = module.params['max_load_ibgp_num'] + if max_load_ibgp_num: + if int(max_load_ibgp_num) > 65535 or int(max_load_ibgp_num) < 1: + module.fail_json( + msg='Error: The value of max_load_ibgp_num %s is out of [1 - 65535].' % max_load_ibgp_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["max_load_ibgp_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != max_load_ibgp_num: + need_cfg = True + else: + need_cfg = True + + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + if ibgp_ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ibgp_ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ibgp_ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + max_load_ebgp_num = module.params['max_load_ebgp_num'] + if max_load_ebgp_num: + if int(max_load_ebgp_num) > 65535 or int(max_load_ebgp_num) < 1: + module.fail_json( + msg='Error: The value of max_load_ebgp_num %s is out of [1 - 65535].' % max_load_ebgp_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["max_load_ebgp_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != max_load_ebgp_num: + need_cfg = True + else: + need_cfg = True + + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + if ebgp_ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ebgp_ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + maximum_load_balance = module.params['maximum_load_balance'] + if maximum_load_balance: + if int(maximum_load_balance) > 65535 or int(maximum_load_balance) < 1: + module.fail_json( + msg='Error: The value of maximum_load_balance %s is out of [1 - 65535].' % maximum_load_balance) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["maximum_load_balance"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != maximum_load_balance: + need_cfg = True + else: + need_cfg = True + + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + if ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + default_local_pref = module.params['default_local_pref'] + if default_local_pref: + if int(default_local_pref) < 0: + module.fail_json( + msg='Error: The value of default_local_pref %s is out of [0 - 4294967295].' % default_local_pref) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_local_pref"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_local_pref: + need_cfg = True + else: + need_cfg = True + + default_med = module.params['default_med'] + if default_med: + if int(default_med) < 0: + module.fail_json( + msg='Error: The value of default_med %s is out of [0 - 4294967295].' % default_med) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_med: + need_cfg = True + else: + need_cfg = True + + default_rt_import_enable = module.params['default_rt_import_enable'] + if default_rt_import_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_import_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_rt_import_enable: + need_cfg = True + else: + need_cfg = True + + router_id = module.params['router_id'] + if router_id: + if len(router_id) > 255: + module.fail_json( + msg='Error: The len of router_id %s is out of [0 - 255].' % router_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vrf_rid_auto_sel"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != vrf_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + nexthop_third_party = module.params['nexthop_third_party'] + if nexthop_third_party != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["nexthop_third_party"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != nexthop_third_party: + need_cfg = True + else: + need_cfg = True + + summary_automatic = module.params['summary_automatic'] + if summary_automatic != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["summary_automatic"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != summary_automatic: + need_cfg = True + else: + need_cfg = True + + auto_frr_enable = module.params['auto_frr_enable'] + if auto_frr_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["auto_frr_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != auto_frr_enable: + need_cfg = True + else: + need_cfg = True + + load_balancing_as_path_ignore = module.params['load_balancing_as_path_ignore'] + if load_balancing_as_path_ignore != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["load_balancing_as_path_ignore"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != load_balancing_as_path_ignore: + need_cfg = True + else: + need_cfg = True + + rib_only_enable = module.params['rib_only_enable'] + if rib_only_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rib_only_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rib_only_enable: + need_cfg = True + else: + need_cfg = True + + rib_only_policy_name = module.params['rib_only_policy_name'] + if rib_only_policy_name: + if len(rib_only_policy_name) > 40 or len(rib_only_policy_name) < 1: + module.fail_json( + msg='Error: The len of rib_only_policy_name %s is out of [1 - 40].' % rib_only_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rib_only_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rib_only_policy_name: + need_cfg = True + else: + need_cfg = True + + active_route_advertise = module.params['active_route_advertise'] + if active_route_advertise != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["active_route_advertise"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != active_route_advertise: + need_cfg = True + else: + need_cfg = True + + as_path_neglect = module.params['as_path_neglect'] + if as_path_neglect != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_neglect"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != as_path_neglect: + need_cfg = True + else: + need_cfg = True + + med_none_as_maximum = module.params['med_none_as_maximum'] + if med_none_as_maximum != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["med_none_as_maximum"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != med_none_as_maximum: + need_cfg = True + else: + need_cfg = True + + router_id_neglect = module.params['router_id_neglect'] + if router_id_neglect != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id_neglect"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != router_id_neglect: + need_cfg = True + else: + need_cfg = True + + igp_metric_ignore = module.params['igp_metric_ignore'] + if igp_metric_ignore != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["igp_metric_ignore"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != igp_metric_ignore: + need_cfg = True + else: + need_cfg = True + + always_compare_med = module.params['always_compare_med'] + if always_compare_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["always_compare_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != always_compare_med: + need_cfg = True + else: + need_cfg = True + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["determin_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != determin_med: + need_cfg = True + else: + need_cfg = True + + preference_external = module.params['preference_external'] + if preference_external: + if int(preference_external) > 255 or int(preference_external) < 1: + module.fail_json( + msg='Error: The value of preference_external %s is out of [1 - 255].' % preference_external) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_external"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_external: + need_cfg = True + else: + need_cfg = True + + preference_internal = module.params['preference_internal'] + if preference_internal: + if int(preference_internal) > 255 or int(preference_internal) < 1: + module.fail_json( + msg='Error: The value of preference_internal %s is out of [1 - 255].' % preference_internal) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_internal"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_internal: + need_cfg = True + else: + need_cfg = True + + preference_local = module.params['preference_local'] + if preference_local: + if int(preference_local) > 255 or int(preference_local) < 1: + module.fail_json( + msg='Error: The value of preference_local %s is out of [1 - 255].' % preference_local) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_local"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_local: + need_cfg = True + else: + need_cfg = True + + prefrence_policy_name = module.params['prefrence_policy_name'] + if prefrence_policy_name: + if len(prefrence_policy_name) > 40 or len(prefrence_policy_name) < 1: + module.fail_json( + msg='Error: The len of prefrence_policy_name %s is out of [1 - 40].' % prefrence_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prefrence_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != prefrence_policy_name: + need_cfg = True + else: + need_cfg = True + + reflect_between_client = module.params['reflect_between_client'] + if reflect_between_client != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_between_client"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflect_between_client: + need_cfg = True + else: + need_cfg = True + + reflector_cluster_id = module.params['reflector_cluster_id'] + if reflector_cluster_id: + if int(reflector_cluster_id) < 0: + module.fail_json( + msg='Error: The value of reflector_cluster_id %s is out of ' + '[1 - 4294967295].' % reflector_cluster_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflector_cluster_id"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflector_cluster_id: + need_cfg = True + else: + need_cfg = True + + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + if reflector_cluster_ipv4: + if len(reflector_cluster_ipv4) > 255: + module.fail_json( + msg='Error: The len of reflector_cluster_ipv4 %s is out of [0 - 255].' % reflector_cluster_ipv4) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflector_cluster_ipv4"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflector_cluster_ipv4: + need_cfg = True + else: + need_cfg = True + + rr_filter_number = module.params['rr_filter_number'] + if rr_filter_number: + if len(rr_filter_number) > 51 or len(rr_filter_number) < 1: + module.fail_json( + msg='Error: The len of rr_filter_number %s is out of [1 - 51].' % rr_filter_number) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rr_filter_number"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rr_filter_number: + need_cfg = True + else: + need_cfg = True + + policy_vpn_target = module.params['policy_vpn_target'] + if policy_vpn_target != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["policy_vpn_target"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != policy_vpn_target: + need_cfg = True + else: + need_cfg = True + + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + if next_hop_sel_depend_type: + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["next_hop_sel_depend_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != next_hop_sel_depend_type: + need_cfg = True + else: + need_cfg = True + + nhp_relay_route_policy_name = module.params[ + 'nhp_relay_route_policy_name'] + if nhp_relay_route_policy_name: + if len(nhp_relay_route_policy_name) > 40 or len(nhp_relay_route_policy_name) < 1: + module.fail_json( + msg='Error: The len of nhp_relay_route_policy_name %s is ' + 'out of [1 - 40].' % nhp_relay_route_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["nhp_relay_route_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != nhp_relay_route_policy_name: + need_cfg = True + else: + need_cfg = True + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + + reflect_chg_path = module.params['reflect_chg_path'] + if reflect_chg_path != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_chg_path"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflect_chg_path: + need_cfg = True + else: + need_cfg = True + + add_path_sel_num = module.params['add_path_sel_num'] + if add_path_sel_num: + if int(add_path_sel_num) > 64 or int(add_path_sel_num) < 2: + module.fail_json( + msg='Error: The value of add_path_sel_num %s is out of [2 - 64].' % add_path_sel_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["add_path_sel_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != add_path_sel_num: + need_cfg = True + else: + need_cfg = True + + route_sel_delay = module.params['route_sel_delay'] + if route_sel_delay: + if int(route_sel_delay) > 3600 or int(route_sel_delay) < 0: + module.fail_json( + msg='Error: The value of route_sel_delay %s is out of [0 - 3600].' % route_sel_delay) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_sel_delay"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != route_sel_delay: + need_cfg = True + else: + need_cfg = True + + allow_invalid_as = module.params['allow_invalid_as'] + if allow_invalid_as != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_invalid_as"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != allow_invalid_as: + need_cfg = True + else: + need_cfg = True + + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + if policy_ext_comm_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["policy_ext_comm_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != policy_ext_comm_enable: + need_cfg = True + else: + need_cfg = True + + supernet_uni_adv = module.params['supernet_uni_adv'] + if supernet_uni_adv != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["supernet_uni_adv"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != supernet_uni_adv: + need_cfg = True + else: + need_cfg = True + + supernet_label_adv = module.params['supernet_label_adv'] + if supernet_label_adv != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["supernet_label_adv"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != supernet_label_adv: + need_cfg = True + else: + need_cfg = True + + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + if ingress_lsp_policy_name: + if len(ingress_lsp_policy_name) > 40 or len(ingress_lsp_policy_name) < 1: + module.fail_json( + msg='Error: The len of ingress_lsp_policy_name %s is out of [1 - 40].' % ingress_lsp_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ingress_lsp_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ingress_lsp_policy_name: + need_cfg = True + else: + need_cfg = True + + originator_prior = module.params['originator_prior'] + if originator_prior != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["originator_prior"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != originator_prior: + need_cfg = True + else: + need_cfg = True + + lowest_priority = module.params['lowest_priority'] + if lowest_priority != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["lowest_priority"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != lowest_priority: + need_cfg = True + else: + need_cfg = True + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["relay_delay_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != relay_delay_enable: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_import_network_route(self, **kwargs): + """ check_bgp_import_network_route """ + + module = kwargs["module"] + result = dict() + import_need_cfg = False + network_need_cfg = False + + vrf_name = module.params['vrf_name'] + + state = module.params['state'] + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol and (import_protocol != "direct" and import_protocol != "static"): + if not import_process_id: + module.fail_json( + msg='Error: Please input import_protocol and import_process_id value at the same time.') + else: + if int(import_process_id) < 0: + module.fail_json( + msg='Error: The value of import_process_id %s is out of [0 - 4294967295].' % import_process_id) + + if import_process_id: + if not import_protocol: + module.fail_json( + msg='Error: Please input import_protocol and import_process_id value at the same time.') + + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + if network_address: + if not mask_len: + module.fail_json( + msg='Error: Please input network_address and mask_len value at the same time.') + if mask_len: + if not network_address: + module.fail_json( + msg='Error: Please input network_address and mask_len value at the same time.') + + conf_str = CE_GET_BGP_IMPORT_AND_NETWORK_ROUTE % (vrf_name, af_type) + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if import_protocol: + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + else: + if not import_process_id or import_process_id == "0": + module.fail_json( + msg='Error: Please input import_process_id not 0 when import_protocol is ' + '[ospf, isis, rip, ospfv3, ripng].') + + bgp_import_route_new = (import_protocol, import_process_id) + + if state == "present": + if "" in recv_xml: + import_need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', + recv_xml) + + if re_find: + result["bgp_import_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_import_route_new not in re_find: + import_need_cfg = True + else: + import_need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', + recv_xml) + + if re_find: + result["bgp_import_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_import_route_new in re_find: + import_need_cfg = True + + if network_address and mask_len: + + bgp_network_route_new = (network_address, mask_len) + + if not check_ip_addr(ipaddr=network_address): + module.fail_json( + msg='Error: The network_address %s is invalid.' % network_address) + + if len(mask_len) > 128: + module.fail_json( + msg='Error: The len of mask_len %s is out of [0 - 128].' % mask_len) + + if state == "present": + if "" in recv_xml: + network_need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', recv_xml) + + if re_find: + result["bgp_network_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_network_route_new not in re_find: + network_need_cfg = True + else: + network_need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', recv_xml) + + if re_find: + result["bgp_network_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_network_route_new in re_find: + network_need_cfg = True + + result["import_need_cfg"] = import_need_cfg + result["network_need_cfg"] = network_need_cfg + return result + + def merge_bgp_af(self, **kwargs): + """ merge_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp address family failed.') + + cmds = [] + + cmd = "ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def create_bgp_af(self, **kwargs): + """ create_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_CREATE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_CREATE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp address family failed.') + + cmds = [] + + cmd = "ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def delete_bgp_af(self, **kwargs): + """ delete_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_DELETE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_DELETE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp address family failed.') + + cmds = [] + + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "undo ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "undo ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "undo ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "undo ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def merge_bgp_af_other(self, **kwargs): + """ merge_bgp_af_other """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + + cmds = [] + + max_load_ibgp_num = module.params['max_load_ibgp_num'] + if max_load_ibgp_num: + conf_str += "%s" % max_load_ibgp_num + + cmd = "maximum load-balancing ibgp %s" % max_load_ibgp_num + cmds.append(cmd) + + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + if ibgp_ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ibgp_ecmp_nexthop_changed + + if ibgp_ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing ibgp %s ecmp-nexthop-changed" % max_load_ibgp_num + cmds.append(cmd) + else: + cmd = "undo maximum load-balancing ibgp %s ecmp-nexthop-changed" % max_load_ibgp_num + cmds.append(cmd) + max_load_ebgp_num = module.params['max_load_ebgp_num'] + if max_load_ebgp_num: + conf_str += "%s" % max_load_ebgp_num + + cmd = "maximum load-balancing ebgp %s" % max_load_ebgp_num + cmds.append(cmd) + + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + if ebgp_ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ebgp_ecmp_nexthop_changed + + if ebgp_ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing ebgp %s ecmp-nexthop-changed" % max_load_ebgp_num + else: + cmd = "undo maximum load-balancing ebgp %s ecmp-nexthop-changed" % max_load_ebgp_num + cmds.append(cmd) + + maximum_load_balance = module.params['maximum_load_balance'] + if maximum_load_balance: + conf_str += "%s" % maximum_load_balance + + cmd = "maximum load-balancing %s" % maximum_load_balance + cmds.append(cmd) + + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + if ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ecmp_nexthop_changed + + if ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing %s ecmp-nexthop-changed" % maximum_load_balance + else: + cmd = "undo maximum load-balancing %s ecmp-nexthop-changed" % maximum_load_balance + cmds.append(cmd) + + default_local_pref = module.params['default_local_pref'] + if default_local_pref: + conf_str += "%s" % default_local_pref + + cmd = "default local-preference %s" % default_local_pref + cmds.append(cmd) + + default_med = module.params['default_med'] + if default_med: + conf_str += "%s" % default_med + + cmd = "default med %s" % default_med + cmds.append(cmd) + + default_rt_import_enable = module.params['default_rt_import_enable'] + if default_rt_import_enable != 'no_use': + conf_str += "%s" % default_rt_import_enable + + if default_rt_import_enable == "true": + cmd = "default-route imported" + else: + cmd = "undo default-route imported" + cmds.append(cmd) + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "router-id %s" % router_id + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + family = "ipv4-family" + if af_type == "ipv6uni": + family = "ipv6-family" + if vrf_rid_auto_sel == "true": + cmd = "%s vpn-instance %s" % (family, vrf_name) + cmds.append(cmd) + cmd = "router-id auto-select" + cmds.append(cmd) + else: + cmd = "%s vpn-instance %s" % (family, vrf_name) + cmds.append(cmd) + cmd = "undo router-id auto-select" + cmds.append(cmd) + + nexthop_third_party = module.params['nexthop_third_party'] + if nexthop_third_party != 'no_use': + conf_str += "%s" % nexthop_third_party + + if nexthop_third_party == "true": + cmd = "nexthop third-party" + else: + cmd = "undo nexthop third-party" + cmds.append(cmd) + + summary_automatic = module.params['summary_automatic'] + if summary_automatic != 'no_use': + conf_str += "%s" % summary_automatic + + if summary_automatic == "true": + cmd = "summary automatic" + else: + cmd = "undo summary automatic" + cmds.append(cmd) + + auto_frr_enable = module.params['auto_frr_enable'] + if auto_frr_enable != 'no_use': + conf_str += "%s" % auto_frr_enable + + if auto_frr_enable == "true": + cmd = "auto-frr" + else: + cmd = "undo auto-frr" + cmds.append(cmd) + + load_balancing_as_path_ignore = module.params[ + 'load_balancing_as_path_ignore'] + if load_balancing_as_path_ignore != 'no_use': + conf_str += "%s" % load_balancing_as_path_ignore + + if load_balancing_as_path_ignore == "true": + cmd = "load-balancing as-path-ignore" + else: + cmd = "undo load-balancing as-path-ignore" + cmds.append(cmd) + + rib_only_enable = module.params['rib_only_enable'] + if rib_only_enable != 'no_use': + conf_str += "%s" % rib_only_enable + + if rib_only_enable == "true": + cmd = "routing-table rib-only" + else: + cmd = "undo routing-table rib-only" + cmds.append(cmd) + + rib_only_policy_name = module.params['rib_only_policy_name'] + if rib_only_policy_name and rib_only_enable == "true": + conf_str += "%s" % rib_only_policy_name + + cmd = "routing-table rib-only route-policy %s" % rib_only_policy_name + cmds.append(cmd) + + active_route_advertise = module.params['active_route_advertise'] + if active_route_advertise != 'no_use': + conf_str += "%s" % active_route_advertise + + if active_route_advertise == "true": + cmd = "active-route-advertise" + else: + cmd = "undo active-route-advertise" + cmds.append(cmd) + + as_path_neglect = module.params['as_path_neglect'] + if as_path_neglect != 'no_use': + conf_str += "%s" % as_path_neglect + + if as_path_neglect == "true": + cmd = "bestroute as-path-ignore" + else: + cmd = "undo bestroute as-path-ignore" + cmds.append(cmd) + + med_none_as_maximum = module.params['med_none_as_maximum'] + if med_none_as_maximum != 'no_use': + conf_str += "%s" % med_none_as_maximum + + if med_none_as_maximum == "true": + cmd = "bestroute med-none-as-maximum" + else: + cmd = "undo bestroute med-none-as-maximum" + cmds.append(cmd) + + router_id_neglect = module.params['router_id_neglect'] + if router_id_neglect != 'no_use': + conf_str += "%s" % router_id_neglect + + if router_id_neglect == "true": + cmd = "bestroute router-id-ignore" + else: + cmd = "undo bestroute router-id-ignore" + cmds.append(cmd) + + igp_metric_ignore = module.params['igp_metric_ignore'] + if igp_metric_ignore != 'no_use': + conf_str += "%s" % igp_metric_ignore + + if igp_metric_ignore == "true": + cmd = "bestroute igp-metric-ignore" + cmds.append(cmd) + else: + cmd = "undo bestroute igp-metric-ignore" + cmds.append(cmd) + always_compare_med = module.params['always_compare_med'] + if always_compare_med != 'no_use': + conf_str += "%s" % always_compare_med + + if always_compare_med == "true": + cmd = "compare-different-as-med" + cmds.append(cmd) + else: + cmd = "undo compare-different-as-med" + cmds.append(cmd) + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + conf_str += "%s" % determin_med + + if determin_med == "true": + cmd = "deterministic-med" + cmds.append(cmd) + else: + cmd = "undo deterministic-med" + cmds.append(cmd) + + preference_external = module.params['preference_external'] + preference_internal = module.params['preference_internal'] + preference_local = module.params['preference_local'] + if any([preference_external, preference_internal, preference_local]): + preference_external = preference_external or "255" + preference_internal = preference_internal or "255" + preference_local = preference_local or "255" + + conf_str += "%s" % preference_external + conf_str += "%s" % preference_internal + conf_str += "%s" % preference_local + + cmd = "preference %s %s %s" % ( + preference_external, preference_internal, preference_local) + cmds.append(cmd) + + prefrence_policy_name = module.params['prefrence_policy_name'] + if prefrence_policy_name: + conf_str += "%s" % prefrence_policy_name + + cmd = "preference route-policy %s" % prefrence_policy_name + cmds.append(cmd) + + reflect_between_client = module.params['reflect_between_client'] + if reflect_between_client != 'no_use': + conf_str += "%s" % reflect_between_client + + if reflect_between_client == "true": + cmd = "reflect between-clients" + else: + cmd = "undo reflect between-clients" + cmds.append(cmd) + + reflector_cluster_id = module.params['reflector_cluster_id'] + if reflector_cluster_id: + conf_str += "%s" % reflector_cluster_id + + cmd = "reflector cluster-id %s" % reflector_cluster_id + cmds.append(cmd) + + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + if reflector_cluster_ipv4: + conf_str += "%s" % reflector_cluster_ipv4 + + cmd = "reflector cluster-id %s" % reflector_cluster_ipv4 + cmds.append(cmd) + + rr_filter_number = module.params['rr_filter_number'] + if rr_filter_number: + conf_str += "%s" % rr_filter_number + cmd = 'rr-filter %s' % rr_filter_number + cmds.append(cmd) + + policy_vpn_target = module.params['policy_vpn_target'] + if policy_vpn_target != 'no_use': + conf_str += "%s" % policy_vpn_target + if policy_vpn_target == 'true': + cmd = 'policy vpn-target' + else: + cmd = 'undo policy vpn-target' + cmds.append(cmd) + + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + if next_hop_sel_depend_type: + conf_str += "%s" % next_hop_sel_depend_type + + nhp_relay_route_policy_name = module.params[ + 'nhp_relay_route_policy_name'] + if nhp_relay_route_policy_name: + conf_str += "%s" % nhp_relay_route_policy_name + + cmd = "nexthop recursive-lookup route-policy %s" % nhp_relay_route_policy_name + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + if ebgp_if_sensitive == "true": + cmd = "ebgp-interface-sensitive" + else: + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + reflect_chg_path = module.params['reflect_chg_path'] + if reflect_chg_path != 'no_use': + conf_str += "%s" % reflect_chg_path + + if reflect_chg_path == "true": + cmd = "reflect change-path-attribute" + else: + cmd = "undo reflect change-path-attribute" + cmds.append(cmd) + + add_path_sel_num = module.params['add_path_sel_num'] + if add_path_sel_num: + conf_str += "%s" % add_path_sel_num + + cmd = "bestroute add-path path-number %s" % add_path_sel_num + cmds.append(cmd) + + route_sel_delay = module.params['route_sel_delay'] + if route_sel_delay: + conf_str += "%s" % route_sel_delay + + cmd = "route-select delay %s" % route_sel_delay + cmds.append(cmd) + + allow_invalid_as = module.params['allow_invalid_as'] + if allow_invalid_as != 'no_use': + conf_str += "%s" % allow_invalid_as + + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + if policy_ext_comm_enable != 'no_use': + conf_str += "%s" % policy_ext_comm_enable + + if policy_ext_comm_enable == "true": + cmd = "ext-community-change enable" + else: + cmd = "undo ext-community-change enable" + cmds.append(cmd) + + supernet_uni_adv = module.params['supernet_uni_adv'] + if supernet_uni_adv != 'no_use': + conf_str += "%s" % supernet_uni_adv + + if supernet_uni_adv == "true": + cmd = "supernet unicast advertise enable" + else: + cmd = "undo supernet unicast advertise enable" + cmds.append(cmd) + + supernet_label_adv = module.params['supernet_label_adv'] + if supernet_label_adv != 'no_use': + conf_str += "%s" % supernet_label_adv + + if supernet_label_adv == "true": + cmd = "supernet label-route advertise enable" + else: + cmd = "undo supernet label-route advertise enable" + cmds.append(cmd) + + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + if ingress_lsp_policy_name: + conf_str += "%s" % ingress_lsp_policy_name + cmd = "ingress-lsp trigger route-policy %s" % ingress_lsp_policy_name + cmds.append(cmd) + + originator_prior = module.params['originator_prior'] + if originator_prior != 'no_use': + conf_str += "%s" % originator_prior + if originator_prior == "true": + cmd = "bestroute routerid-prior-clusterlist" + else: + cmd = "undo bestroute routerid-prior-clusterlist" + cmds.append(cmd) + + lowest_priority = module.params['lowest_priority'] + if lowest_priority != 'no_use': + conf_str += "%s" % lowest_priority + + if lowest_priority == "true": + cmd = "advertise lowest-priority on-startup" + else: + cmd = "undo advertise lowest-priority on-startup" + cmds.append(cmd) + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + conf_str += "%s" % relay_delay_enable + + if relay_delay_enable == "true": + cmd = "nexthop recursive-lookup restrain enable" + else: + cmd = "nexthop recursive-lookup restrain disable" + cmds.append(cmd) + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge bgp address family other agrus failed.') + + return cmds + + def delete_bgp_af_other(self, **kwargs): + """ delete_bgp_af_other """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "" + + cmd = "undo router-id %s" % router_id + cmds.append(cmd) + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + conf_str += "" + + cmd = "undo deterministic-med" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "" + + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + conf_str += "" + + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge bgp address family other agrus failed.') + + return cmds + + def merge_bgp_import_route(self, **kwargs): + """ merge_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_MERGE_BGP_IMPORT_ROUTE_HEADER % ( + vrf_name, af_type, import_protocol, import_process_id) + CE_MERGE_BGP_IMPORT_ROUTE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp import route failed.') + + cmds = [] + cmd = "import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def create_bgp_import_route(self, **kwargs): + """ create_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_CREATE_BGP_IMPORT_ROUTE % ( + vrf_name, af_type, import_protocol, import_process_id) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp import route failed.') + + cmds = [] + cmd = "import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def delete_bgp_import_route(self, **kwargs): + """ delete_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_DELETE_BGP_IMPORT_ROUTE % ( + vrf_name, af_type, import_protocol, import_process_id) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp import route failed.') + + cmds = [] + cmd = "undo import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "undo import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def merge_bgp_network_route(self, **kwargs): + """ merge_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_MERGE_BGP_NETWORK_ROUTE_HEADER % ( + vrf_name, af_type, network_address, mask_len) + CE_MERGE_BGP_NETWORK_ROUTE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp network route failed.') + + cmds = [] + cmd = "network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + def create_bgp_network_route(self, **kwargs): + """ create_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_CREATE_BGP_NETWORK_ROUTE % ( + vrf_name, af_type, network_address, mask_len) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp network route failed.') + + cmds = [] + cmd = "network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + def delete_bgp_network_route(self, **kwargs): + """ delete_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_DELETE_BGP_NETWORK_ROUTE % ( + vrf_name, af_type, network_address, mask_len) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp network route failed.') + + cmds = [] + cmd = "undo network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + af_type=dict(choices=['ipv4uni', 'ipv4multi', 'ipv4vpn', + 'ipv6uni', 'ipv6vpn', 'evpn'], required=True), + max_load_ibgp_num=dict(type='str'), + ibgp_ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + max_load_ebgp_num=dict(type='str'), + ebgp_ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + maximum_load_balance=dict(type='str'), + ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_local_pref=dict(type='str'), + default_med=dict(type='str'), + default_rt_import_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id=dict(type='str'), + vrf_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + nexthop_third_party=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + summary_automatic=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + auto_frr_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + load_balancing_as_path_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rib_only_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rib_only_policy_name=dict(type='str'), + active_route_advertise=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + as_path_neglect=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + med_none_as_maximum=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id_neglect=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + igp_metric_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + always_compare_med=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + determin_med=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + preference_external=dict(type='str'), + preference_internal=dict(type='str'), + preference_local=dict(type='str'), + prefrence_policy_name=dict(type='str'), + reflect_between_client=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + reflector_cluster_id=dict(type='str'), + reflector_cluster_ipv4=dict(type='str'), + rr_filter_number=dict(type='str'), + policy_vpn_target=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + next_hop_sel_depend_type=dict( + choices=['default', 'dependTunnel', 'dependIp']), + nhp_relay_route_policy_name=dict(type='str'), + ebgp_if_sensitive=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + reflect_chg_path=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + add_path_sel_num=dict(type='str'), + route_sel_delay=dict(type='str'), + allow_invalid_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + policy_ext_comm_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + supernet_uni_adv=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + supernet_label_adv=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + ingress_lsp_policy_name=dict(type='str'), + originator_prior=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + lowest_priority=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + relay_delay_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + import_protocol=dict( + choices=['direct', 'ospf', 'isis', 'static', 'rip', 'ospfv3', 'ripng']), + import_process_id=dict(type='str'), + network_address=dict(type='str'), + mask_len=dict(type='str')) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + max_load_ibgp_num = module.params['max_load_ibgp_num'] + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + max_load_ebgp_num = module.params['max_load_ebgp_num'] + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + maximum_load_balance = module.params['maximum_load_balance'] + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + default_local_pref = module.params['default_local_pref'] + default_med = module.params['default_med'] + default_rt_import_enable = module.params['default_rt_import_enable'] + router_id = module.params['router_id'] + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + nexthop_third_party = module.params['nexthop_third_party'] + summary_automatic = module.params['summary_automatic'] + auto_frr_enable = module.params['auto_frr_enable'] + load_balancing_as_path_ignore = module.params[ + 'load_balancing_as_path_ignore'] + rib_only_enable = module.params['rib_only_enable'] + rib_only_policy_name = module.params['rib_only_policy_name'] + active_route_advertise = module.params['active_route_advertise'] + as_path_neglect = module.params['as_path_neglect'] + med_none_as_maximum = module.params['med_none_as_maximum'] + router_id_neglect = module.params['router_id_neglect'] + igp_metric_ignore = module.params['igp_metric_ignore'] + always_compare_med = module.params['always_compare_med'] + determin_med = module.params['determin_med'] + preference_external = module.params['preference_external'] + preference_internal = module.params['preference_internal'] + preference_local = module.params['preference_local'] + prefrence_policy_name = module.params['prefrence_policy_name'] + reflect_between_client = module.params['reflect_between_client'] + reflector_cluster_id = module.params['reflector_cluster_id'] + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + rr_filter_number = module.params['rr_filter_number'] + policy_vpn_target = module.params['policy_vpn_target'] + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + nhp_relay_route_policy_name = module.params['nhp_relay_route_policy_name'] + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + reflect_chg_path = module.params['reflect_chg_path'] + add_path_sel_num = module.params['add_path_sel_num'] + route_sel_delay = module.params['route_sel_delay'] + allow_invalid_as = module.params['allow_invalid_as'] + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + supernet_uni_adv = module.params['supernet_uni_adv'] + supernet_label_adv = module.params['supernet_label_adv'] + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + originator_prior = module.params['originator_prior'] + lowest_priority = module.params['lowest_priority'] + relay_delay_enable = module.params['relay_delay_enable'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + ce_bgp_af_obj = BgpAf() + + if not ce_bgp_af_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if af_type: + proposed["af_type"] = af_type + if max_load_ibgp_num: + proposed["max_load_ibgp_num"] = max_load_ibgp_num + if ibgp_ecmp_nexthop_changed != 'no_use': + proposed["ibgp_ecmp_nexthop_changed"] = ibgp_ecmp_nexthop_changed + if max_load_ebgp_num: + proposed["max_load_ebgp_num"] = max_load_ebgp_num + if ebgp_ecmp_nexthop_changed != 'no_use': + proposed["ebgp_ecmp_nexthop_changed"] = ebgp_ecmp_nexthop_changed + if maximum_load_balance: + proposed["maximum_load_balance"] = maximum_load_balance + if ecmp_nexthop_changed != 'no_use': + proposed["ecmp_nexthop_changed"] = ecmp_nexthop_changed + if default_local_pref: + proposed["default_local_pref"] = default_local_pref + if default_med: + proposed["default_med"] = default_med + if default_rt_import_enable != 'no_use': + proposed["default_rt_import_enable"] = default_rt_import_enable + if router_id: + proposed["router_id"] = router_id + if vrf_rid_auto_sel != 'no_use': + proposed["vrf_rid_auto_sel"] = vrf_rid_auto_sel + if nexthop_third_party != 'no_use': + proposed["nexthop_third_party"] = nexthop_third_party + if summary_automatic != 'no_use': + proposed["summary_automatic"] = summary_automatic + if auto_frr_enable != 'no_use': + proposed["auto_frr_enable"] = auto_frr_enable + if load_balancing_as_path_ignore != 'no_use': + proposed["load_balancing_as_path_ignore"] = load_balancing_as_path_ignore + if rib_only_enable != 'no_use': + proposed["rib_only_enable"] = rib_only_enable + if rib_only_policy_name: + proposed["rib_only_policy_name"] = rib_only_policy_name + if active_route_advertise != 'no_use': + proposed["active_route_advertise"] = active_route_advertise + if as_path_neglect != 'no_use': + proposed["as_path_neglect"] = as_path_neglect + if med_none_as_maximum != 'no_use': + proposed["med_none_as_maximum"] = med_none_as_maximum + if router_id_neglect != 'no_use': + proposed["router_id_neglect"] = router_id_neglect + if igp_metric_ignore != 'no_use': + proposed["igp_metric_ignore"] = igp_metric_ignore + if always_compare_med != 'no_use': + proposed["always_compare_med"] = always_compare_med + if determin_med != 'no_use': + proposed["determin_med"] = determin_med + if preference_external: + proposed["preference_external"] = preference_external + if preference_internal: + proposed["preference_internal"] = preference_internal + if preference_local: + proposed["preference_local"] = preference_local + if prefrence_policy_name: + proposed["prefrence_policy_name"] = prefrence_policy_name + if reflect_between_client != 'no_use': + proposed["reflect_between_client"] = reflect_between_client + if reflector_cluster_id: + proposed["reflector_cluster_id"] = reflector_cluster_id + if reflector_cluster_ipv4: + proposed["reflector_cluster_ipv4"] = reflector_cluster_ipv4 + if rr_filter_number: + proposed["rr_filter_number"] = rr_filter_number + if policy_vpn_target != 'no_use': + proposed["policy_vpn_target"] = policy_vpn_target + if next_hop_sel_depend_type: + proposed["next_hop_sel_depend_type"] = next_hop_sel_depend_type + if nhp_relay_route_policy_name: + proposed["nhp_relay_route_policy_name"] = nhp_relay_route_policy_name + if ebgp_if_sensitive != 'no_use': + proposed["ebgp_if_sensitive"] = ebgp_if_sensitive + if reflect_chg_path != 'no_use': + proposed["reflect_chg_path"] = reflect_chg_path + if add_path_sel_num: + proposed["add_path_sel_num"] = add_path_sel_num + if route_sel_delay: + proposed["route_sel_delay"] = route_sel_delay + if allow_invalid_as != 'no_use': + proposed["allow_invalid_as"] = allow_invalid_as + if policy_ext_comm_enable != 'no_use': + proposed["policy_ext_comm_enable"] = policy_ext_comm_enable + if supernet_uni_adv != 'no_use': + proposed["supernet_uni_adv"] = supernet_uni_adv + if supernet_label_adv != 'no_use': + proposed["supernet_label_adv"] = supernet_label_adv + if ingress_lsp_policy_name: + proposed["ingress_lsp_policy_name"] = ingress_lsp_policy_name + if originator_prior != 'no_use': + proposed["originator_prior"] = originator_prior + if lowest_priority != 'no_use': + proposed["lowest_priority"] = lowest_priority + if relay_delay_enable != 'no_use': + proposed["relay_delay_enable"] = relay_delay_enable + if import_protocol: + proposed["import_protocol"] = import_protocol + if import_process_id: + proposed["import_process_id"] = import_process_id + if network_address: + proposed["network_address"] = network_address + if mask_len: + proposed["mask_len"] = mask_len + + bgp_af_rst = ce_bgp_af_obj.check_bgp_af_args(module=module) + bgp_af_other_rst = ce_bgp_af_obj.check_bgp_af_other_args(module=module) + bgp_af_other_can_del_rst = ce_bgp_af_obj.check_bgp_af_other_can_del( + module=module) + bgp_import_network_route_rst = ce_bgp_af_obj.check_bgp_import_network_route( + module=module) + + # state exist bgp address family config + exist_tmp = dict() + for item in bgp_af_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_af_rst[item] + + if exist_tmp: + existing["bgp af"] = exist_tmp + # state exist bgp address family other config + exist_tmp = dict() + for item in bgp_af_other_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_af_other_rst[item] + if exist_tmp: + existing["bgp af other"] = exist_tmp + # state exist bgp import route config + exist_tmp = dict() + for item in bgp_import_network_route_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_import_network_route_rst[item] + + if exist_tmp: + existing["bgp import & network route"] = exist_tmp + + if state == "present": + if bgp_af_rst["need_cfg"] and bgp_import_network_route_rst["import_need_cfg"] and \ + bgp_import_network_route_rst["network_need_cfg"]: + changed = True + if "af_type" in bgp_af_rst.keys(): + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + else: + conf_str = CE_CREATE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_IMPORT_UNIT % ( + import_protocol, import_process_id) + else: + conf_str += CE_BGP_CREATE_IMPORT_UNIT % ( + import_protocol, import_process_id) + + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_NETWORK_UNIT % ( + network_address, mask_len) + else: + conf_str += CE_BGP_CREATE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Present bgp af_type import and network route failed.') + + cmd = "import-route %s %s" % (import_protocol, import_process_id) + updates.append(cmd) + cmd = "network %s %s" % (network_address, mask_len) + updates.append(cmd) + + elif bgp_import_network_route_rst["import_need_cfg"] and bgp_import_network_route_rst["network_need_cfg"]: + changed = True + conf_str = CE_BGP_IMPORT_NETWORK_ROUTE_HEADER % (vrf_name, af_type) + + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_IMPORT_UNIT % ( + import_protocol, import_process_id) + else: + conf_str += CE_BGP_CREATE_IMPORT_UNIT % ( + import_protocol, import_process_id) + + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_NETWORK_UNIT % ( + network_address, mask_len) + else: + conf_str += CE_BGP_CREATE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_BGP_IMPORT_NETWORK_ROUTE_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Present bgp import and network route failed.') + + cmd = "import-route %s %s" % (import_protocol, import_process_id) + updates.append(cmd) + cmd = "network %s %s" % (network_address, mask_len) + updates.append(cmd) + + else: + if bgp_af_rst["need_cfg"]: + if "af_type" in bgp_af_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_rst["need_cfg"]: + cmd = ce_bgp_af_obj.merge_bgp_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["import_need_cfg"]: + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["network_need_cfg"]: + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if bgp_import_network_route_rst["import_need_cfg"] and bgp_import_network_route_rst["network_need_cfg"]: + changed = True + conf_str = CE_BGP_IMPORT_NETWORK_ROUTE_HEADER % (vrf_name, af_type) + conf_str += CE_BGP_DELETE_IMPORT_UNIT % ( + import_protocol, import_process_id) + conf_str += CE_BGP_DELETE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_BGP_IMPORT_NETWORK_ROUTE_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Absent bgp import and network route failed.') + + cmd = "undo import-route %s %s" % (import_protocol, + import_process_id) + updates.append(cmd) + cmd = "undo network %s %s" % (network_address, mask_len) + updates.append(cmd) + + else: + if bgp_import_network_route_rst["import_need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["network_need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_can_del_rst["need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_rst["need_cfg"] and not bgp_af_other_can_del_rst["need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_rst["need_cfg"]: + pass + + # state end bgp address family config + bgp_af_rst = ce_bgp_af_obj.check_bgp_af_args(module=module) + end_tmp = dict() + for item in bgp_af_rst: + if item != "need_cfg": + end_tmp[item] = bgp_af_rst[item] + if end_tmp: + end_state["bgp af"] = end_tmp + # state end bgp address family other config + bgp_af_other_rst = ce_bgp_af_obj.check_bgp_af_other_args(module=module) + end_tmp = dict() + for item in bgp_af_other_rst: + if item != "need_cfg": + end_tmp[item] = bgp_af_other_rst[item] + if end_tmp: + end_state["bgp af other"] = end_tmp + # state end bgp import route config + bgp_import_network_route_rst = ce_bgp_af_obj.check_bgp_import_network_route( + module=module) + end_tmp = dict() + for item in bgp_import_network_route_rst: + if item != "need_cfg": + end_tmp[item] = bgp_import_network_route_rst[item] + if end_tmp: + end_state["bgp import & network route"] = end_tmp + if end_state == existing: + changed = False + updates = list() + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor.py new file mode 100644 index 00000000..b4a940f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor.py @@ -0,0 +1,2047 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_neighbor +short_description: Manages BGP peer configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP peer configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + required: true + peer_addr: + description: + - Connection address of a peer, which can be an IPv4 or IPv6 address. + required: true + remote_as: + description: + - AS number of a peer. + The value is a string of 1 to 11 characters. + required: true + description: + description: + - Description of a peer, which can be letters or digits. + The value is a string of 1 to 80 characters. + fake_as: + description: + - Fake AS number that is specified for a local peer. + The value is a string of 1 to 11 characters. + dual_as: + description: + - If the value is true, the EBGP peer can use either a fake AS number or the actual AS number. + If the value is false, the EBGP peer can only use a fake AS number. + choices: ['no_use','true','false'] + default: no_use + conventional: + description: + - If the value is true, the router has all extended capabilities. + If the value is false, the router does not have all extended capabilities. + choices: ['no_use','true','false'] + default: no_use + route_refresh: + description: + - If the value is true, BGP is enabled to advertise REFRESH packets. + If the value is false, the route refresh function is enabled. + choices: ['no_use','true','false'] + default: no_use + is_ignore: + description: + - If the value is true, the session with a specified peer is torn down and all related + routing entries are cleared. + If the value is false, the session with a specified peer is retained. + choices: ['no_use','true','false'] + default: no_use + local_if_name: + description: + - Name of a source interface that sends BGP packets. + The value is a string of 1 to 63 characters. + ebgp_max_hop: + description: + - Maximum number of hops in an indirect EBGP connection. + The value is an ranging from 1 to 255. + valid_ttl_hops: + description: + - Enable GTSM on a peer or peer group. + The valid-TTL-Value parameter is used to specify the number of TTL hops to be detected. + The value is an integer ranging from 1 to 255. + connect_mode: + description: + - The value can be Connect-only, Listen-only, or Both. + is_log_change: + description: + - If the value is true, BGP is enabled to record peer session status and event information. + If the value is false, BGP is disabled from recording peer session status and event information. + choices: ['no_use','true','false'] + default: no_use + pswd_type: + description: + - Enable BGP peers to establish a TCP connection and perform the Message Digest 5 (MD5) + authentication for BGP messages. + choices: ['null','cipher','simple'] + pswd_cipher_text: + description: + - The character string in a password identifies the contents of the password, spaces not supported. + The value is a string of 1 to 255 characters. + keep_alive_time: + description: + - Specify the Keepalive time of a peer or peer group. + The value is an integer ranging from 0 to 21845. The default value is 60. + hold_time: + description: + - Specify the Hold time of a peer or peer group. + The value is 0 or an integer ranging from 3 to 65535. + min_hold_time: + description: + - Specify the Min hold time of a peer or peer group. + key_chain_name: + description: + - Specify the Keychain authentication name used when BGP peers establish a TCP connection. + The value is a string of 1 to 47 case-insensitive characters. + conn_retry_time: + description: + - ConnectRetry interval. + The value is an integer ranging from 1 to 65535. + tcp_MSS: + description: + - Maximum TCP MSS value used for TCP connection establishment for a peer. + The value is an integer ranging from 176 to 4096. + mpls_local_ifnet_disable: + description: + - If the value is true, peer create MPLS Local IFNET disable. + If the value is false, peer create MPLS Local IFNET enable. + choices: ['no_use','true','false'] + default: no_use + prepend_global_as: + description: + - Add the global AS number to the Update packets to be advertised. + choices: ['no_use','true','false'] + default: no_use + prepend_fake_as: + description: + - Add the Fake AS number to received Update packets. + choices: ['no_use','true','false'] + default: no_use + is_bfd_block: + description: + - If the value is true, peers are enabled to inherit the BFD function from the peer group. + If the value is false, peers are disabled to inherit the BFD function from the peer group. + choices: ['no_use','true','false'] + default: no_use + multiplier: + description: + - Specify the detection multiplier. The default value is 3. + The value is an integer ranging from 3 to 50. + is_bfd_enable: + description: + - If the value is true, BFD is enabled. + If the value is false, BFD is disabled. + choices: ['no_use','true','false'] + default: no_use + rx_interval: + description: + - Specify the minimum interval at which BFD packets are received. + The value is an integer ranging from 50 to 1000, in milliseconds. + tx_interval: + description: + - Specify the minimum interval at which BFD packets are sent. + The value is an integer ranging from 50 to 1000, in milliseconds. + is_single_hop: + description: + - If the value is true, the system is enabled to preferentially use the single-hop mode for + BFD session setup between IBGP peers. + If the value is false, the system is disabled from preferentially using the single-hop + mode for BFD session setup between IBGP peers. + choices: ['no_use','true','false'] + default: no_use +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP neighbor test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config bgp peer" + community.network.ce_bgp_neighbor: + state: present + vrf_name: js + peer_addr: 192.168.10.10 + remote_as: 500 + provider: "{{ cli }}" + + - name: "Config bgp route id" + community.network.ce_bgp_neighbor: + state: absent + vrf_name: js + peer_addr: 192.168.10.10 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"peer_addr": "192.168.10.10", "remote_as": "500", "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp peer": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp peer": [["192.168.10.10", "500"]]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 192.168.10.10 as-number 500"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +# get bgp peer +CE_GET_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_GET_BGP_PEER_TAIL = """ + + + + + + + +""" + +# merge bgp peer +CE_MERGE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_MERGE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# create bgp peer +CE_CREATE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_CREATE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# delete bgp peer +CE_DELETE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_DELETE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# get peer bfd +CE_GET_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_GET_PEER_BFD_TAIL = """ + + + + + + + + +""" + +# merge peer bfd +CE_MERGE_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_MERGE_PEER_BFD_TAIL = """ + + + + + + + + +""" + +# delete peer bfd +CE_DELETE_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_DELETE_PEER_BFD_TAIL = """ + + + + + + + + +""" + + +class BgpNeighbor(object): + """ Manages BGP peer configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_peer_args(self, **kwargs): + """ check_bgp_peer_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + if peer_addr: + if not check_ip_addr(ipaddr=peer_addr): + module.fail_json( + msg='Error: The peer_addr %s is invalid.' % peer_addr) + + need_cfg = True + + remote_as = module.params['remote_as'] + if remote_as: + if len(remote_as) > 11 or len(remote_as) < 1: + module.fail_json( + msg='Error: The len of remote_as %s is out of [1 - 11].' % remote_as) + + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_peer_other_args(self, **kwargs): + """ check_bgp_peer_other_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + description = module.params['description'] + if description: + if len(description) > 80 or len(description) < 1: + module.fail_json( + msg='Error: The len of description %s is out of [1 - 80].' % description) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["description"] = re_find + if re_find[0] != description: + need_cfg = True + else: + need_cfg = True + + fake_as = module.params['fake_as'] + if fake_as: + if len(fake_as) > 11 or len(fake_as) < 1: + module.fail_json( + msg='Error: The len of fake_as %s is out of [1 - 11].' % fake_as) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["fake_as"] = re_find + if re_find[0] != fake_as: + need_cfg = True + else: + need_cfg = True + + dual_as = module.params['dual_as'] + if dual_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["dual_as"] = re_find + if re_find[0] != dual_as: + need_cfg = True + else: + need_cfg = True + + conventional = module.params['conventional'] + if conventional != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conventional"] = re_find + if re_find[0] != conventional: + need_cfg = True + else: + need_cfg = True + + route_refresh = module.params['route_refresh'] + if route_refresh != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_refresh"] = re_find + if re_find[0] != route_refresh: + need_cfg = True + else: + need_cfg = True + + four_byte_as = module.params['four_byte_as'] + if four_byte_as != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["four_byte_as"] = re_find + if re_find[0] != four_byte_as: + need_cfg = True + else: + need_cfg = True + + is_ignore = module.params['is_ignore'] + if is_ignore != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_ignore"] = re_find + if re_find[0] != is_ignore: + need_cfg = True + else: + need_cfg = True + + local_if_name = module.params['local_if_name'] + if local_if_name: + if len(local_if_name) > 63 or len(local_if_name) < 1: + module.fail_json( + msg='Error: The len of local_if_name %s is out of [1 - 63].' % local_if_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["local_if_name"] = re_find + if re_find[0].lower() != local_if_name.lower(): + need_cfg = True + else: + need_cfg = True + + ebgp_max_hop = module.params['ebgp_max_hop'] + if ebgp_max_hop: + if int(ebgp_max_hop) > 255 or int(ebgp_max_hop) < 1: + module.fail_json( + msg='Error: The value of ebgp_max_hop %s is out of [1 - 255].' % ebgp_max_hop) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_max_hop"] = re_find + if re_find[0] != ebgp_max_hop: + need_cfg = True + else: + need_cfg = True + + valid_ttl_hops = module.params['valid_ttl_hops'] + if valid_ttl_hops: + if int(valid_ttl_hops) > 255 or int(valid_ttl_hops) < 1: + module.fail_json( + msg='Error: The value of valid_ttl_hops %s is out of [1 - 255].' % valid_ttl_hops) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["valid_ttl_hops"] = re_find + if re_find[0] != valid_ttl_hops: + need_cfg = True + else: + need_cfg = True + + connect_mode = module.params['connect_mode'] + if connect_mode: + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["connect_mode"] = re_find + if re_find[0] != connect_mode: + need_cfg = True + else: + need_cfg = True + + is_log_change = module.params['is_log_change'] + if is_log_change != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_log_change"] = re_find + if re_find[0] != is_log_change: + need_cfg = True + else: + need_cfg = True + + pswd_type = module.params['pswd_type'] + if pswd_type: + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["pswd_type"] = re_find + if re_find[0] != pswd_type: + need_cfg = True + else: + need_cfg = True + + pswd_cipher_text = module.params['pswd_cipher_text'] + if pswd_cipher_text: + if len(pswd_cipher_text) > 255 or len(pswd_cipher_text) < 1: + module.fail_json( + msg='Error: The len of pswd_cipher_text %s is out of [1 - 255].' % pswd_cipher_text) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["pswd_cipher_text"] = re_find + if re_find[0] != pswd_cipher_text: + need_cfg = True + else: + need_cfg = True + + keep_alive_time = module.params['keep_alive_time'] + if keep_alive_time: + if int(keep_alive_time) > 21845 or len(keep_alive_time) < 0: + module.fail_json( + msg='Error: The len of keep_alive_time %s is out of [0 - 21845].' % keep_alive_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_alive_time"] = re_find + if re_find[0] != keep_alive_time: + need_cfg = True + else: + need_cfg = True + + hold_time = module.params['hold_time'] + if hold_time: + if int(hold_time) != 0 and (int(hold_time) > 65535 or int(hold_time) < 3): + module.fail_json( + msg='Error: The value of hold_time %s is out of [0 or 3 - 65535].' % hold_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] != hold_time: + need_cfg = True + else: + need_cfg = True + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + if int(min_hold_time) != 0 and (int(min_hold_time) > 65535 or int(min_hold_time) < 20): + module.fail_json( + msg='Error: The value of min_hold_time %s is out of [0 or 20 - 65535].' % min_hold_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] != min_hold_time: + need_cfg = True + else: + need_cfg = True + + key_chain_name = module.params['key_chain_name'] + if key_chain_name: + if len(key_chain_name) > 47 or len(key_chain_name) < 1: + module.fail_json( + msg='Error: The len of key_chain_name %s is out of [1 - 47].' % key_chain_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["key_chain_name"] = re_find + if re_find[0] != key_chain_name: + need_cfg = True + else: + need_cfg = True + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + if int(conn_retry_time) > 65535 or int(conn_retry_time) < 1: + module.fail_json( + msg='Error: The value of conn_retry_time %s is out of [1 - 65535].' % conn_retry_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] != conn_retry_time: + need_cfg = True + else: + need_cfg = True + + tcp_mss = module.params['tcp_MSS'] + if tcp_mss: + if int(tcp_mss) > 4096 or int(tcp_mss) < 176: + module.fail_json( + msg='Error: The value of tcp_mss %s is out of [176 - 4096].' % tcp_mss) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tcp_MSS"] = re_find + if re_find[0] != tcp_mss: + need_cfg = True + else: + need_cfg = True + + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + if mpls_local_ifnet_disable != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["mpls_local_ifnet_disable"] = re_find + if re_find[0] != mpls_local_ifnet_disable: + need_cfg = True + else: + need_cfg = True + + prepend_global_as = module.params['prepend_global_as'] + if prepend_global_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prepend_global_as"] = re_find + if re_find[0] != prepend_global_as: + need_cfg = True + else: + need_cfg = True + + prepend_fake_as = module.params['prepend_fake_as'] + if prepend_fake_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prepend_fake_as"] = re_find + if re_find[0] != prepend_fake_as: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_peer_bfd_merge_args(self, **kwargs): + """ check_peer_bfd_merge_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + if state == "absent": + result["need_cfg"] = need_cfg + return result + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_block"] = re_find + if re_find[0] != is_bfd_block: + need_cfg = True + else: + need_cfg = True + + multiplier = module.params['multiplier'] + if multiplier: + if int(multiplier) > 50 or int(multiplier) < 3: + module.fail_json( + msg='Error: The value of multiplier %s is out of [3 - 50].' % multiplier) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["multiplier"] = re_find + if re_find[0] != multiplier: + need_cfg = True + else: + need_cfg = True + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_enable"] = re_find + if re_find[0] != is_bfd_enable: + need_cfg = True + else: + need_cfg = True + + rx_interval = module.params['rx_interval'] + if rx_interval: + if int(rx_interval) > 1000 or int(rx_interval) < 50: + module.fail_json( + msg='Error: The value of rx_interval %s is out of [50 - 1000].' % rx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rx_interval"] = re_find + if re_find[0] != rx_interval: + need_cfg = True + else: + need_cfg = True + + tx_interval = module.params['tx_interval'] + if tx_interval: + if int(tx_interval) > 1000 or int(tx_interval) < 50: + module.fail_json( + msg='Error: The value of tx_interval %s is out of [50 - 1000].' % tx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tx_interval"] = re_find + if re_find[0] != tx_interval: + need_cfg = True + else: + need_cfg = True + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_single_hop"] = re_find + if re_find[0] != is_single_hop: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_peer_bfd_delete_args(self, **kwargs): + """ check_peer_bfd_delete_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + if state == "present": + result["need_cfg"] = need_cfg + return result + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_block"] = re_find + if re_find[0] == is_bfd_block: + need_cfg = True + + multiplier = module.params['multiplier'] + if multiplier: + if int(multiplier) > 50 or int(multiplier) < 3: + module.fail_json( + msg='Error: The value of multiplier %s is out of [3 - 50].' % multiplier) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["multiplier"] = re_find + if re_find[0] == multiplier: + need_cfg = True + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_enable"] = re_find + if re_find[0] == is_bfd_enable: + need_cfg = True + + rx_interval = module.params['rx_interval'] + if rx_interval: + if int(rx_interval) > 1000 or int(rx_interval) < 50: + module.fail_json( + msg='Error: The value of rx_interval %s is out of [50 - 1000].' % rx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rx_interval"] = re_find + if re_find[0] == rx_interval: + need_cfg = True + + tx_interval = module.params['tx_interval'] + if tx_interval: + if int(tx_interval) > 1000 or int(tx_interval) < 50: + module.fail_json( + msg='Error: The value of tx_interval %s is out of [50 - 1000].' % tx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tx_interval"] = re_find + if re_find[0] == tx_interval: + need_cfg = True + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_single_hop"] = re_find + if re_find[0] == is_single_hop: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def get_bgp_peer(self, **kwargs): + """ get_bgp_peer """ + + module = kwargs["module"] + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_bgp_del_peer(self, **kwargs): + """ get_bgp_del_peer """ + + module = kwargs["module"] + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + CE_GET_BGP_PEER_TAIL + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_peer(self, **kwargs): + """ merge_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + + conf_str = CE_MERGE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + "%s" % remote_as + CE_MERGE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer failed.') + + cmds = [] + cmd = "peer %s as-number %s" % (peer_addr, remote_as) + cmds.append(cmd) + + return cmds + + def create_bgp_peer(self, **kwargs): + """ create_bgp_peer """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + + conf_str = CE_CREATE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + "%s" % remote_as + CE_CREATE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp peer failed.') + + cmds = [] + cmd = "peer %s as-number %s" % (peer_addr, remote_as) + cmds.append(cmd) + + return cmds + + def delete_bgp_peer(self, **kwargs): + """ delete_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_DELETE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + CE_DELETE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp peer failed.') + + cmds = [] + cmd = "undo peer %s" % peer_addr + cmds.append(cmd) + + return cmds + + def merge_bgp_peer_other(self, **kwargs): + """ merge_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_MERGE_BGP_PEER_HEADER % (vrf_name, peer_addr) + + cmds = [] + + description = module.params['description'] + if description: + conf_str += "%s" % description + + cmd = "peer %s description %s" % (peer_addr, description) + cmds.append(cmd) + + fake_as = module.params['fake_as'] + if fake_as: + conf_str += "%s" % fake_as + + cmd = "peer %s local-as %s" % (peer_addr, fake_as) + cmds.append(cmd) + + dual_as = module.params['dual_as'] + if dual_as != 'no_use': + conf_str += "%s" % dual_as + + if dual_as == "true": + cmd = "peer %s local-as %s dual-as" % (peer_addr, fake_as) + else: + cmd = "peer %s local-as %s" % (peer_addr, fake_as) + cmds.append(cmd) + + conventional = module.params['conventional'] + if conventional != 'no_use': + conf_str += "%s" % conventional + if conventional == "true": + cmd = "peer %s capability-advertise conventional" % peer_addr + else: + cmd = "undo peer %s capability-advertise conventional" % peer_addr + cmds.append(cmd) + + route_refresh = module.params['route_refresh'] + if route_refresh != 'no_use': + conf_str += "%s" % route_refresh + + if route_refresh == "true": + cmd = "peer %s capability-advertise route-refresh" % peer_addr + else: + cmd = "undo peer %s capability-advertise route-refresh" % peer_addr + cmds.append(cmd) + + four_byte_as = module.params['four_byte_as'] + if four_byte_as != 'no_use': + conf_str += "%s" % four_byte_as + + if four_byte_as == "true": + cmd = "peer %s capability-advertise 4-byte-as" % peer_addr + else: + cmd = "undo peer %s capability-advertise 4-byte-as" % peer_addr + cmds.append(cmd) + + is_ignore = module.params['is_ignore'] + if is_ignore != 'no_use': + conf_str += "%s" % is_ignore + + if is_ignore == "true": + cmd = "peer %s ignore" % peer_addr + else: + cmd = "undo peer %s ignore" % peer_addr + cmds.append(cmd) + + local_if_name = module.params['local_if_name'] + if local_if_name: + conf_str += "%s" % local_if_name + + cmd = "peer %s connect-interface %s" % (peer_addr, local_if_name) + cmds.append(cmd) + + ebgp_max_hop = module.params['ebgp_max_hop'] + if ebgp_max_hop: + conf_str += "%s" % ebgp_max_hop + + cmd = "peer %s ebgp-max-hop %s" % (peer_addr, ebgp_max_hop) + cmds.append(cmd) + + valid_ttl_hops = module.params['valid_ttl_hops'] + if valid_ttl_hops: + conf_str += "%s" % valid_ttl_hops + + cmd = "peer %s valid-ttl-hops %s" % (peer_addr, valid_ttl_hops) + cmds.append(cmd) + + connect_mode = module.params['connect_mode'] + if connect_mode: + + if connect_mode == "listenOnly": + cmd = "peer %s listen-only" % peer_addr + cmds.append(cmd) + elif connect_mode == "connectOnly": + cmd = "peer %s connect-only" % peer_addr + cmds.append(cmd) + elif connect_mode == "both": + connect_mode = "null" + cmd = "peer %s listen-only" % peer_addr + cmds.append(cmd) + cmd = "peer %s connect-only" % peer_addr + cmds.append(cmd) + conf_str += "%s" % connect_mode + + is_log_change = module.params['is_log_change'] + if is_log_change != 'no_use': + conf_str += "%s" % is_log_change + + if is_log_change == "true": + cmd = "peer %s log-change" % peer_addr + else: + cmd = "undo peer %s log-change" % peer_addr + cmds.append(cmd) + + pswd_type = module.params['pswd_type'] + if pswd_type: + conf_str += "%s" % pswd_type + + pswd_cipher_text = module.params['pswd_cipher_text'] + if pswd_cipher_text: + conf_str += "%s" % pswd_cipher_text + + if pswd_type == "cipher": + cmd = "peer %s password cipher %s" % ( + peer_addr, pswd_cipher_text) + elif pswd_type == "simple": + cmd = "peer %s password simple %s" % ( + peer_addr, pswd_cipher_text) + cmds.append(cmd) + + keep_alive_time = module.params['keep_alive_time'] + if keep_alive_time: + conf_str += "%s" % keep_alive_time + + cmd = "peer %s timer keepalive %s" % (peer_addr, keep_alive_time) + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "peer %s timer hold %s" % (peer_addr, hold_time) + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "peer %s timer min-holdtime %s" % (peer_addr, min_hold_time) + cmds.append(cmd) + + key_chain_name = module.params['key_chain_name'] + if key_chain_name: + conf_str += "%s" % key_chain_name + + cmd = "peer %s keychain %s" % (peer_addr, key_chain_name) + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "peer %s timer connect-retry %s" % ( + peer_addr, conn_retry_time) + cmds.append(cmd) + + tcp_mss = module.params['tcp_MSS'] + if tcp_mss: + conf_str += "%s" % tcp_mss + + cmd = "peer %s tcp-mss %s" % (peer_addr, tcp_mss) + cmds.append(cmd) + + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + if mpls_local_ifnet_disable != 'no_use': + conf_str += "%s" % mpls_local_ifnet_disable + + if mpls_local_ifnet_disable == "false": + cmd = "undo peer %s mpls-local-ifnet disable" % peer_addr + else: + cmd = "peer %s mpls-local-ifnet disable" % peer_addr + cmds.append(cmd) + + prepend_global_as = module.params['prepend_global_as'] + if prepend_global_as != 'no_use': + conf_str += "%s" % prepend_global_as + + if prepend_global_as == "true": + cmd = "peer %s local-as %s prepend-global-as" % (peer_addr, fake_as) + else: + cmd = "undo peer %s local-as %s prepend-global-as" % (peer_addr, fake_as) + cmds.append(cmd) + + prepend_fake_as = module.params['prepend_fake_as'] + if prepend_fake_as != 'no_use': + conf_str += "%s" % prepend_fake_as + + if prepend_fake_as == "true": + cmd = "peer %s local-as %s prepend-local-as" % (peer_addr, fake_as) + else: + cmd = "undo peer %s local-as %s prepend-local-as" % (peer_addr, fake_as) + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer other failed.') + + return cmds + + def merge_peer_bfd(self, **kwargs): + """ merge_peer_bfd """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_MERGE_PEER_BFD_HEADER % (vrf_name, peer_addr) + + cmds = [] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + conf_str += "%s" % is_bfd_block + + if is_bfd_block == "true": + cmd = "peer %s bfd block" % peer_addr + else: + cmd = "undo peer %s bfd block" % peer_addr + cmds.append(cmd) + + multiplier = module.params['multiplier'] + if multiplier: + conf_str += "%s" % multiplier + + cmd = "peer %s bfd detect-multiplier %s" % (peer_addr, multiplier) + cmds.append(cmd) + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + conf_str += "%s" % is_bfd_enable + + if is_bfd_enable == "true": + cmd = "peer %s bfd enable" % peer_addr + else: + cmd = "undo peer %s bfd enable" % peer_addr + cmds.append(cmd) + + rx_interval = module.params['rx_interval'] + if rx_interval: + conf_str += "%s" % rx_interval + + cmd = "peer %s bfd min-rx-interval %s" % (peer_addr, rx_interval) + cmds.append(cmd) + + tx_interval = module.params['tx_interval'] + if tx_interval: + conf_str += "%s" % tx_interval + + cmd = "peer %s bfd min-tx-interval %s" % (peer_addr, tx_interval) + cmds.append(cmd) + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + conf_str += "%s" % is_single_hop + + if is_single_hop == "true": + cmd = "peer %s bfd enable single-hop-prefer" % peer_addr + else: + cmd = "undo peer %s bfd enable single-hop-prefer" % peer_addr + cmds.append(cmd) + + conf_str += CE_MERGE_PEER_BFD_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge peer bfd failed.') + + return cmds + + def delete_peer_bfd(self, **kwargs): + """ delete_peer_bfd """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_DELETE_PEER_BFD_HEADER % (vrf_name, peer_addr) + + cmds = [] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + conf_str += "%s" % is_bfd_block + + cmd = "undo peer %s bfd block" % peer_addr + cmds.append(cmd) + + multiplier = module.params['multiplier'] + if multiplier: + conf_str += "%s" % multiplier + + cmd = "undo peer %s bfd detect-multiplier %s" % ( + peer_addr, multiplier) + cmds.append(cmd) + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + conf_str += "%s" % is_bfd_enable + + cmd = "undo peer %s bfd enable" % peer_addr + cmds.append(cmd) + + rx_interval = module.params['rx_interval'] + if rx_interval: + conf_str += "%s" % rx_interval + + cmd = "undo peer %s bfd min-rx-interval %s" % ( + peer_addr, rx_interval) + cmds.append(cmd) + + tx_interval = module.params['tx_interval'] + if tx_interval: + conf_str += "%s" % tx_interval + + cmd = "undo peer %s bfd min-tx-interval %s" % ( + peer_addr, tx_interval) + cmds.append(cmd) + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + conf_str += "%s" % is_single_hop + + cmd = "undo peer %s bfd enable single-hop-prefer" % peer_addr + cmds.append(cmd) + + conf_str += CE_DELETE_PEER_BFD_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete peer bfd failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + peer_addr=dict(type='str', required=True), + remote_as=dict(type='str', required=True), + description=dict(type='str'), + fake_as=dict(type='str'), + dual_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + conventional=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + route_refresh=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + four_byte_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + local_if_name=dict(type='str'), + ebgp_max_hop=dict(type='str'), + valid_ttl_hops=dict(type='str'), + connect_mode=dict(choices=['listenOnly', 'connectOnly', 'both']), + is_log_change=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + pswd_type=dict(choices=['null', 'cipher', 'simple']), + pswd_cipher_text=dict(type='str', no_log=True), + keep_alive_time=dict(type='str'), + hold_time=dict(type='str'), + min_hold_time=dict(type='str'), + key_chain_name=dict(type='str'), + conn_retry_time=dict(type='str'), + tcp_MSS=dict(type='str'), + mpls_local_ifnet_disable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + prepend_global_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + prepend_fake_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_bfd_block=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + multiplier=dict(type='str'), + is_bfd_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rx_interval=dict(type='str'), + tx_interval=dict(type='str'), + is_single_hop=dict(type='str', default='no_use', choices=['no_use', 'true', 'false'])) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + description = module.params['description'] + fake_as = module.params['fake_as'] + dual_as = module.params['dual_as'] + conventional = module.params['conventional'] + route_refresh = module.params['route_refresh'] + four_byte_as = module.params['four_byte_as'] + is_ignore = module.params['is_ignore'] + local_if_name = module.params['local_if_name'] + ebgp_max_hop = module.params['ebgp_max_hop'] + valid_ttl_hops = module.params['valid_ttl_hops'] + connect_mode = module.params['connect_mode'] + is_log_change = module.params['is_log_change'] + pswd_type = module.params['pswd_type'] + pswd_cipher_text = module.params['pswd_cipher_text'] + keep_alive_time = module.params['keep_alive_time'] + hold_time = module.params['hold_time'] + min_hold_time = module.params['min_hold_time'] + key_chain_name = module.params['key_chain_name'] + conn_retry_time = module.params['conn_retry_time'] + tcp_mss = module.params['tcp_MSS'] + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + prepend_global_as = module.params['prepend_global_as'] + prepend_fake_as = module.params['prepend_fake_as'] + is_bfd_block = module.params['is_bfd_block'] + multiplier = module.params['multiplier'] + is_bfd_enable = module.params['is_bfd_enable'] + rx_interval = module.params['rx_interval'] + tx_interval = module.params['tx_interval'] + is_single_hop = module.params['is_single_hop'] + + ce_bgp_peer_obj = BgpNeighbor() + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if peer_addr: + proposed["peer_addr"] = peer_addr + if remote_as: + proposed["remote_as"] = remote_as + if description: + proposed["description"] = description + if fake_as: + proposed["fake_as"] = fake_as + if dual_as != 'no_use': + proposed["dual_as"] = dual_as + if conventional != 'no_use': + proposed["conventional"] = conventional + if route_refresh != 'no_use': + proposed["route_refresh"] = route_refresh + if four_byte_as != 'no_use': + proposed["four_byte_as"] = four_byte_as + if is_ignore != 'no_use': + proposed["is_ignore"] = is_ignore + if local_if_name: + proposed["local_if_name"] = local_if_name + if ebgp_max_hop: + proposed["ebgp_max_hop"] = ebgp_max_hop + if valid_ttl_hops: + proposed["valid_ttl_hops"] = valid_ttl_hops + if connect_mode: + proposed["connect_mode"] = connect_mode + if is_log_change != 'no_use': + proposed["is_log_change"] = is_log_change + if pswd_type: + proposed["pswd_type"] = pswd_type + if pswd_cipher_text: + proposed["pswd_cipher_text"] = pswd_cipher_text + if keep_alive_time: + proposed["keep_alive_time"] = keep_alive_time + if hold_time: + proposed["hold_time"] = hold_time + if min_hold_time: + proposed["min_hold_time"] = min_hold_time + if key_chain_name: + proposed["key_chain_name"] = key_chain_name + if conn_retry_time: + proposed["conn_retry_time"] = conn_retry_time + if tcp_mss: + proposed["tcp_MSS"] = tcp_mss + if mpls_local_ifnet_disable != 'no_use': + proposed["mpls_local_ifnet_disable"] = mpls_local_ifnet_disable + if prepend_global_as != 'no_use': + proposed["prepend_global_as"] = prepend_global_as + if prepend_fake_as != 'no_use': + proposed["prepend_fake_as"] = prepend_fake_as + if is_bfd_block != 'no_use': + proposed["is_bfd_block"] = is_bfd_block + if multiplier: + proposed["multiplier"] = multiplier + if is_bfd_enable != 'no_use': + proposed["is_bfd_enable"] = is_bfd_enable + if rx_interval: + proposed["rx_interval"] = rx_interval + if tx_interval: + proposed["tx_interval"] = tx_interval + if is_single_hop != 'no_use': + proposed["is_single_hop"] = is_single_hop + + if not ce_bgp_peer_obj: + module.fail_json(msg='Error: Init module failed.') + + need_bgp_peer_enable = ce_bgp_peer_obj.check_bgp_peer_args(module=module) + need_bgp_peer_other_rst = ce_bgp_peer_obj.check_bgp_peer_other_args( + module=module) + need_peer_bfd_merge_rst = ce_bgp_peer_obj.check_peer_bfd_merge_args( + module=module) + need_peer_bfd_del_rst = ce_bgp_peer_obj.check_peer_bfd_delete_args( + module=module) + + # bgp peer config + if need_bgp_peer_enable["need_cfg"]: + + if state == "present": + + if remote_as: + + bgp_peer_exist = ce_bgp_peer_obj.get_bgp_peer(module=module) + existing["bgp peer"] = bgp_peer_exist + + bgp_peer_new = (peer_addr, remote_as) + if len(bgp_peer_exist) == 0: + cmd = ce_bgp_peer_obj.create_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + elif bgp_peer_new in bgp_peer_exist: + pass + + else: + cmd = ce_bgp_peer_obj.merge_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + bgp_peer_end = ce_bgp_peer_obj.get_bgp_peer(module=module) + end_state["bgp peer"] = bgp_peer_end + + else: + + bgp_peer_exist = ce_bgp_peer_obj.get_bgp_del_peer(module=module) + existing["bgp peer"] = bgp_peer_exist + + bgp_peer_new = (peer_addr) + + if len(bgp_peer_exist) == 0: + pass + + elif bgp_peer_new in bgp_peer_exist: + cmd = ce_bgp_peer_obj.delete_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + bgp_peer_end = ce_bgp_peer_obj.get_bgp_del_peer(module=module) + end_state["bgp peer"] = bgp_peer_end + + # bgp peer other args + exist_tmp = dict() + for item in need_bgp_peer_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_peer_other_rst[item] + if exist_tmp: + existing["bgp peer other"] = exist_tmp + + if need_bgp_peer_other_rst["need_cfg"]: + + if state == "present": + cmd = ce_bgp_peer_obj.merge_bgp_peer_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_peer_other_rst = ce_bgp_peer_obj.check_bgp_peer_other_args( + module=module) + end_tmp = dict() + for item in need_bgp_peer_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_peer_other_rst[item] + if end_tmp: + end_state["bgp peer other"] = end_tmp + + # peer bfd args + if state == "present": + exist_tmp = dict() + for item in need_peer_bfd_merge_rst: + if item != "need_cfg": + exist_tmp[item] = need_peer_bfd_merge_rst[item] + if exist_tmp: + existing["peer bfd"] = exist_tmp + + if need_peer_bfd_merge_rst["need_cfg"]: + cmd = ce_bgp_peer_obj.merge_peer_bfd(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_peer_bfd_merge_rst = ce_bgp_peer_obj.check_peer_bfd_merge_args( + module=module) + end_tmp = dict() + for item in need_peer_bfd_merge_rst: + if item != "need_cfg": + end_tmp[item] = need_peer_bfd_merge_rst[item] + if end_tmp: + end_state["peer bfd"] = end_tmp + else: + exist_tmp = dict() + for item in need_peer_bfd_del_rst: + if item != "need_cfg": + exist_tmp[item] = need_peer_bfd_del_rst[item] + if exist_tmp: + existing["peer bfd"] = exist_tmp + + # has already delete with bgp peer + + need_peer_bfd_del_rst = ce_bgp_peer_obj.check_peer_bfd_delete_args( + module=module) + end_tmp = dict() + for item in need_peer_bfd_del_rst: + if item != "need_cfg": + end_tmp[item] = need_peer_bfd_del_rst[item] + if end_tmp: + end_state["peer bfd"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor_af.py new file mode 100644 index 00000000..93bee2a8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_bgp_neighbor_af.py @@ -0,0 +1,2675 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_neighbor_af +short_description: Manages BGP neighbor Address-family configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP neighbor Address-family configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + required: true + af_type: + description: + - Address family type of a BGP instance. + required: true + choices: ['ipv4uni', 'ipv4multi', 'ipv4vpn', 'ipv6uni', 'ipv6vpn', 'evpn'] + remote_address: + description: + - IPv4 or IPv6 peer connection address. + required: true + advertise_irb: + description: + - If the value is true, advertised IRB routes are distinguished. + If the value is false, advertised IRB routes are not distinguished. + default: no_use + choices: ['no_use','true','false'] + advertise_arp: + description: + - If the value is true, advertised ARP routes are distinguished. + If the value is false, advertised ARP routes are not distinguished. + default: no_use + choices: ['no_use','true','false'] + advertise_remote_nexthop: + description: + - If the value is true, the remote next-hop attribute is advertised to peers. + If the value is false, the remote next-hop attribute is not advertised to any peers. + default: no_use + choices: ['no_use','true','false'] + advertise_community: + description: + - If the value is true, the community attribute is advertised to peers. + If the value is false, the community attribute is not advertised to peers. + default: no_use + choices: ['no_use','true','false'] + advertise_ext_community: + description: + - If the value is true, the extended community attribute is advertised to peers. + If the value is false, the extended community attribute is not advertised to peers. + default: no_use + choices: ['no_use','true','false'] + discard_ext_community: + description: + - If the value is true, the extended community attribute in the peer route information is discarded. + If the value is false, the extended community attribute in the peer route information is not discarded. + default: no_use + choices: ['no_use','true','false'] + allow_as_loop_enable: + description: + - If the value is true, repetitive local AS numbers are allowed. + If the value is false, repetitive local AS numbers are not allowed. + default: no_use + choices: ['no_use','true','false'] + allow_as_loop_limit: + description: + - Set the maximum number of repetitive local AS number. + The value is an integer ranging from 1 to 10. + keep_all_routes: + description: + - If the value is true, the system stores all route update messages received from all peers (groups) + after BGP connection setup. + If the value is false, the system stores only BGP update messages that are received from peers + and pass the configured import policy. + default: no_use + choices: ['no_use','true','false'] + nexthop_configure: + description: + - null, The next hop is not changed. + local, The next hop is changed to the local IP address. + invariable, Prevent the device from changing the next hop of each imported IGP route + when advertising it to its BGP peers. + choices: ['null', 'local', 'invariable'] + preferred_value: + description: + - Assign a preferred value for the routes learned from a specified peer. + The value is an integer ranging from 0 to 65535. + public_as_only: + description: + - If the value is true, sent BGP update messages carry only the public AS number but do not carry + private AS numbers. + If the value is false, sent BGP update messages can carry private AS numbers. + default: no_use + choices: ['no_use','true','false'] + public_as_only_force: + description: + - If the value is true, sent BGP update messages carry only the public AS number but do not carry + private AS numbers. + If the value is false, sent BGP update messages can carry private AS numbers. + default: no_use + choices: ['no_use','true','false'] + public_as_only_limited: + description: + - Limited use public as number. + default: no_use + choices: ['no_use','true','false'] + public_as_only_replace: + description: + - Private as replaced by public as number. + default: no_use + choices: ['no_use','true','false'] + public_as_only_skip_peer_as: + description: + - Public as only skip peer as. + default: no_use + choices: ['no_use','true','false'] + route_limit: + description: + - Configure the maximum number of routes that can be accepted from a peer. + The value is an integer ranging from 1 to 4294967295. + route_limit_percent: + description: + - Specify the percentage of routes when a router starts to generate an alarm. + The value is an integer ranging from 1 to 100. + route_limit_type: + description: + - Noparameter, After the number of received routes exceeds the threshold and the timeout + timer expires,no action. + AlertOnly, An alarm is generated and no additional routes will be accepted if the maximum + number of routes allowed have been received. + IdleForever, The connection that is interrupted is not automatically re-established if the + maximum number of routes allowed have been received. + IdleTimeout, After the number of received routes exceeds the threshold and the timeout timer + expires, the connection that is interrupted is automatically re-established. + choices: ['noparameter', 'alertOnly', 'idleForever', 'idleTimeout'] + route_limit_idle_timeout: + description: + - Specify the value of the idle-timeout timer to automatically reestablish the connections after + they are cut off when the number of routes exceeds the set threshold. + The value is an integer ranging from 1 to 1200. + rt_updt_interval: + description: + - Specify the minimum interval at which Update packets are sent. The value is an integer, in seconds. + The value is an integer ranging from 0 to 600. + redirect_ip: + description: + - Redirect ip. + default: no_use + choices: ['no_use','true','false'] + redirect_ip_validation: + description: + - Redirect ip validation. + default: no_use + choices: ['no_use','true','false'] + aliases: ['redirect_ip_vaildation'] + reflect_client: + description: + - If the value is true, the local device functions as the route reflector and a peer functions + as a client of the route reflector. + If the value is false, the route reflector and client functions are not configured. + default: no_use + choices: ['no_use','true','false'] + substitute_as_enable: + description: + - If the value is true, the function to replace a specified peer's AS number in the AS-Path attribute with + the local AS number is enabled. + If the value is false, the function to replace a specified peer's AS number in the AS-Path attribute with + the local AS number is disabled. + default: no_use + choices: ['no_use','true','false'] + import_rt_policy_name: + description: + - Specify the filtering policy applied to the routes learned from a peer. + The value is a string of 1 to 40 characters. + export_rt_policy_name: + description: + - Specify the filtering policy applied to the routes to be advertised to a peer. + The value is a string of 1 to 40 characters. + import_pref_filt_name: + description: + - Specify the IPv4 filtering policy applied to the routes received from a specified peer. + The value is a string of 1 to 169 characters. + export_pref_filt_name: + description: + - Specify the IPv4 filtering policy applied to the routes to be advertised to a specified peer. + The value is a string of 1 to 169 characters. + import_as_path_filter: + description: + - Apply an AS_Path-based filtering policy to the routes received from a specified peer. + The value is an integer ranging from 1 to 256. + export_as_path_filter: + description: + - Apply an AS_Path-based filtering policy to the routes to be advertised to a specified peer. + The value is an integer ranging from 1 to 256. + import_as_path_name_or_num: + description: + - A routing strategy based on the AS path list for routing received by a designated peer. + export_as_path_name_or_num: + description: + - Application of a AS path list based filtering policy to the routing of a specified peer. + import_acl_name_or_num: + description: + - Apply an IPv4 ACL-based filtering policy to the routes received from a specified peer. + The value is a string of 1 to 32 characters. + export_acl_name_or_num: + description: + - Apply an IPv4 ACL-based filtering policy to the routes to be advertised to a specified peer. + The value is a string of 1 to 32 characters. + ipprefix_orf_enable: + description: + - If the value is true, the address prefix-based Outbound Route Filter (ORF) capability is + enabled for peers. + If the value is false, the address prefix-based Outbound Route Filter (ORF) capability is + disabled for peers. + default: no_use + choices: ['no_use','true','false'] + is_nonstd_ipprefix_mod: + description: + - If the value is true, Non-standard capability codes are used during capability negotiation. + If the value is false, RFC-defined standard ORF capability codes are used during capability negotiation. + default: no_use + choices: ['no_use','true','false'] + orftype: + description: + - ORF Type. + The value is an integer ranging from 0 to 65535. + orf_mode: + description: + - ORF mode. + null, Default value. + receive, ORF for incoming packets. + send, ORF for outgoing packets. + both, ORF for incoming and outgoing packets. + choices: ['null', 'receive', 'send', 'both'] + soostring: + description: + - Configure the Site-of-Origin (SoO) extended community attribute. + The value is a string of 3 to 21 characters. + default_rt_adv_enable: + description: + - If the value is true, the function to advertise default routes to peers is enabled. + If the value is false, the function to advertise default routes to peers is disabled. + default: no_use + choices: ['no_use','true', 'false'] + default_rt_adv_policy: + description: + - Specify the name of a used policy. The value is a string. + The value is a string of 1 to 40 characters. + default_rt_match_mode: + description: + - null, Null. + matchall, Advertise the default route if all matching conditions are met. + matchany, Advertise the default route if any matching condition is met. + choices: ['null', 'matchall', 'matchany'] + add_path_mode: + description: + - null, Null. + receive, Support receiving Add-Path routes. + send, Support sending Add-Path routes. + both, Support receiving and sending Add-Path routes. + choices: ['null', 'receive', 'send', 'both'] + adv_add_path_num: + description: + - The number of addPath advertise route. + The value is an integer ranging from 2 to 64. + origin_as_valid: + description: + - If the value is true, Application results of route announcement. + If the value is false, Routing application results are not notified. + default: no_use + choices: ['no_use','true', 'false'] + vpls_enable: + description: + - If the value is true, vpls enable. + If the value is false, vpls disable. + default: no_use + choices: ['no_use','true', 'false'] + vpls_ad_disable: + description: + - If the value is true, enable vpls-ad. + If the value is false, disable vpls-ad. + default: no_use + choices: ['no_use','true', 'false'] + update_pkt_standard_compatible: + description: + - If the value is true, When the vpnv4 multicast neighbor receives and updates the message, + the message has no label. + If the value is false, When the vpnv4 multicast neighbor receives and updates the message, + the message has label. + default: no_use + choices: ['no_use','true', 'false'] +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP neighbor address family test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config BGP peer Address_Family" + community.network.ce_bgp_neighbor_af: + state: present + vrf_name: js + af_type: ipv4uni + remote_address: 192.168.10.10 + nexthop_configure: local + provider: "{{ cli }}" + + - name: "Undo BGP peer Address_Family" + community.network.ce_bgp_neighbor_af: + state: absent + vrf_name: js + af_type: ipv4uni + remote_address: 192.168.10.10 + nexthop_configure: local + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"af_type": "ipv4uni", "nexthop_configure": "local", + "remote_address": "192.168.10.10", + "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp neighbor af": {"af_type": "ipv4uni", "remote_address": "192.168.10.10", + "vrf_name": "js"}, + "bgp neighbor af other": {"af_type": "ipv4uni", "nexthop_configure": "null", + "vrf_name": "js"}} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp neighbor af": {"af_type": "ipv4uni", "remote_address": "192.168.10.10", + "vrf_name": "js"}, + "bgp neighbor af other": {"af_type": "ipv4uni", "nexthop_configure": "local", + "vrf_name": "js"}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 192.168.10.10 next-hop-local"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get bgp peer af +CE_GET_BGP_PEER_AF_HEADER = """ + + + + + + %s + + + %s + + + %s +""" +CE_GET_BGP_PEER_AF_TAIL = """ + + + + + + + + + +""" + +# merge bgp peer af +CE_MERGE_BGP_PEER_AF_HEADER = """ + + + + + + %s + + + %s + + + %s +""" +CE_MERGE_BGP_PEER_AF_TAIL = """ + + + + + + + + + +""" + +# create bgp peer af +CE_CREATE_BGP_PEER_AF = """ + + + + + + %s + + + %s + + + %s + + + + + + + + + +""" + +# delete bgp peer af +CE_DELETE_BGP_PEER_AF = """ + + + + + + %s + + + %s + + + %s + + + + + + + + + +""" + + +class BgpNeighborAf(object): + """ Manages BGP neighbor Address-family configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_neighbor_af_args(self, **kwargs): + """ check_bgp_neighbor_af_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + state = module.params['state'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + if not check_ip_addr(ipaddr=remote_address): + module.fail_json( + msg='Error: The remote_address %s is invalid.' % remote_address) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + if re_find: + result["remote_address"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if remote_address not in re_find: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["remote_address"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] == remote_address: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_neighbor_af_other(self, **kwargs): + """ check_bgp_neighbor_af_other """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + if state == "absent": + result["need_cfg"] = need_cfg + return result + + advertise_irb = module.params['advertise_irb'] + if advertise_irb != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall(r'.*%s\s*' + r'(.*).*' % remote_address, recv_xml) + if re_find: + result["advertise_irb"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_irb: + need_cfg = True + else: + need_cfg = True + + advertise_arp = module.params['advertise_arp'] + if advertise_arp != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall(r'.*%s\s*' + r'.*(.*).*' % remote_address, recv_xml) + + if re_find: + result["advertise_arp"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_arp: + need_cfg = True + else: + need_cfg = True + + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + if advertise_remote_nexthop != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_remote_nexthop"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_remote_nexthop: + need_cfg = True + else: + need_cfg = True + + advertise_community = module.params['advertise_community'] + if advertise_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_community: + need_cfg = True + else: + need_cfg = True + + advertise_ext_community = module.params['advertise_ext_community'] + if advertise_ext_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_ext_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_ext_community: + need_cfg = True + else: + need_cfg = True + + discard_ext_community = module.params['discard_ext_community'] + if discard_ext_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["discard_ext_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != discard_ext_community: + need_cfg = True + else: + need_cfg = True + + allow_as_loop_enable = module.params['allow_as_loop_enable'] + if allow_as_loop_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_as_loop_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != allow_as_loop_enable: + need_cfg = True + else: + need_cfg = True + + allow_as_loop_limit = module.params['allow_as_loop_limit'] + if allow_as_loop_limit: + if int(allow_as_loop_limit) > 10 or int(allow_as_loop_limit) < 1: + module.fail_json( + msg='the value of allow_as_loop_limit %s is out of [1 - 10].' % allow_as_loop_limit) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_as_loop_limit"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != allow_as_loop_limit: + need_cfg = True + else: + need_cfg = True + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_all_routes"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != keep_all_routes: + need_cfg = True + else: + need_cfg = True + + nexthop_configure = module.params['nexthop_configure'] + if nexthop_configure: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + self.exist_nexthop_configure = "null" + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + self.exist_nexthop_configure = re_find[0] + result["nexthop_configure"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != nexthop_configure: + need_cfg = True + else: + need_cfg = True + + preferred_value = module.params['preferred_value'] + if preferred_value: + if int(preferred_value) > 65535 or int(preferred_value) < 0: + module.fail_json( + msg='the value of preferred_value %s is out of [0 - 65535].' % preferred_value) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preferred_value"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != preferred_value: + need_cfg = True + else: + need_cfg = True + + public_as_only = module.params['public_as_only'] + if public_as_only != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only: + need_cfg = True + else: + need_cfg = True + + public_as_only_force = module.params['public_as_only_force'] + if public_as_only_force != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_force"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_force: + need_cfg = True + else: + need_cfg = True + + public_as_only_limited = module.params['public_as_only_limited'] + if public_as_only_limited != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_limited"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_limited: + need_cfg = True + else: + need_cfg = True + + public_as_only_replace = module.params['public_as_only_replace'] + if public_as_only_replace != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_replace"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_replace: + need_cfg = True + else: + need_cfg = True + + public_as_only_skip_peer_as = module.params[ + 'public_as_only_skip_peer_as'] + if public_as_only_skip_peer_as != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_skip_peer_as"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_skip_peer_as: + need_cfg = True + else: + need_cfg = True + + route_limit = module.params['route_limit'] + if route_limit: + + if int(route_limit) < 1: + module.fail_json( + msg='the value of route_limit %s is out of [1 - 4294967295].' % route_limit) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit: + need_cfg = True + else: + need_cfg = True + + route_limit_percent = module.params['route_limit_percent'] + if route_limit_percent: + + if int(route_limit_percent) < 1 or int(route_limit_percent) > 100: + module.fail_json( + msg='Error: The value of route_limit_percent %s is out of [1 - 100].' % route_limit_percent) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_percent"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_percent: + need_cfg = True + else: + need_cfg = True + + route_limit_type = module.params['route_limit_type'] + if route_limit_type: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_type"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_type: + need_cfg = True + else: + need_cfg = True + + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + if route_limit_idle_timeout: + + if int(route_limit_idle_timeout) < 1 or int(route_limit_idle_timeout) > 1200: + module.fail_json( + msg='Error: The value of route_limit_idle_timeout %s is out of ' + '[1 - 1200].' % route_limit_idle_timeout) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_idle_timeout"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_idle_timeout: + need_cfg = True + else: + need_cfg = True + + rt_updt_interval = module.params['rt_updt_interval'] + if rt_updt_interval: + + if int(rt_updt_interval) < 0 or int(rt_updt_interval) > 600: + module.fail_json( + msg='Error: The value of rt_updt_interval %s is out of [0 - 600].' % rt_updt_interval) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rt_updt_interval"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != rt_updt_interval: + need_cfg = True + else: + need_cfg = True + + redirect_ip = module.params['redirect_ip'] + if redirect_ip != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["redirect_ip"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != redirect_ip: + need_cfg = True + else: + need_cfg = True + + redirect_ip_validation = module.params['redirect_ip_validation'] + if redirect_ip_validation != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["redirect_ip_validation"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != redirect_ip_validation: + need_cfg = True + else: + need_cfg = True + + reflect_client = module.params['reflect_client'] + if reflect_client != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_client"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != reflect_client: + need_cfg = True + else: + need_cfg = True + + substitute_as_enable = module.params['substitute_as_enable'] + if substitute_as_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["substitute_as_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != substitute_as_enable: + need_cfg = True + else: + need_cfg = True + + import_rt_policy_name = module.params['import_rt_policy_name'] + if import_rt_policy_name: + + if len(import_rt_policy_name) < 1 or len(import_rt_policy_name) > 40: + module.fail_json( + msg='Error: The len of import_rt_policy_name %s is out of [1 - 40].' % import_rt_policy_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_rt_policy_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_rt_policy_name: + need_cfg = True + else: + need_cfg = True + + export_rt_policy_name = module.params['export_rt_policy_name'] + if export_rt_policy_name: + + if len(export_rt_policy_name) < 1 or len(export_rt_policy_name) > 40: + module.fail_json( + msg='Error: The len of export_rt_policy_name %s is out of [1 - 40].' % export_rt_policy_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_rt_policy_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_rt_policy_name: + need_cfg = True + else: + need_cfg = True + + import_pref_filt_name = module.params['import_pref_filt_name'] + if import_pref_filt_name: + + if len(import_pref_filt_name) < 1 or len(import_pref_filt_name) > 169: + module.fail_json( + msg='Error: The len of import_pref_filt_name %s is out of [1 - 169].' % import_pref_filt_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_pref_filt_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_pref_filt_name: + need_cfg = True + else: + need_cfg = True + + export_pref_filt_name = module.params['export_pref_filt_name'] + if export_pref_filt_name: + + if len(export_pref_filt_name) < 1 or len(export_pref_filt_name) > 169: + module.fail_json( + msg='Error: The len of export_pref_filt_name %s is out of [1 - 169].' % export_pref_filt_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_pref_filt_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_pref_filt_name: + need_cfg = True + else: + need_cfg = True + + import_as_path_filter = module.params['import_as_path_filter'] + if import_as_path_filter: + + if int(import_as_path_filter) < 1 or int(import_as_path_filter) > 256: + module.fail_json( + msg='Error: The value of import_as_path_filter %s is out of [1 - 256].' % import_as_path_filter) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_as_path_filter"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_as_path_filter: + need_cfg = True + else: + need_cfg = True + + export_as_path_filter = module.params['export_as_path_filter'] + if export_as_path_filter: + + if int(export_as_path_filter) < 1 or int(export_as_path_filter) > 256: + module.fail_json( + msg='Error: The value of export_as_path_filter %s is out of [1 - 256].' % export_as_path_filter) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_as_path_filter"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_as_path_filter: + need_cfg = True + else: + need_cfg = True + + import_as_path_name_or_num = module.params[ + 'import_as_path_name_or_num'] + if import_as_path_name_or_num: + + if len(import_as_path_name_or_num) < 1 or len(import_as_path_name_or_num) > 51: + module.fail_json( + msg='Error: The len of import_as_path_name_or_num %s is out ' + 'of [1 - 51].' % import_as_path_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_as_path_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_as_path_name_or_num: + need_cfg = True + else: + need_cfg = True + + export_as_path_name_or_num = module.params[ + 'export_as_path_name_or_num'] + if export_as_path_name_or_num: + + if len(export_as_path_name_or_num) < 1 or len(export_as_path_name_or_num) > 51: + module.fail_json( + msg='Error: The len of export_as_path_name_or_num %s is out ' + 'of [1 - 51].' % export_as_path_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_as_path_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_as_path_name_or_num: + need_cfg = True + else: + need_cfg = True + + import_acl_name_or_num = module.params['import_acl_name_or_num'] + if import_acl_name_or_num: + + if len(import_acl_name_or_num) < 1 or len(import_acl_name_or_num) > 32: + module.fail_json( + msg='Error: The len of import_acl_name_or_num %s is out of [1 - 32].' % import_acl_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_acl_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_acl_name_or_num: + need_cfg = True + else: + need_cfg = True + + export_acl_name_or_num = module.params['export_acl_name_or_num'] + if export_acl_name_or_num: + + if len(export_acl_name_or_num) < 1 or len(export_acl_name_or_num) > 32: + module.fail_json( + msg='Error: The len of export_acl_name_or_num %s is out of [1 - 32].' % export_acl_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_acl_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_acl_name_or_num: + need_cfg = True + else: + need_cfg = True + + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + if ipprefix_orf_enable != 'no_use': + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ipprefix_orf_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != ipprefix_orf_enable: + need_cfg = True + else: + need_cfg = True + + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + if is_nonstd_ipprefix_mod != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_nonstd_ipprefix_mod"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != is_nonstd_ipprefix_mod: + need_cfg = True + else: + need_cfg = True + + orftype = module.params['orftype'] + if orftype: + + if int(orftype) < 0 or int(orftype) > 65535: + module.fail_json( + msg='Error: The value of orftype %s is out of [0 - 65535].' % orftype) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["orftype"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != orftype: + need_cfg = True + else: + need_cfg = True + + orf_mode = module.params['orf_mode'] + if orf_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["orf_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != orf_mode: + need_cfg = True + else: + need_cfg = True + + soostring = module.params['soostring'] + if soostring: + + if len(soostring) < 3 or len(soostring) > 21: + module.fail_json( + msg='Error: The len of soostring %s is out of [3 - 21].' % soostring) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["soostring"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != soostring: + need_cfg = True + else: + need_cfg = True + + default_rt_adv_enable = module.params['default_rt_adv_enable'] + if default_rt_adv_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_adv_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_adv_enable: + need_cfg = True + else: + need_cfg = True + + default_rt_adv_policy = module.params['default_rt_adv_policy'] + if default_rt_adv_policy: + + if len(default_rt_adv_policy) < 1 or len(default_rt_adv_policy) > 40: + module.fail_json( + msg='Error: The len of default_rt_adv_policy %s is out of [1 - 40].' % default_rt_adv_policy) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_adv_policy"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_adv_policy: + need_cfg = True + else: + need_cfg = True + + default_rt_match_mode = module.params['default_rt_match_mode'] + if default_rt_match_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_match_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_match_mode: + need_cfg = True + else: + need_cfg = True + + add_path_mode = module.params['add_path_mode'] + if add_path_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["add_path_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != add_path_mode: + need_cfg = True + else: + need_cfg = True + + adv_add_path_num = module.params['adv_add_path_num'] + if adv_add_path_num: + + if int(adv_add_path_num) < 2 or int(adv_add_path_num) > 64: + module.fail_json( + msg='Error: The value of adv_add_path_num %s is out of [2 - 64].' % adv_add_path_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["adv_add_path_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != adv_add_path_num: + need_cfg = True + else: + need_cfg = True + + origin_as_valid = module.params['origin_as_valid'] + if origin_as_valid != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["origin_as_valid"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != origin_as_valid: + need_cfg = True + else: + need_cfg = True + + vpls_enable = module.params['vpls_enable'] + if vpls_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vpls_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != vpls_enable: + need_cfg = True + else: + need_cfg = True + + vpls_ad_disable = module.params['vpls_ad_disable'] + if vpls_ad_disable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vpls_ad_disable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != vpls_ad_disable: + need_cfg = True + else: + need_cfg = True + + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + if update_pkt_standard_compatible != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + \ + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["update_pkt_standard_compatible"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != update_pkt_standard_compatible: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def merge_bgp_peer_af(self, **kwargs): + """ merge_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_MERGE_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + CE_MERGE_BGP_PEER_AF_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "peer %s enable" % remote_address + else: + cmd = "peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def create_bgp_peer_af(self, **kwargs): + """ create_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_CREATE_BGP_PEER_AF % (vrf_name, af_type, remote_address) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "peer %s enable" % remote_address + else: + cmd = "peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def delete_bgp_peer_af(self, **kwargs): + """ delete_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_DELETE_BGP_PEER_AF % (vrf_name, af_type, remote_address) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "undo peer %s enable" % remote_address + else: + cmd = "undo peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def merge_bgp_peer_af_other(self, **kwargs): + """ merge_bgp_peer_af_other """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_MERGE_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + + cmds = [] + + advertise_irb = module.params['advertise_irb'] + if advertise_irb != 'no_use': + conf_str += "%s" % advertise_irb + + if advertise_irb == "true": + cmd = "peer %s advertise irb" % remote_address + else: + cmd = "undo peer %s advertise irb" % remote_address + cmds.append(cmd) + + advertise_arp = module.params['advertise_arp'] + if advertise_arp != 'no_use': + conf_str += "%s" % advertise_arp + + if advertise_arp == "true": + cmd = "peer %s advertise arp" % remote_address + else: + cmd = "undo peer %s advertise arp" % remote_address + cmds.append(cmd) + + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + if advertise_remote_nexthop != 'no_use': + conf_str += "%s" % advertise_remote_nexthop + + if advertise_remote_nexthop == "true": + cmd = "peer %s advertise remote-nexthop" % remote_address + else: + cmd = "undo peer %s advertise remote-nexthop" % remote_address + cmds.append(cmd) + + advertise_community = module.params['advertise_community'] + if advertise_community != 'no_use': + conf_str += "%s" % advertise_community + + if advertise_community == "true": + cmd = "peer %s advertise-community" % remote_address + else: + cmd = "undo peer %s advertise-community" % remote_address + cmds.append(cmd) + + advertise_ext_community = module.params['advertise_ext_community'] + if advertise_ext_community != 'no_use': + conf_str += "%s" % advertise_ext_community + + if advertise_ext_community == "true": + cmd = "peer %s advertise-ext-community" % remote_address + else: + cmd = "undo peer %s advertise-ext-community" % remote_address + cmds.append(cmd) + + discard_ext_community = module.params['discard_ext_community'] + if discard_ext_community != 'no_use': + conf_str += "%s" % discard_ext_community + + if discard_ext_community == "true": + cmd = "peer %s discard-ext-community" % remote_address + else: + cmd = "undo peer %s discard-ext-community" % remote_address + cmds.append(cmd) + + allow_as_loop_enable = module.params['allow_as_loop_enable'] + if allow_as_loop_enable != 'no_use': + conf_str += "%s" % allow_as_loop_enable + + if allow_as_loop_enable == "true": + cmd = "peer %s allow-as-loop" % remote_address + else: + cmd = "undo peer %s allow-as-loop" % remote_address + cmds.append(cmd) + + allow_as_loop_limit = module.params['allow_as_loop_limit'] + if allow_as_loop_limit: + conf_str += "%s" % allow_as_loop_limit + + if allow_as_loop_enable == "true": + cmd = "peer %s allow-as-loop %s" % (remote_address, allow_as_loop_limit) + else: + cmd = "undo peer %s allow-as-loop" % remote_address + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "peer %s keep-all-routes" % remote_address + else: + cmd = "undo peer %s keep-all-routes" % remote_address + cmds.append(cmd) + + nexthop_configure = module.params['nexthop_configure'] + if nexthop_configure: + conf_str += "%s" % nexthop_configure + + if nexthop_configure == "local": + cmd = "peer %s next-hop-local" % remote_address + cmds.append(cmd) + elif nexthop_configure == "invariable": + cmd = "peer %s next-hop-invariable" % remote_address + cmds.append(cmd) + else: + if self.exist_nexthop_configure != "null": + if self.exist_nexthop_configure == "local": + cmd = "undo peer %s next-hop-local" % remote_address + cmds.append(cmd) + elif self.exist_nexthop_configure == "invariable": + cmd = "undo peer %s next-hop-invariable" % remote_address + cmds.append(cmd) + preferred_value = module.params['preferred_value'] + if preferred_value: + conf_str += "%s" % preferred_value + + cmd = "peer %s preferred-value %s" % (remote_address, preferred_value) + cmds.append(cmd) + + public_as_only = module.params['public_as_only'] + if public_as_only != 'no_use': + conf_str += "%s" % public_as_only + + if public_as_only == "true": + cmd = "peer %s public-as-only" % remote_address + else: + cmd = "undo peer %s public-as-only" % remote_address + cmds.append(cmd) + + public_as_only_force = module.params['public_as_only_force'] + if public_as_only_force != 'no_use': + conf_str += "%s" % public_as_only_force + + if public_as_only_force == "true": + cmd = "peer %s public-as-only force" % remote_address + else: + cmd = "undo peer %s public-as-only force" % remote_address + cmds.append(cmd) + + public_as_only_limited = module.params['public_as_only_limited'] + if public_as_only_limited != 'no_use': + conf_str += "%s" % public_as_only_limited + + if public_as_only_limited == "true": + cmd = "peer %s public-as-only limited" % remote_address + else: + cmd = "undo peer %s public-as-only limited" % remote_address + cmds.append(cmd) + + public_as_only_replace = module.params['public_as_only_replace'] + if public_as_only_replace != 'no_use': + conf_str += "%s" % public_as_only_replace + + if public_as_only_replace == "true": + if public_as_only_force != "no_use": + cmd = "peer %s public-as-only force replace" % remote_address + if public_as_only_limited != "no_use": + cmd = "peer %s public-as-only limited replace" % remote_address + else: + if public_as_only_force != "no_use": + cmd = "undo peer %s public-as-only force replace" % remote_address + if public_as_only_limited != "no_use": + cmd = "undo peer %s public-as-only limited replace" % remote_address + cmds.append(cmd) + + public_as_only_skip_peer_as = module.params[ + 'public_as_only_skip_peer_as'] + if public_as_only_skip_peer_as != 'no_use': + conf_str += "%s" % public_as_only_skip_peer_as + + if public_as_only_skip_peer_as == "true": + if public_as_only_force != "no_use": + cmd = "peer %s public-as-only force include-peer-as" % remote_address + if public_as_only_limited != "no_use": + cmd = "peer %s public-as-only limited include-peer-as" % remote_address + else: + if public_as_only_force != "no_use": + cmd = "undo peer %s public-as-only force include-peer-as" % remote_address + if public_as_only_limited != "no_use": + cmd = "undo peer %s public-as-only limited include-peer-as" % remote_address + cmds.append(cmd) + + route_limit_sign = "route-limit" + if af_type == "evpn": + route_limit_sign = "mac-limit" + route_limit = module.params['route_limit'] + if route_limit: + conf_str += "%s" % route_limit + + cmd = "peer %s %s %s" % (remote_address, route_limit_sign, route_limit) + cmds.append(cmd) + + route_limit_percent = module.params['route_limit_percent'] + if route_limit_percent: + conf_str += "%s" % route_limit_percent + + cmd = "peer %s %s %s %s" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + + route_limit_type = module.params['route_limit_type'] + if route_limit_type: + conf_str += "%s" % route_limit_type + + if route_limit_type == "alertOnly": + cmd = "peer %s %s %s %s alert-only" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + elif route_limit_type == "idleForever": + cmd = "peer %s %s %s %s idle-forever" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + elif route_limit_type == "idleTimeout": + cmd = "peer %s %s %s %s idle-timeout" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + if route_limit_idle_timeout: + conf_str += "%s" % route_limit_idle_timeout + + cmd = "peer %s %s %s %s idle-timeout %s" % (remote_address, route_limit_sign, route_limit, route_limit_percent, route_limit_idle_timeout) + cmds.append(cmd) + + rt_updt_interval = module.params['rt_updt_interval'] + if rt_updt_interval: + conf_str += "%s" % rt_updt_interval + + cmd = "peer %s route-update-interval %s" % (remote_address, rt_updt_interval) + cmds.append(cmd) + + redirect_ip = module.params['redirect_ip'] + if redirect_ip != 'no_use': + conf_str += "%s" % redirect_ip + + redirect_ip_validation = module.params['redirect_ip_validation'] + if redirect_ip_validation != 'no_use': + conf_str += "%s" % redirect_ip_validation + + reflect_client = module.params['reflect_client'] + if reflect_client != 'no_use': + conf_str += "%s" % reflect_client + + if reflect_client == "true": + cmd = "peer %s reflect-client" % remote_address + else: + cmd = "undo peer %s reflect-client" % remote_address + cmds.append(cmd) + + substitute_as_enable = module.params['substitute_as_enable'] + if substitute_as_enable != 'no_use': + conf_str += "%s" % substitute_as_enable + + if substitute_as_enable == "true": + cmd = "peer %s substitute-as" % remote_address + else: + cmd = "undo peer %s substitute-as" % remote_address + cmds.append(cmd) + + import_rt_policy_name = module.params['import_rt_policy_name'] + if import_rt_policy_name: + conf_str += "%s" % import_rt_policy_name + + cmd = "peer %s route-policy %s import" % (remote_address, import_rt_policy_name) + cmds.append(cmd) + + export_rt_policy_name = module.params['export_rt_policy_name'] + if export_rt_policy_name: + conf_str += "%s" % export_rt_policy_name + + cmd = "peer %s route-policy %s export" % (remote_address, export_rt_policy_name) + cmds.append(cmd) + + import_pref_filt_name = module.params['import_pref_filt_name'] + if import_pref_filt_name: + conf_str += "%s" % import_pref_filt_name + + cmd = "peer %s ip-prefix %s import" % (remote_address, import_pref_filt_name) + cmds.append(cmd) + + export_pref_filt_name = module.params['export_pref_filt_name'] + if export_pref_filt_name: + conf_str += "%s" % export_pref_filt_name + + cmd = "peer %s ip-prefix %s export" % (remote_address, export_pref_filt_name) + cmds.append(cmd) + + import_as_path_filter = module.params['import_as_path_filter'] + if import_as_path_filter: + conf_str += "%s" % import_as_path_filter + + cmd = "peer %s as-path-filter %s import" % (remote_address, import_as_path_filter) + cmds.append(cmd) + + export_as_path_filter = module.params['export_as_path_filter'] + if export_as_path_filter: + conf_str += "%s" % export_as_path_filter + + cmd = "peer %s as-path-filter %s export" % (remote_address, export_as_path_filter) + cmds.append(cmd) + + import_as_path_name_or_num = module.params[ + 'import_as_path_name_or_num'] + if import_as_path_name_or_num: + conf_str += "%s" % import_as_path_name_or_num + + cmd = "peer %s as-path-filter %s import" % (remote_address, import_as_path_name_or_num) + cmds.append(cmd) + + export_as_path_name_or_num = module.params[ + 'export_as_path_name_or_num'] + if export_as_path_name_or_num: + conf_str += "%s" % export_as_path_name_or_num + + cmd = "peer %s as-path-filter %s export" % (remote_address, export_as_path_name_or_num) + cmds.append(cmd) + + import_acl_name_or_num = module.params['import_acl_name_or_num'] + if import_acl_name_or_num: + conf_str += "%s" % import_acl_name_or_num + if import_acl_name_or_num.isdigit(): + cmd = "peer %s filter-policy %s import" % (remote_address, import_acl_name_or_num) + else: + cmd = "peer %s filter-policy acl-name %s import" % (remote_address, import_acl_name_or_num) + cmds.append(cmd) + + export_acl_name_or_num = module.params['export_acl_name_or_num'] + if export_acl_name_or_num: + conf_str += "%s" % export_acl_name_or_num + if export_acl_name_or_num.isdigit(): + cmd = "peer %s filter-policy %s export" % (remote_address, export_acl_name_or_num) + else: + cmd = "peer %s filter-policy acl-name %s export" % (remote_address, export_acl_name_or_num) + cmds.append(cmd) + + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + if ipprefix_orf_enable != 'no_use': + conf_str += "%s" % ipprefix_orf_enable + + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf ip-prefix" % remote_address + else: + cmd = "undo peer %s capability-advertise orf ip-prefix" % remote_address + cmds.append(cmd) + + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + if is_nonstd_ipprefix_mod != 'no_use': + conf_str += "%s" % is_nonstd_ipprefix_mod + + if is_nonstd_ipprefix_mod == "true": + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf non-standard-compatible" % remote_address + else: + cmd = "undo peer %s capability-advertise orf non-standard-compatible" % remote_address + cmds.append(cmd) + else: + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf" % remote_address + else: + cmd = "undo peer %s capability-advertise orf" % remote_address + cmds.append(cmd) + + orftype = module.params['orftype'] + if orftype: + conf_str += "%s" % orftype + + orf_mode = module.params['orf_mode'] + if orf_mode: + conf_str += "%s" % orf_mode + + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf ip-prefix %s" % (remote_address, orf_mode) + else: + cmd = "undo peer %s capability-advertise orf ip-prefix %s" % (remote_address, orf_mode) + cmds.append(cmd) + + soostring = module.params['soostring'] + if soostring: + conf_str += "%s" % soostring + + cmd = "peer %s soo %s" % (remote_address, soostring) + cmds.append(cmd) + + cmd = "" + default_rt_adv_enable = module.params['default_rt_adv_enable'] + if default_rt_adv_enable != 'no_use': + conf_str += "%s" % default_rt_adv_enable + + if default_rt_adv_enable == "true": + cmd += "peer %s default-route-advertise" % remote_address + else: + cmd += "undo peer %s default-route-advertise" % remote_address + + default_rt_adv_policy = module.params['default_rt_adv_policy'] + if default_rt_adv_policy: + conf_str += "%s" % default_rt_adv_policy + cmd += " route-policy %s" % default_rt_adv_policy + + default_rt_match_mode = module.params['default_rt_match_mode'] + if default_rt_match_mode: + conf_str += "%s" % default_rt_match_mode + + if default_rt_match_mode == "matchall": + cmd += " conditional-route-match-all" + elif default_rt_match_mode == "matchany": + cmd += " conditional-route-match-any" + + if cmd: + cmds.append(cmd) + + add_path_mode = module.params['add_path_mode'] + if add_path_mode: + conf_str += "%s" % add_path_mode + if add_path_mode == "receive": + cmd = "peer %s capability-advertise add-path receive" % remote_address + elif add_path_mode == "send": + cmd = "peer %s capability-advertise add-path send" % remote_address + elif add_path_mode == "both": + cmd = "peer %s capability-advertise add-path both" % remote_address + cmds.append(cmd) + + adv_add_path_num = module.params['adv_add_path_num'] + if adv_add_path_num: + conf_str += "%s" % adv_add_path_num + cmd = "peer %s advertise add-path path-number %s" % (remote_address, adv_add_path_num) + cmds.append(cmd) + origin_as_valid = module.params['origin_as_valid'] + if origin_as_valid != 'no_use': + conf_str += "%s" % origin_as_valid + + vpls_enable = module.params['vpls_enable'] + if vpls_enable != 'no_use': + conf_str += "%s" % vpls_enable + + vpls_ad_disable = module.params['vpls_ad_disable'] + if vpls_ad_disable != 'no_use': + conf_str += "%s" % vpls_ad_disable + + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + if update_pkt_standard_compatible != 'no_use': + conf_str += "%s" % update_pkt_standard_compatible + + conf_str += CE_MERGE_BGP_PEER_AF_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer address family other failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + af_type=dict(choices=['ipv4uni', 'ipv4multi', 'ipv4vpn', + 'ipv6uni', 'ipv6vpn', 'evpn'], required=True), + remote_address=dict(type='str', required=True), + advertise_irb=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_arp=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_remote_nexthop=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_ext_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + discard_ext_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + allow_as_loop_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + allow_as_loop_limit=dict(type='str'), + keep_all_routes=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + nexthop_configure=dict(choices=['null', 'local', 'invariable']), + preferred_value=dict(type='str'), + public_as_only=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_force=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_limited=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_replace=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_skip_peer_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + route_limit=dict(type='str'), + route_limit_percent=dict(type='str'), + route_limit_type=dict( + choices=['noparameter', 'alertOnly', 'idleForever', 'idleTimeout']), + route_limit_idle_timeout=dict(type='str'), + rt_updt_interval=dict(type='str'), + redirect_ip=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + redirect_ip_validation=dict( + type='str', default='no_use', + choices=['no_use', 'true', 'false'], aliases=['redirect_ip_vaildation']), + reflect_client=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + substitute_as_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + import_rt_policy_name=dict(type='str'), + export_rt_policy_name=dict(type='str'), + import_pref_filt_name=dict(type='str'), + export_pref_filt_name=dict(type='str'), + import_as_path_filter=dict(type='str'), + export_as_path_filter=dict(type='str'), + import_as_path_name_or_num=dict(type='str'), + export_as_path_name_or_num=dict(type='str'), + import_acl_name_or_num=dict(type='str'), + export_acl_name_or_num=dict(type='str'), + ipprefix_orf_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_nonstd_ipprefix_mod=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + orftype=dict(type='str'), + orf_mode=dict(choices=['null', 'receive', 'send', 'both']), + soostring=dict(type='str'), + default_rt_adv_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_rt_adv_policy=dict(type='str'), + default_rt_match_mode=dict(choices=['null', 'matchall', 'matchany']), + add_path_mode=dict(choices=['null', 'receive', 'send', 'both']), + adv_add_path_num=dict(type='str'), + origin_as_valid=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + vpls_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + vpls_ad_disable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + update_pkt_standard_compatible=dict(type='str', default='no_use', choices=['no_use', 'true', 'false'])) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + advertise_irb = module.params['advertise_irb'] + advertise_arp = module.params['advertise_arp'] + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + advertise_community = module.params['advertise_community'] + advertise_ext_community = module.params['advertise_ext_community'] + discard_ext_community = module.params['discard_ext_community'] + allow_as_loop_enable = module.params['allow_as_loop_enable'] + allow_as_loop_limit = module.params['allow_as_loop_limit'] + keep_all_routes = module.params['keep_all_routes'] + nexthop_configure = module.params['nexthop_configure'] + preferred_value = module.params['preferred_value'] + public_as_only = module.params['public_as_only'] + public_as_only_force = module.params['public_as_only_force'] + public_as_only_limited = module.params['public_as_only_limited'] + public_as_only_replace = module.params['public_as_only_replace'] + public_as_only_skip_peer_as = module.params['public_as_only_skip_peer_as'] + route_limit = module.params['route_limit'] + route_limit_percent = module.params['route_limit_percent'] + route_limit_type = module.params['route_limit_type'] + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + rt_updt_interval = module.params['rt_updt_interval'] + redirect_ip = module.params['redirect_ip'] + redirect_ip_validation = module.params['redirect_ip_validation'] + reflect_client = module.params['reflect_client'] + substitute_as_enable = module.params['substitute_as_enable'] + import_rt_policy_name = module.params['import_rt_policy_name'] + export_rt_policy_name = module.params['export_rt_policy_name'] + import_pref_filt_name = module.params['import_pref_filt_name'] + export_pref_filt_name = module.params['export_pref_filt_name'] + import_as_path_filter = module.params['import_as_path_filter'] + export_as_path_filter = module.params['export_as_path_filter'] + import_as_path_name_or_num = module.params['import_as_path_name_or_num'] + export_as_path_name_or_num = module.params['export_as_path_name_or_num'] + import_acl_name_or_num = module.params['import_acl_name_or_num'] + export_acl_name_or_num = module.params['export_acl_name_or_num'] + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + orftype = module.params['orftype'] + orf_mode = module.params['orf_mode'] + soostring = module.params['soostring'] + default_rt_adv_enable = module.params['default_rt_adv_enable'] + default_rt_adv_policy = module.params['default_rt_adv_policy'] + default_rt_match_mode = module.params['default_rt_match_mode'] + add_path_mode = module.params['add_path_mode'] + adv_add_path_num = module.params['adv_add_path_num'] + origin_as_valid = module.params['origin_as_valid'] + vpls_enable = module.params['vpls_enable'] + vpls_ad_disable = module.params['vpls_ad_disable'] + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + + ce_bgp_peer_af_obj = BgpNeighborAf() + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if af_type: + proposed["af_type"] = af_type + if remote_address: + proposed["remote_address"] = remote_address + if advertise_irb != 'no_use': + proposed["advertise_irb"] = advertise_irb + if advertise_arp != 'no_use': + proposed["advertise_arp"] = advertise_arp + if advertise_remote_nexthop != 'no_use': + proposed["advertise_remote_nexthop"] = advertise_remote_nexthop + if advertise_community != 'no_use': + proposed["advertise_community"] = advertise_community + if advertise_ext_community != 'no_use': + proposed["advertise_ext_community"] = advertise_ext_community + if discard_ext_community != 'no_use': + proposed["discard_ext_community"] = discard_ext_community + if allow_as_loop_enable != 'no_use': + proposed["allow_as_loop_enable"] = allow_as_loop_enable + if allow_as_loop_limit: + proposed["allow_as_loop_limit"] = allow_as_loop_limit + if keep_all_routes != 'no_use': + proposed["keep_all_routes"] = keep_all_routes + if nexthop_configure: + proposed["nexthop_configure"] = nexthop_configure + if preferred_value: + proposed["preferred_value"] = preferred_value + if public_as_only != 'no_use': + proposed["public_as_only"] = public_as_only + if public_as_only_force != 'no_use': + proposed["public_as_only_force"] = public_as_only_force + if public_as_only_limited != 'no_use': + proposed["public_as_only_limited"] = public_as_only_limited + if public_as_only_replace != 'no_use': + proposed["public_as_only_replace"] = public_as_only_replace + if public_as_only_skip_peer_as != 'no_use': + proposed["public_as_only_skip_peer_as"] = public_as_only_skip_peer_as + if route_limit: + proposed["route_limit"] = route_limit + if route_limit_percent: + proposed["route_limit_percent"] = route_limit_percent + if route_limit_type: + proposed["route_limit_type"] = route_limit_type + if route_limit_idle_timeout: + proposed["route_limit_idle_timeout"] = route_limit_idle_timeout + if rt_updt_interval: + proposed["rt_updt_interval"] = rt_updt_interval + if redirect_ip != 'no_use': + proposed["redirect_ip"] = redirect_ip + if redirect_ip_validation != 'no_use': + proposed["redirect_ip_validation"] = redirect_ip_validation + if reflect_client != 'no_use': + proposed["reflect_client"] = reflect_client + if substitute_as_enable != 'no_use': + proposed["substitute_as_enable"] = substitute_as_enable + if import_rt_policy_name: + proposed["import_rt_policy_name"] = import_rt_policy_name + if export_rt_policy_name: + proposed["export_rt_policy_name"] = export_rt_policy_name + if import_pref_filt_name: + proposed["import_pref_filt_name"] = import_pref_filt_name + if export_pref_filt_name: + proposed["export_pref_filt_name"] = export_pref_filt_name + if import_as_path_filter: + proposed["import_as_path_filter"] = import_as_path_filter + if export_as_path_filter: + proposed["export_as_path_filter"] = export_as_path_filter + if import_as_path_name_or_num: + proposed["import_as_path_name_or_num"] = import_as_path_name_or_num + if export_as_path_name_or_num: + proposed["export_as_path_name_or_num"] = export_as_path_name_or_num + if import_acl_name_or_num: + proposed["import_acl_name_or_num"] = import_acl_name_or_num + if export_acl_name_or_num: + proposed["export_acl_name_or_num"] = export_acl_name_or_num + if ipprefix_orf_enable != 'no_use': + proposed["ipprefix_orf_enable"] = ipprefix_orf_enable + if is_nonstd_ipprefix_mod != 'no_use': + proposed["is_nonstd_ipprefix_mod"] = is_nonstd_ipprefix_mod + if orftype: + proposed["orftype"] = orftype + if orf_mode: + proposed["orf_mode"] = orf_mode + if soostring: + proposed["soostring"] = soostring + if default_rt_adv_enable != 'no_use': + proposed["default_rt_adv_enable"] = default_rt_adv_enable + if default_rt_adv_policy: + proposed["default_rt_adv_policy"] = default_rt_adv_policy + if default_rt_match_mode: + proposed["default_rt_match_mode"] = default_rt_match_mode + if add_path_mode: + proposed["add_path_mode"] = add_path_mode + if adv_add_path_num: + proposed["adv_add_path_num"] = adv_add_path_num + if origin_as_valid != 'no_use': + proposed["origin_as_valid"] = origin_as_valid + if vpls_enable != 'no_use': + proposed["vpls_enable"] = vpls_enable + if vpls_ad_disable != 'no_use': + proposed["vpls_ad_disable"] = vpls_ad_disable + if update_pkt_standard_compatible != 'no_use': + proposed["update_pkt_standard_compatible"] = update_pkt_standard_compatible + + if not ce_bgp_peer_af_obj: + module.fail_json(msg='Error: Init module failed.') + + bgp_peer_af_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_args( + module=module) + bgp_peer_af_other_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_other( + module=module) + + # state exist bgp peer address family config + exist_tmp = dict() + for item in bgp_peer_af_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_peer_af_rst[item] + if exist_tmp: + existing["bgp neighbor af"] = exist_tmp + # state exist bgp peer address family other config + exist_tmp = dict() + for item in bgp_peer_af_other_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_peer_af_other_rst[item] + if exist_tmp: + existing["bgp neighbor af other"] = exist_tmp + + if state == "present": + if bgp_peer_af_rst["need_cfg"]: + if "remote_address" in bgp_peer_af_rst.keys(): + cmd = ce_bgp_peer_af_obj.merge_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_peer_af_obj.create_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_peer_af_other_rst["need_cfg"]: + cmd = ce_bgp_peer_af_obj.merge_bgp_peer_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if bgp_peer_af_rst["need_cfg"]: + cmd = ce_bgp_peer_af_obj.delete_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_peer_af_other_rst["need_cfg"]: + pass + + # state end bgp peer address family config + bgp_peer_af_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_args( + module=module) + end_tmp = dict() + for item in bgp_peer_af_rst: + if item != "need_cfg": + end_tmp[item] = bgp_peer_af_rst[item] + if end_tmp: + end_state["bgp neighbor af"] = end_tmp + # state end bgp peer address family other config + bgp_peer_af_other_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_other( + module=module) + end_tmp = dict() + for item in bgp_peer_af_other_rst: + if item != "need_cfg": + end_tmp[item] = bgp_peer_af_other_rst[item] + if end_tmp: + end_state["bgp neighbor af other"] = end_tmp + if end_state == existing: + changed = False + updates = list() + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_command.py new file mode 100644 index 00000000..4bfe3dc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_command.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_command +author: "JackyGao2016 (@CloudEngine-Ansible)" +short_description: Run arbitrary command on HUAWEI CloudEngine devices. +description: + - Sends an arbitrary command to an HUAWEI CloudEngine node and returns + the results read from the device. The ce_command module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + commands: + description: + - The commands to send to the remote HUAWEI CloudEngine device + over the configured provider. The resulting output from the + command is returned. If the I(wait_for) argument is provided, + the module is not returned until the condition is satisfied + or the number of I(retries) has been exceeded. + required: true + wait_for: + description: + - Specifies what to evaluate from the output of the command + and what conditionals to apply. This argument will cause + the task to wait for a particular conditional to be true + before moving forward. If the conditional is not true + by the configured retries, the task fails. See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the I(wait_for) must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the I(wait_for) + conditionals. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditional, the interval indicates how to long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine command test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: "Run display version on remote devices" + community.network.ce_command: + commands: display version + provider: "{{ cli }}" + + - name: "Run display version and check to see if output contains HUAWEI" + community.network.ce_command: + commands: display version + wait_for: result[0] contains HUAWEI + provider: "{{ cli }}" + + - name: "Run multiple commands on remote nodes" + community.network.ce_command: + commands: + - display version + - display device + provider: "{{ cli }}" + + - name: "Run multiple commands and evaluate the output" + community.network.ce_command: + commands: + - display version + - display device + wait_for: + - result[0] contains HUAWEI + - result[1] contains Device + provider: "{{ cli }}" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + + +import time +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_native + + +def to_lines(stdout): + lines = list() + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + lines.append(item) + return lines + + +def parse_commands(module, warnings): + transform = ComplexList(dict( + command=dict(key=True), + output=dict(), + prompt=dict(), + answer=dict() + ), module) + + commands = transform(module.params['commands']) + + for _, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('dis'): + warnings.append( + 'Only display commands are supported when using check_mode, not ' + 'executing %s' % item['command'] + ) + + return commands + + +def to_cli(obj): + cmd = obj['command'] + return cmd + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + # { command: , output: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['any', 'all']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + + try: + conditionals = [Conditional(c) for c in wait_for] + except AttributeError as exc: + module.fail_json(msg=to_native(exc), exception=traceback.format_exc()) + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'stdout': responses, + 'stdout_lines': to_lines(responses) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_config.py new file mode 100644 index 00000000..2f8e71bb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_config.py @@ -0,0 +1,492 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_config +author: "QijunPan (@QijunPan)" +short_description: Manage Huawei CloudEngine configuration sections. +description: + - Huawei CloudEngine configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with CloudEngine configuration sections in + a deterministic way. This module works with CLI transports. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device current-configuration. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - The I(src) argument provides a path to the configuration file + to load into the remote system. The path can either be a full + system path to the configuration file if the value starts with / + or relative to the root of the implemented role or playbook. + This argument is mutually exclusive with the I(lines) and + I(parents) arguments. + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the current-configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - This argument will cause the module to create a full backup of + the current C(current-configuration) from the remote device before any + changes are made. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current current-configuration to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current-configuration for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + defaults: + description: + - The I(defaults) argument will influence how the current-configuration + is collected from the device. When the value is set to true, + the command used to collect the current-configuration is append with + the all keyword. When the value is set to false, the command + is issued without the all keyword. + type: bool + default: 'no' + save: + description: + - The C(save) argument instructs the module to save the + current-configuration to saved-configuration. This operation is performed + after any changes are made to the current running config. If + no changes are made, the configuration is still saved to the + startup config. This option will always cause the module to + return changed. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine config test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: "Configure top level configuration and save it" + community.network.ce_config: + lines: sysname {{ inventory_hostname }} + save: yes + provider: "{{ cli }}" + + - name: "Configure acl configuration and save it" + community.network.ce_config: + lines: + - rule 10 permit source 1.1.1.1 32 + - rule 20 permit source 2.2.2.2 32 + - rule 30 permit source 3.3.3.3 32 + - rule 40 permit source 4.4.4.4 32 + - rule 50 permit source 5.5.5.5 32 + parents: acl 2000 + before: undo acl 2000 + match: exact + provider: "{{ cli }}" + + - name: "Configure acl configuration and save it" + community.network.ce_config: + lines: + - rule 10 permit source 1.1.1.1 32 + - rule 20 permit source 2.2.2.2 32 + - rule 30 permit source 3.3.3.3 32 + - rule 40 permit source 4.4.4.4 32 + parents: acl 2000 + before: undo acl 2000 + replace: block + provider: "{{ cli }}" + + - name: Configurable backup path + community.network.ce_config: + lines: sysname {{ inventory_hostname }} + provider: "{{ cli }}" + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ce_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError, Connection +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig as _NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps, ConfigLine, ignore_line +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_config, run_commands, exec_command, cli_err_msg +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import check_args as ce_check_args +import re + + +def check_args(module, warnings): + ce_check_args(module, warnings) + + +def not_user_view(prompt): + return prompt is not None and prompt.strip().startswith("[") + + +def command_level(command): + regex_level = re.search(r"^(\s*)\S+", command) + if regex_level is not None: + level = str(regex_level.group(1)) + return len(level) + return 0 + + +def _load_config(module, config): + """Sends configuration commands to the remote device + """ + connection = Connection(module._socket_path) + rc, out, err = exec_command(module, 'mmi-mode enable') + if rc != 0: + module.fail_json(msg='unable to set mmi-mode enable', output=err) + rc, out, err = exec_command(module, 'system-view immediately') + if rc != 0: + module.fail_json(msg='unable to enter system-view', output=err) + current_view_prompt = system_view_prompt = connection.get_prompt() + + for index, cmd in enumerate(config): + level = command_level(cmd) + current_view_prompt = connection.get_prompt() + rc, out, err = exec_command(module, cmd) + if rc != 0: + print_msg = cli_err_msg(cmd.strip(), err) + # re-try command max 3 times + for i in (1, 2, 3): + current_view_prompt = connection.get_prompt() + if current_view_prompt != system_view_prompt and not_user_view(current_view_prompt): + exec_command(module, "quit") + current_view_prompt = connection.get_prompt() + # if current view is system-view, break. + if current_view_prompt == system_view_prompt and level > 0: + break + elif current_view_prompt == system_view_prompt or not not_user_view(current_view_prompt): + break + rc, out, err = exec_command(module, cmd) + if rc == 0: + print_msg = None + break + if print_msg is not None: + module.fail_json(msg=print_msg) + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + command = "display current-configuration " + if module.params['defaults']: + command += 'include-default' + resp = run_commands(module, command) + contents = resp[0] + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + config = module.params['src'] + candidate.load(config) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + + if match != 'none': + before = get_running_config(module) + path = module.params['parents'] + configobjs = candidate.difference(before, match=match, replace=replace, path=path) + else: + configobjs = candidate.items + + if configobjs: + out_type = "commands" + if module.params["src"] is not None: + out_type = "raw" + commands = dumps(configobjs, out_type).split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + command_display = [] + for per_command in commands: + if per_command.strip() not in ['quit', 'return', 'system-view']: + command_display.append(per_command) + + result['commands'] = command_display + result['updates'] = command_display + + if not module.check_mode: + if module.params['parents'] is not None: + load_config(module, commands) + else: + _load_config(module, commands) + if match != "none": + after = get_running_config(module) + path = module.params["parents"] + if path is not None and match != 'line': + before_objs = before.get_block(path) + after_objs = after.get_block(path) + update = [] + if len(before_objs) == len(after_objs): + for b_item, a_item in zip(before_objs, after_objs): + if b_item != a_item: + update.append(a_item.text) + else: + update = [item.text for item in after_objs] + if len(update) == 0: + result["changed"] = False + result['updates'] = [] + else: + result["changed"] = True + result['updates'] = update + else: + configobjs = after.difference(before, match=match, replace=replace, path=path) + if len(configobjs) > 0: + result["changed"] = True + else: + result["changed"] = False + result['updates'] = [] + else: + result['changed'] = True + + +class NetworkConfig(_NetworkConfig): + + def add(self, lines, parents=None): + ancestors = list() + offset = 0 + obj = None + + # global config command + if not parents: + for line in lines: + # handle ignore lines + if ignore_line(line): + continue + + item = ConfigLine(line) + item.raw = line + self.items.append(item) + + else: + for index, p in enumerate(parents): + try: + i = index + 1 + obj = self.get_block(parents[:i])[0] + ancestors.append(obj) + + except ValueError: + # add parent to config + offset = index * self._indent + obj = ConfigLine(p) + obj.raw = p.rjust(len(p) + offset) + if ancestors: + obj._parents = list(ancestors) + ancestors[-1]._children.append(obj) + self.items.append(obj) + ancestors.append(obj) + + # add child objects + for line in lines: + # handle ignore lines + if ignore_line(line): + continue + + # check if child already exists + for child in ancestors[-1]._children: + if child.text == line: + break + else: + offset = len(parents) * self._indent + item = ConfigLine(line) + item.raw = line.rjust(len(line) + offset) + item._parents = ancestors + ancestors[-1]._children.append(item) + self.items.append(item) + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + config=dict(), + defaults=dict(type='bool', default=False), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + argument_spec.update(ce_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + if any((module.params['src'], module.params['lines'])): + run(module, result) + + if module.params['save']: + if not module.check_mode: + run_commands(module, ['return', 'mmi-mode enable', 'save']) + result["changed"] = True + run_commands(module, ['return', 'undo mmi-mode enable']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp.py new file mode 100644 index 00000000..f292e0b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp.py @@ -0,0 +1,549 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_dldp +short_description: Manages global DLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages global DLDP configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - The relevant configurations will be deleted if DLDP is disabled using enable=disable. + - When using auth_mode=none, it will restore the default DLDP authentication mode. By default, + DLDP packets are not authenticated. + - By default, the working mode of DLDP is enhance, so you are advised to use work_mode=enhance to restore default + DLDP working mode. + - The default interval for sending Advertisement packets is 5 seconds, so you are advised to use time_interval=5 to + restore default DLDP interval. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + enable: + description: + - Set global DLDP enable state. + choices: ['enable', 'disable'] + work_mode: + description: + - Set global DLDP work-mode. + choices: ['enhance', 'normal'] + time_internal: + description: + - Specifies the interval for sending Advertisement packets. + The value is an integer ranging from 1 to 100, in seconds. + The default interval for sending Advertisement packets is 5 seconds. + auth_mode: + description: + - Specifies authentication algorithm of DLDP. + choices: ['md5', 'simple', 'sha', 'hmac-sha256', 'none'] + auth_pwd: + description: + - Specifies authentication password. + The value is a string of 1 to 16 case-sensitive plaintexts or 24/32/48/108/128 case-sensitive encrypted + characters. The string excludes a question mark (?). + reset: + description: + - Specify whether reset DLDP state of disabled interfaces. + choices: ['enable', 'disable'] +''' + +EXAMPLES = ''' +- name: DLDP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure global DLDP enable state" + community.network.ce_dldp: + enable: enable + provider: "{{ cli }}" + + - name: "Configure DLDP work-mode and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + work_mode: normal + provider: "{{ cli }}" + + - name: "Configure advertisement message time interval in seconds and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + time_interval: 6 + provider: "{{ cli }}" + + - name: "Configure a DLDP authentication mode and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + auth_mode: md5 + auth_pwd: abc + provider: "{{ cli }}" + + - name: "Reset DLDP state of disabled interfaces and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + reset: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "enable": "enable", + "reset": "enable", + "time_internal": "12", + "work_mode": "normal" + } +existing: + description: k/v pairs of existing global DLDP configuration + returned: always + type: dict + sample: { + "enable": "disable", + "reset": "disable", + "time_internal": "5", + "work_mode": "enhance" + } +end_state: + description: k/v pairs of global DLDP configuration after module execution + returned: always + type: dict + sample: { + "enable": "enable", + "reset": "enable", + "time_internal": "12", + "work_mode": "normal" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "dldp enable", + "dldp work-mode normal", + "dldp interval 12", + "dldp reset" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, set_nc_config, get_nc_config, execute_nc_action + +CE_NC_ACTION_RESET_DLDP = """ + + + + + +""" + +CE_NC_GET_GLOBAL_DLDP_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_DLDP_GLOBAL_CONFIG_HEAD = """ + + + + %s + %s + %s +""" + +CE_NC_MERGE_DLDP_GLOBAL_CONFIG_TAIL = """ + + + +""" + + +class Dldp(object): + """Manage global dldp configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # DLDP global configuration info + self.enable = self.module.params['enable'] or None + self.work_mode = self.module.params['work_mode'] or None + self.internal = self.module.params['time_interval'] or None + self.reset = self.module.params['reset'] or None + self.auth_mode = self.module.params['auth_mode'] + self.auth_pwd = self.module.params['auth_pwd'] + + self.dldp_conf = dict() + self.same_conf = False + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + def check_config_if_same(self): + """Judge whether current config is the same as what we excepted""" + + if self.enable and self.enable != self.dldp_conf['dldpEnable']: + return False + + if self.internal and self.internal != self.dldp_conf['dldpInterval']: + return False + + work_mode = 'normal' + if self.dldp_conf['dldpWorkMode'] == 'dldpEnhance': + work_mode = 'enhance' + if self.work_mode and self.work_mode != work_mode: + return False + + if self.auth_mode: + if self.auth_mode != 'none': + return False + + if self.auth_mode == 'none' and self.dldp_conf['dldpAuthMode'] != 'dldpAuthNone': + return False + + if self.reset and self.reset == 'enable': + return False + + return True + + def check_params(self): + """Check all input params""" + + if (self.auth_mode and self.auth_mode != 'none' and not self.auth_pwd) \ + or (self.auth_pwd and not self.auth_mode): + self.module.fail_json(msg="Error: auth_mode and auth_pwd must both exist or not exist.") + + if self.dldp_conf['dldpEnable'] == 'disable' and not self.enable: + if self.work_mode or self.reset or self.internal or self.auth_mode: + self.module.fail_json(msg="Error: when DLDP is already disabled globally, " + "work_mode, time_internal auth_mode and reset parameters are not " + "expected to configure.") + + if self.enable == 'disable' and (self.work_mode or self.internal or self.reset or self.auth_mode): + self.module.fail_json(msg="Error: when using enable=disable, work_mode, " + "time_internal auth_mode and reset parameters are not expected " + "to configure.") + + if self.internal: + if not self.internal.isdigit(): + self.module.fail_json( + msg='Error: time_interval must be digit.') + + if int(self.internal) < 1 or int(self.internal) > 100: + self.module.fail_json( + msg='Error: The value of time_internal should be between 1 and 100.') + + if self.auth_pwd: + if '?' in self.auth_pwd: + self.module.fail_json( + msg='Error: The auth_pwd string excludes a question mark (?).') + if (len(self.auth_pwd) != 24) and (len(self.auth_pwd) != 32) and (len(self.auth_pwd) != 48) and \ + (len(self.auth_pwd) != 108) and (len(self.auth_pwd) != 128): + if (len(self.auth_pwd) < 1) or (len(self.auth_pwd) > 16): + self.module.fail_json( + msg='Error: The value is a string of 1 to 16 case-sensitive plaintexts or 24/32/48/108/128 ' + 'case-sensitive encrypted characters.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dldp_exist_config(self): + """Get current dldp existed configuration""" + + dldp_conf = dict() + xml_str = CE_NC_GET_GLOBAL_DLDP_CONFIG + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return dldp_conf + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global DLDP info + root = ElementTree.fromstring(xml_str) + topo = root.find("dldp/dldpSys") + if not topo: + self.module.fail_json( + msg="Error: Get current DLDP configuration failed.") + + for eles in topo: + if eles.tag in ["dldpEnable", "dldpInterval", "dldpWorkMode", "dldpAuthMode"]: + if eles.tag == 'dldpEnable': + if eles.text == 'true': + value = 'enable' + else: + value = 'disable' + else: + value = eles.text + dldp_conf[eles.tag] = value + + return dldp_conf + + def config_global_dldp(self): + """Config global dldp""" + + if self.same_conf: + return + + enable = self.enable + if not self.enable: + enable = self.dldp_conf['dldpEnable'] + if enable == 'enable': + enable = 'true' + else: + enable = 'false' + + internal = self.internal + if not self.internal: + internal = self.dldp_conf['dldpInterval'] + + work_mode = self.work_mode + if not self.work_mode: + work_mode = self.dldp_conf['dldpWorkMode'] + + if work_mode == 'enhance' or work_mode == 'dldpEnhance': + work_mode = 'dldpEnhance' + else: + work_mode = 'dldpNormal' + + auth_mode = self.auth_mode + if not self.auth_mode: + auth_mode = self.dldp_conf['dldpAuthMode'] + if auth_mode == 'md5': + auth_mode = 'dldpAuthMD5' + elif auth_mode == 'simple': + auth_mode = 'dldpAuthSimple' + elif auth_mode == 'sha': + auth_mode = 'dldpAuthSHA' + elif auth_mode == 'hmac-sha256': + auth_mode = 'dldpAuthHMAC-SHA256' + elif auth_mode == 'none': + auth_mode = 'dldpAuthNone' + + xml_str = CE_NC_MERGE_DLDP_GLOBAL_CONFIG_HEAD % ( + enable, internal, work_mode) + if self.auth_mode: + if self.auth_mode == 'none': + xml_str += "dldpAuthNone" + else: + xml_str += "%s" % auth_mode + xml_str += "%s" % self.auth_pwd + + xml_str += CE_NC_MERGE_DLDP_GLOBAL_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MERGE_DLDP_GLOBAL_CONFIG") + + if self.reset == 'enable': + xml_str = CE_NC_ACTION_RESET_DLDP + ret_xml = execute_nc_action(self.module, xml_str) + self.check_response(ret_xml, "ACTION_RESET_DLDP") + + self.changed = True + + def get_existing(self): + """Get existing info""" + + dldp_conf = dict() + + dldp_conf['enable'] = self.dldp_conf.get('dldpEnable', None) + dldp_conf['time_interval'] = self.dldp_conf.get('dldpInterval', None) + work_mode = self.dldp_conf.get('dldpWorkMode', None) + if work_mode == 'dldpEnhance': + dldp_conf['work_mode'] = 'enhance' + else: + dldp_conf['work_mode'] = 'normal' + + auth_mode = self.dldp_conf.get('dldpAuthMode', None) + if auth_mode == 'dldpAuthNone': + dldp_conf['auth_mode'] = 'none' + elif auth_mode == 'dldpAuthSimple': + dldp_conf['auth_mode'] = 'simple' + elif auth_mode == 'dldpAuthMD5': + dldp_conf['auth_mode'] = 'md5' + elif auth_mode == 'dldpAuthSHA': + dldp_conf['auth_mode'] = 'sha' + else: + dldp_conf['auth_mode'] = 'hmac-sha256' + + dldp_conf['reset'] = 'disable' + + self.existing = copy.deepcopy(dldp_conf) + + def get_proposed(self): + """Get proposed result""" + + self.proposed = dict(enable=self.enable, work_mode=self.work_mode, + time_interval=self.internal, reset=self.reset, + auth_mode=self.auth_mode, auth_pwd=self.auth_pwd) + + def get_update_cmd(self): + """Get update commands""" + if self.same_conf: + return + + if self.enable and self.enable != self.dldp_conf['dldpEnable']: + if self.enable == 'enable': + self.updates_cmd.append("dldp enable") + elif self.enable == 'disable': + self.updates_cmd.append("undo dldp enable") + return + + work_mode = 'normal' + if self.dldp_conf['dldpWorkMode'] == 'dldpEnhance': + work_mode = 'enhance' + if self.work_mode and self.work_mode != work_mode: + if self.work_mode == 'enhance': + self.updates_cmd.append("dldp work-mode enhance") + else: + self.updates_cmd.append("dldp work-mode normal") + + if self.internal and self.internal != self.dldp_conf['dldpInterval']: + self.updates_cmd.append("dldp interval %s" % self.internal) + + if self.auth_mode: + if self.auth_mode == 'none': + self.updates_cmd.append("undo dldp authentication-mode") + else: + self.updates_cmd.append("dldp authentication-mode %s %s" % (self.auth_mode, self.auth_pwd)) + + if self.reset and self.reset == 'enable': + self.updates_cmd.append('dldp reset') + + def get_end_state(self): + """Get end state info""" + + dldp_conf = dict() + self.dldp_conf = self.get_dldp_exist_config() + + dldp_conf['enable'] = self.dldp_conf.get('dldpEnable', None) + dldp_conf['time_interval'] = self.dldp_conf.get('dldpInterval', None) + work_mode = self.dldp_conf.get('dldpWorkMode', None) + if work_mode == 'dldpEnhance': + dldp_conf['work_mode'] = 'enhance' + else: + dldp_conf['work_mode'] = 'normal' + + auth_mode = self.dldp_conf.get('dldpAuthMode', None) + if auth_mode == 'dldpAuthNone': + dldp_conf['auth_mode'] = 'none' + elif auth_mode == 'dldpAuthSimple': + dldp_conf['auth_mode'] = 'simple' + elif auth_mode == 'dldpAuthMD5': + dldp_conf['auth_mode'] = 'md5' + elif auth_mode == 'dldpAuthSHA': + dldp_conf['auth_mode'] = 'sha' + else: + dldp_conf['auth_mode'] = 'hmac-sha256' + + dldp_conf['reset'] = 'disable' + if self.reset == 'enable': + dldp_conf['reset'] = 'enable' + self.end_state = copy.deepcopy(dldp_conf) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Worker""" + + self.dldp_conf = self.get_dldp_exist_config() + self.check_params() + self.same_conf = self.check_config_if_same() + self.get_existing() + self.get_proposed() + self.config_global_dldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + enable=dict(choices=['enable', 'disable'], type='str'), + work_mode=dict(choices=['enhance', 'normal'], type='str'), + time_interval=dict(type='str'), + reset=dict(choices=['enable', 'disable'], type='str'), + auth_mode=dict(choices=['md5', 'simple', 'sha', 'hmac-sha256', 'none'], type='str'), + auth_pwd=dict(type='str', no_log=True), + ) + argument_spec.update(ce_argument_spec) + dldp_obj = Dldp(argument_spec) + dldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp_interface.py new file mode 100644 index 00000000..8a9d965a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_dldp_interface.py @@ -0,0 +1,658 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_dldp_interface +short_description: Manages interface DLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages interface DLDP configuration on HUAWEI CloudEngine switches. +author: + - Zhou Zhijin (@QijunPan) +notes: + - If C(state=present, enable=disable), interface DLDP enable will be turned off and + related interface DLDP configuration will be cleared. + - If C(state=absent), only local_mac is supported to configure. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Must be fully qualified interface name, i.e. GE1/0/1, 10GE1/0/1, 40GE1/0/22, 100GE1/0/1. + required: true + enable: + description: + - Set interface DLDP enable state. + choices: ['enable', 'disable'] + mode_enable: + description: + - Set DLDP compatible-mode enable state. + choices: ['enable', 'disable'] + local_mac: + description: + - Set the source MAC address for DLDP packets sent in the DLDP-compatible mode. + The value of MAC address is in H-H-H format. H contains 1 to 4 hexadecimal digits. + reset: + description: + - Specify whether reseting interface DLDP state. + choices: ['enable', 'disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: DLDP interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure interface DLDP enable state and ensure global dldp enable is turned on" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + provider: "{{ cli }}" + + - name: "Configuire interface DLDP compatible-mode enable state and ensure interface DLDP state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + mode_enable: enable + provider: "{{ cli }}" + + - name: "Configuire the source MAC address for DLDP packets sent in the DLDP-compatible mode and + ensure interface DLDP state and compatible-mode enable state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + mode_enable: enable + local_mac: aa-aa-aa + provider: "{{ cli }}" + + - name: "Reset DLDP state of specified interface and ensure interface DLDP state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + reset: enable + provider: "{{ cli }}" + + - name: "Unconfigure interface DLDP local mac address when C(state=absent)" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + state: absent + local_mac: aa-aa-aa + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "enable": "enalbe", + "interface": "40GE2/0/22", + "local_mac": "aa-aa-aa", + "mode_enable": "enable", + "reset": "enable" + } +existing: + description: k/v pairs of existing interface DLDP configuration + returned: always + type: dict + sample: { + "enable": "disable", + "interface": "40GE2/0/22", + "local_mac": null, + "mode_enable": null, + "reset": "disable" + } +end_state: + description: k/v pairs of interface DLDP configuration after module execution + returned: always + type: dict + sample: { + "enable": "enable", + "interface": "40GE2/0/22", + "local_mac": "00aa-00aa-00aa", + "mode_enable": "enable", + "reset": "enable" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "dldp enable", + "dldp compatible-mode enable", + "dldp compatible-mode local-mac aa-aa-aa", + "dldp reset" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, set_nc_config, get_nc_config, execute_nc_action + + +CE_NC_ACTION_RESET_INTF_DLDP = """ + + + + %s + + + +""" + +CE_NC_GET_INTF_DLDP_CONFIG = """ + + + + + %s + + + + + + + +""" + +CE_NC_MERGE_DLDP_INTF_CONFIG = """ + + + + + %s + %s + %s + %s + + + + +""" + +CE_NC_CREATE_DLDP_INTF_CONFIG = """ + + + + + %s + %s + %s + %s + + + + +""" + +CE_NC_DELETE_DLDP_INTF_CONFIG = """ + + + + + %s + + + + +""" + + +def judge_is_mac_same(mac1, mac2): + """Judge whether two macs are the same""" + + if mac1 == mac2: + return True + + list1 = re.findall(r'([0-9A-Fa-f]+)', mac1) + list2 = re.findall(r'([0-9A-Fa-f]+)', mac2) + if len(list1) != len(list2): + return False + + for index, value in enumerate(list1, start=0): + if value.lstrip('0').lower() != list2[index].lstrip('0').lower(): + return False + + return True + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class DldpInterface(object): + """Manage interface dldp configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # DLDP interface configuration info + self.interface = self.module.params['interface'] + self.enable = self.module.params['enable'] or None + self.reset = self.module.params['reset'] or None + self.mode_enable = self.module.params['mode_enable'] or None + self.local_mac = self.module.params['local_mac'] or None + self.state = self.module.params['state'] + + self.dldp_intf_conf = dict() + self.same_conf = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + def check_config_if_same(self): + """Judge whether current config is the same as what we excepted""" + + if self.state == 'absent': + return False + else: + if self.enable and self.enable != self.dldp_intf_conf['dldpEnable']: + return False + + if self.mode_enable and self.mode_enable != self.dldp_intf_conf['dldpCompatibleEnable']: + return False + + if self.local_mac: + flag = judge_is_mac_same( + self.local_mac, self.dldp_intf_conf['dldpLocalMac']) + if not flag: + return False + + if self.reset and self.reset == 'enable': + return False + return True + + def check_macaddr(self): + """Check mac-address whether valid""" + + valid_char = '0123456789abcdef-' + mac = self.local_mac + + if len(mac) > 16: + return False + + mac_list = re.findall(r'([0-9a-fA-F]+)', mac) + if len(mac_list) != 3: + return False + + if mac.count('-') != 2: + return False + + for _, value in enumerate(mac, start=0): + if value.lower() not in valid_char: + return False + + return True + + def check_params(self): + """Check all input params""" + + if not self.interface: + self.module.fail_json(msg='Error: Interface name cannot be empty.') + + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if (self.state == 'absent') and (self.reset or self.mode_enable or self.enable): + self.module.fail_json(msg="Error: It's better to use state=present when " + "configuring or unconfiguring enable, mode_enable " + "or using reset flag. state=absent is just for " + "when using local_mac param.") + + if self.state == 'absent' and not self.local_mac: + self.module.fail_json( + msg="Error: Please specify local_mac parameter.") + + if self.state == 'present': + if (self.dldp_intf_conf['dldpEnable'] == 'disable' and not self.enable and + (self.mode_enable or self.local_mac or self.reset)): + self.module.fail_json(msg="Error: when DLDP is already disabled on this port, " + "mode_enable, local_mac and reset parameters are not " + "expected to configure.") + + if self.enable == 'disable' and (self.mode_enable or self.local_mac or self.reset): + self.module.fail_json(msg="Error: when using enable=disable, " + "mode_enable, local_mac and reset parameters " + "are not expected to configure.") + + if self.local_mac and (self.mode_enable == 'disable' or + (self.dldp_intf_conf['dldpCompatibleEnable'] == 'disable' and self.mode_enable != 'enable')): + self.module.fail_json(msg="Error: when DLDP compatible-mode is disabled on this port, " + "Configuring local_mac is not allowed.") + + if self.local_mac: + if not self.check_macaddr(): + self.module.fail_json( + msg="Error: local_mac has invalid value %s." % self.local_mac) + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dldp_intf_exist_config(self): + """Get current dldp existed config""" + + dldp_conf = dict() + xml_str = CE_NC_GET_INTF_DLDP_CONFIG % self.interface + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + dldp_conf['dldpEnable'] = 'disable' + dldp_conf['dldpCompatibleEnable'] = "" + dldp_conf['dldpLocalMac'] = "" + return dldp_conf + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global DLDP info + root = ElementTree.fromstring(xml_str) + topo = root.find("dldp/dldpInterfaces/dldpInterface") + if topo is None: + self.module.fail_json( + msg="Error: Get current DLDP configuration failed.") + for eles in topo: + if eles.tag in ["dldpEnable", "dldpCompatibleEnable", "dldpLocalMac"]: + if not eles.text: + dldp_conf[eles.tag] = "" + else: + if eles.tag == "dldpEnable" or eles.tag == "dldpCompatibleEnable": + if eles.text == 'true': + value = 'enable' + else: + value = 'disable' + else: + value = eles.text + dldp_conf[eles.tag] = value + + return dldp_conf + + def config_intf_dldp(self): + """Config global dldp""" + + if self.same_conf: + return + + if self.state == "present": + enable = self.enable + if not self.enable: + enable = self.dldp_intf_conf['dldpEnable'] + if enable == 'enable': + enable = 'true' + else: + enable = 'false' + + mode_enable = self.mode_enable + if not self.mode_enable: + mode_enable = self.dldp_intf_conf['dldpCompatibleEnable'] + if mode_enable == 'enable': + mode_enable = 'true' + else: + mode_enable = 'false' + + local_mac = self.local_mac + if not self.local_mac: + local_mac = self.dldp_intf_conf['dldpLocalMac'] + + if self.enable == 'disable' and self.enable != self.dldp_intf_conf['dldpEnable']: + xml_str = CE_NC_DELETE_DLDP_INTF_CONFIG % self.interface + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "DELETE_DLDP_INTF_CONFIG") + elif self.dldp_intf_conf['dldpEnable'] == 'disable' and self.enable == 'enable': + xml_str = CE_NC_CREATE_DLDP_INTF_CONFIG % ( + self.interface, 'true', mode_enable, local_mac) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "CREATE_DLDP_INTF_CONFIG") + elif self.dldp_intf_conf['dldpEnable'] == 'enable': + if mode_enable == 'false': + local_mac = '' + xml_str = CE_NC_MERGE_DLDP_INTF_CONFIG % ( + self.interface, enable, mode_enable, local_mac) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MERGE_DLDP_INTF_CONFIG") + + if self.reset == 'enable': + xml_str = CE_NC_ACTION_RESET_INTF_DLDP % self.interface + ret_xml = execute_nc_action(self.module, xml_str) + self.check_response(ret_xml, "ACTION_RESET_INTF_DLDP") + + self.changed = True + else: + if self.local_mac and judge_is_mac_same(self.local_mac, self.dldp_intf_conf['dldpLocalMac']): + if self.dldp_intf_conf['dldpEnable'] == 'enable': + dldp_enable = 'true' + else: + dldp_enable = 'false' + if self.dldp_intf_conf['dldpCompatibleEnable'] == 'enable': + dldp_compat_enable = 'true' + else: + dldp_compat_enable = 'false' + xml_str = CE_NC_MERGE_DLDP_INTF_CONFIG % (self.interface, dldp_enable, dldp_compat_enable, '') + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "UNDO_DLDP_INTF_LOCAL_MAC_CONFIG") + self.changed = True + + def get_existing(self): + """Get existing info""" + + dldp_conf = dict() + + dldp_conf['interface'] = self.interface + dldp_conf['enable'] = self.dldp_intf_conf.get('dldpEnable', None) + dldp_conf['mode_enable'] = self.dldp_intf_conf.get( + 'dldpCompatibleEnable', None) + dldp_conf['local_mac'] = self.dldp_intf_conf.get('dldpLocalMac', None) + dldp_conf['reset'] = 'disable' + + self.existing = copy.deepcopy(dldp_conf) + + def get_proposed(self): + """Get proposed result """ + + self.proposed = dict(interface=self.interface, enable=self.enable, + mode_enable=self.mode_enable, local_mac=self.local_mac, + reset=self.reset, state=self.state) + + def get_update_cmd(self): + """Get updated commands""" + + if self.same_conf: + return + + if self.state == "present": + if self.enable and self.enable != self.dldp_intf_conf['dldpEnable']: + if self.enable == 'enable': + self.updates_cmd.append("dldp enable") + elif self.enable == 'disable': + self.updates_cmd.append("undo dldp enable") + + if self.mode_enable and self.mode_enable != self.dldp_intf_conf['dldpCompatibleEnable']: + if self.mode_enable == 'enable': + self.updates_cmd.append("dldp compatible-mode enable") + else: + self.updates_cmd.append("undo dldp compatible-mode enable") + + if self.local_mac: + flag = judge_is_mac_same( + self.local_mac, self.dldp_intf_conf['dldpLocalMac']) + if not flag: + self.updates_cmd.append( + "dldp compatible-mode local-mac %s" % self.local_mac) + + if self.reset and self.reset == 'enable': + self.updates_cmd.append('dldp reset') + else: + if self.changed: + self.updates_cmd.append("undo dldp compatible-mode local-mac") + + def get_end_state(self): + """Get end state info""" + + dldp_conf = dict() + self.dldp_intf_conf = self.get_dldp_intf_exist_config() + + dldp_conf['interface'] = self.interface + dldp_conf['enable'] = self.dldp_intf_conf.get('dldpEnable', None) + dldp_conf['mode_enable'] = self.dldp_intf_conf.get( + 'dldpCompatibleEnable', None) + dldp_conf['local_mac'] = self.dldp_intf_conf.get('dldpLocalMac', None) + dldp_conf['reset'] = 'disable' + if self.reset == 'enable': + dldp_conf['reset'] = 'enable' + + self.end_state = copy.deepcopy(dldp_conf) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Execute task""" + + self.dldp_intf_conf = self.get_dldp_intf_exist_config() + self.check_params() + self.same_conf = self.check_config_if_same() + self.get_existing() + self.get_proposed() + self.config_intf_dldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + enable=dict(choices=['enable', 'disable'], type='str'), + reset=dict(choices=['enable', 'disable'], type='str'), + mode_enable=dict(choices=['enable', 'disable'], type='str'), + local_mac=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + dldp_intf_obj = DldpInterface(argument_spec) + dldp_intf_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_eth_trunk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_eth_trunk.py new file mode 100644 index 00000000..c658f93f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_eth_trunk.py @@ -0,0 +1,672 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_eth_trunk +short_description: Manages Eth-Trunk interfaces on HUAWEI CloudEngine switches. +description: + - Manages Eth-Trunk specific configuration parameters on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - C(state=absent) removes the Eth-Trunk config and interface if it + already exists. If members to be removed are not explicitly + passed, all existing members (if any), are removed, + and Eth-Trunk removed. + - Members must be a list. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + trunk_id: + description: + - Eth-Trunk interface number. + The value is an integer. + The value range depends on the assign forward eth-trunk mode command. + When 256 is specified, the value ranges from 0 to 255. + When 512 is specified, the value ranges from 0 to 511. + When 1024 is specified, the value ranges from 0 to 1023. + required: true + mode: + description: + - Specifies the working mode of an Eth-Trunk interface. + choices: ['manual','lacp-dynamic','lacp-static'] + min_links: + description: + - Specifies the minimum number of Eth-Trunk member links in the Up state. + The value is an integer ranging from 1 to the maximum number of interfaces + that can be added to a Eth-Trunk interface. + hash_type: + description: + - Hash algorithm used for load balancing among Eth-Trunk member interfaces. + choices: ['src-dst-ip', 'src-dst-mac', 'enhanced', 'dst-ip', 'dst-mac', 'src-ip', 'src-mac'] + members: + description: + - List of interfaces that will be managed in a given Eth-Trunk. + The interface name must be full name. + force: + description: + - When true it forces Eth-Trunk members to match what is + declared in the members param. This can be used to remove + members. + type: bool + default: 'no' + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Eth_trunk module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure Eth-Trunk100 is created, add two members, and set to mode lacp-static + community.network.ce_eth_trunk: + trunk_id: 100 + members: ['10GE1/0/24','10GE1/0/25'] + mode: 'lacp-static' + state: present + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"trunk_id": "100", "members": ['10GE1/0/24','10GE1/0/25'], "mode": "lacp-static"} +existing: + description: k/v pairs of existing Eth-Trunk + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "manual"} +end_state: + description: k/v pairs of Eth-Trunk info after module execution + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/24", "memberIfState": "Down"}, + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "lacp-static"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Eth-Trunk 100", + "mode lacp-static", + "interface 10GE1/0/25", + "eth-trunk 100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_TRUNK = """ + + + + + Eth-Trunk%s + + + + + + + + + + + + + + + + + +""" + +CE_NC_XML_BUILD_TRUNK_CFG = """ + + + %s + + +""" + +CE_NC_XML_DELETE_TRUNK = """ + + Eth-Trunk%s + +""" + +CE_NC_XML_CREATE_TRUNK = """ + + Eth-Trunk%s + +""" + +CE_NC_XML_MERGE_MINUPNUM = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_HASHTYPE = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_WORKMODE = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_BUILD_MEMBER_CFG = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_MEMBER = """ + + %s + +""" + +CE_NC_XML_DELETE_MEMBER = """ + + %s + +""" + +MODE_XML2CLI = {"Manual": "manual", "Dynamic": "lacp-dynamic", "Static": "lacp-static"} +MODE_CLI2XML = {"manual": "Manual", "lacp-dynamic": "Dynamic", "lacp-static": "Static"} +HASH_XML2CLI = {"IP": "src-dst-ip", "MAC": "src-dst-mac", "Enhanced": "enhanced", + "Desip": "dst-ip", "Desmac": "dst-mac", "Sourceip": "src-ip", "Sourcemac": "src-mac"} +HASH_CLI2XML = {"src-dst-ip": "IP", "src-dst-mac": "MAC", "enhanced": "Enhanced", + "dst-ip": "Desip", "dst-mac": "Desmac", "src-ip": "Sourceip", "src-mac": "Sourcemac"} + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +def mode_xml_to_cli_str(mode): + """convert mode to cli format string""" + + if not mode: + return "" + + return MODE_XML2CLI.get(mode) + + +def hash_type_xml_to_cli_str(hash_type): + """convert trunk hash type netconf xml to cli format string""" + + if not hash_type: + return "" + + return HASH_XML2CLI.get(hash_type) + + +class EthTrunk(object): + """ + Manages Eth-Trunk interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.trunk_id = self.module.params['trunk_id'] + self.mode = self.module.params['mode'] + self.min_links = self.module.params['min_links'] + self.hash_type = self.module.params['hash_type'] + self.members = self.module.params['members'] + self.state = self.module.params['state'] + self.force = self.module.params['force'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # interface info + self.trunk_info = dict() + + def __init_module__(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def netconf_set_config(self, xml_str, xml_name): + """ netconf set config """ + + recv_xml = set_nc_config(self.module, xml_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_trunk_dict(self, trunk_id): + """ get one interface attributes dict.""" + + trunk_info = dict() + conf_str = CE_NC_GET_TRUNK % trunk_id + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return trunk_info + + # get trunk base info + base = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*', recv_xml) + + if base: + trunk_info = dict(ifName=base[0][0], + trunkId=base[0][0].lower().replace("eth-trunk", "").replace(" ", ""), + minUpNum=base[0][1], + maxUpNum=base[0][2], + trunkType=base[0][3], + hashType=base[0][4], + workMode=base[0][5], + upMemberIfNum=base[0][6], + memberIfNum=base[0][7]) + + # get trunk member interface info + member = re.findall( + r'.*(.*).*\s*' + r'(.*).*', recv_xml) + trunk_info["TrunkMemberIfs"] = list() + + for mem in member: + trunk_info["TrunkMemberIfs"].append( + dict(memberIfName=mem[0], memberIfState=mem[1])) + + return trunk_info + + def is_member_exist(self, ifname): + """is trunk member exist""" + + if not self.trunk_info["TrunkMemberIfs"]: + return False + + for mem in self.trunk_info["TrunkMemberIfs"]: + if ifname.replace(" ", "").upper() == mem["memberIfName"].replace(" ", "").upper(): + return True + + return False + + def get_mode_xml_str(self): + """trunk mode netconf xml format string""" + + return MODE_CLI2XML.get(self.mode) + + def get_hash_type_xml_str(self): + """trunk hash type netconf xml format string""" + + return HASH_CLI2XML.get(self.hash_type) + + def create_eth_trunk(self): + """Create Eth-Trunk interface""" + + xml_str = CE_NC_XML_CREATE_TRUNK % self.trunk_id + self.updates_cmd.append("interface Eth-Trunk %s" % self.trunk_id) + + if self.hash_type: + self.updates_cmd.append("load-balance %s" % self.hash_type) + xml_str += CE_NC_XML_MERGE_HASHTYPE % (self.trunk_id, self.get_hash_type_xml_str()) + + if self.mode: + self.updates_cmd.append("mode %s" % self.mode) + xml_str += CE_NC_XML_MERGE_WORKMODE % (self.trunk_id, self.get_mode_xml_str()) + + if self.min_links: + self.updates_cmd.append("least active-linknumber %s" % self.min_links) + xml_str += CE_NC_XML_MERGE_MINUPNUM % (self.trunk_id, self.min_links) + + if self.members: + mem_xml = "" + for mem in self.members: + mem_xml += CE_NC_XML_MERGE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("eth-trunk %s" % self.trunk_id) + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "CREATE_TRUNK") + self.changed = True + + def delete_eth_trunk(self): + """Delete Eth-Trunk interface and remove all member""" + + if not self.trunk_info: + return + + xml_str = "" + mem_str = "" + if self.trunk_info["TrunkMemberIfs"]: + for mem in self.trunk_info["TrunkMemberIfs"]: + mem_str += CE_NC_XML_DELETE_MEMBER % mem["memberIfName"] + self.updates_cmd.append("interface %s" % mem["memberIfName"]) + self.updates_cmd.append("undo eth-trunk") + if mem_str: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_str) + + xml_str += CE_NC_XML_DELETE_TRUNK % self.trunk_id + self.updates_cmd.append("undo interface Eth-Trunk %s" % self.trunk_id) + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "DELETE_TRUNK") + self.changed = True + + def remove_member(self): + """delete trunk member""" + + if not self.members: + return + + change = False + mem_xml = "" + xml_str = "" + for mem in self.members: + if self.is_member_exist(mem): + mem_xml += CE_NC_XML_DELETE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("undo eth-trunk") + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + change = True + + if not change: + return + + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "REMOVE_TRUNK_MEMBER") + self.changed = True + + def merge_eth_trunk(self): + """Create or merge Eth-Trunk""" + + change = False + xml_str = "" + self.updates_cmd.append("interface Eth-Trunk %s" % self.trunk_id) + if self.hash_type and self.get_hash_type_xml_str() != self.trunk_info["hashType"]: + self.updates_cmd.append("load-balance %s" % + self.hash_type) + xml_str += CE_NC_XML_MERGE_HASHTYPE % ( + self.trunk_id, self.get_hash_type_xml_str()) + change = True + if self.min_links and self.min_links != self.trunk_info["minUpNum"]: + self.updates_cmd.append( + "least active-linknumber %s" % self.min_links) + xml_str += CE_NC_XML_MERGE_MINUPNUM % ( + self.trunk_id, self.min_links) + change = True + if self.mode and self.get_mode_xml_str() != self.trunk_info["workMode"]: + self.updates_cmd.append("mode %s" % self.mode) + xml_str += CE_NC_XML_MERGE_WORKMODE % ( + self.trunk_id, self.get_mode_xml_str()) + change = True + + if not change: + self.updates_cmd.pop() # remove 'interface Eth-Trunk' command + + # deal force: + # When true it forces Eth-Trunk members to match + # what is declared in the members param. + if self.force and self.trunk_info["TrunkMemberIfs"]: + mem_xml = "" + for mem in self.trunk_info["TrunkMemberIfs"]: + if not self.members or mem["memberIfName"].replace(" ", "").upper() not in self.members: + mem_xml += CE_NC_XML_DELETE_MEMBER % mem["memberIfName"] + self.updates_cmd.append("interface %s" % mem["memberIfName"]) + self.updates_cmd.append("undo eth-trunk") + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + change = True + + if self.members: + mem_xml = "" + for mem in self.members: + if not self.is_member_exist(mem): + mem_xml += CE_NC_XML_MERGE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("eth-trunk %s" % self.trunk_id) + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % ( + self.trunk_id, mem_xml) + change = True + + if not change: + return + + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "MERGE_TRUNK") + self.changed = True + + def check_params(self): + """Check all input params""" + + # trunk_id check + if not self.trunk_id.isdigit(): + self.module.fail_json(msg='The parameter of trunk_id is invalid.') + + # min_links check + if self.min_links and not self.min_links.isdigit(): + self.module.fail_json(msg='The parameter of min_links is invalid.') + + # members check and convert members to upper + if self.members: + for mem in self.members: + if not get_interface_type(mem.replace(" ", "")): + self.module.fail_json( + msg='The parameter of members is invalid.') + + for mem_id in range(len(self.members)): + self.members[mem_id] = self.members[mem_id].replace(" ", "").upper() + + def get_proposed(self): + """get proposed info""" + + self.proposed["trunk_id"] = self.trunk_id + self.proposed["mode"] = self.mode + if self.min_links: + self.proposed["min_links"] = self.min_links + self.proposed["hash_type"] = self.hash_type + if self.members: + self.proposed["members"] = self.members + self.proposed["state"] = self.state + self.proposed["force"] = self.force + + def get_existing(self): + """get existing info""" + + if not self.trunk_info: + return + + self.existing["trunk_id"] = self.trunk_info["trunkId"] + self.existing["min_links"] = self.trunk_info["minUpNum"] + self.existing["hash_type"] = hash_type_xml_to_cli_str(self.trunk_info["hashType"]) + self.existing["mode"] = mode_xml_to_cli_str(self.trunk_info["workMode"]) + self.existing["members_detail"] = self.trunk_info["TrunkMemberIfs"] + + def get_end_state(self): + """get end state info""" + + trunk_info = self.get_trunk_dict(self.trunk_id) + if not trunk_info: + return + + self.end_state["trunk_id"] = trunk_info["trunkId"] + self.end_state["min_links"] = trunk_info["minUpNum"] + self.end_state["hash_type"] = hash_type_xml_to_cli_str(trunk_info["hashType"]) + self.end_state["mode"] = mode_xml_to_cli_str(trunk_info["workMode"]) + self.end_state["members_detail"] = trunk_info["TrunkMemberIfs"] + + def work(self): + """worker""" + + self.check_params() + self.trunk_info = self.get_trunk_dict(self.trunk_id) + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.trunk_info: + # create + self.create_eth_trunk() + else: + # merge trunk + self.merge_eth_trunk() + else: + if self.trunk_info: + if not self.members: + # remove all members and delete trunk + self.delete_eth_trunk() + else: + # remove some trunk members + self.remove_member() + else: + self.module.fail_json(msg='Error: Eth-Trunk does not exist.') + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + trunk_id=dict(required=True), + mode=dict(required=False, + choices=['manual', 'lacp-dynamic', 'lacp-static'], + type='str'), + min_links=dict(required=False, type='str'), + hash_type=dict(required=False, + choices=['src-dst-ip', 'src-dst-mac', 'enhanced', + 'dst-ip', 'dst-mac', 'src-ip', 'src-mac'], + type='str'), + members=dict(required=False, default=None, type='list'), + force=dict(required=False, default=False, type='bool'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = EthTrunk(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bd_vni.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bd_vni.py new file mode 100644 index 00000000..1fcc8ae6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bd_vni.py @@ -0,0 +1,1053 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bd_vni +short_description: Manages EVPN VXLAN Network Identifier (VNI) on HUAWEI CloudEngine switches. +description: + - Manages Ethernet Virtual Private Network (EVPN) VXLAN Network + Identifier (VNI) configurations on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that EVPN has been configured to serve as the VXLAN control plane when state is present. + - Ensure that a bridge domain (BD) has existed when state is present. + - Ensure that a VNI has been created and associated with a broadcast domain (BD) when state is present. + - If you configure evpn:false to delete an EVPN instance, all configurations in the EVPN instance are deleted. + - After an EVPN instance has been created in the BD view, you can configure an RD using route_distinguisher + parameter in BD-EVPN instance view. + - Before configuring VPN targets for a BD EVPN instance, ensure that an RD has been configured + for the BD EVPN instance + - If you unconfigure route_distinguisher, all VPN target attributes for the BD EVPN instance will be removed at the same time. + - When using state:absent, evpn is not supported and it will be ignored. + - When using state:absent to delete VPN target attributes, ensure the configuration of VPN target attributes has + existed and otherwise it will report an error. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specify an existed bridge domain (BD).The value is an integer ranging from 1 to 16777215. + required: true + evpn: + description: + - Create or delete an EVPN instance for a VXLAN in BD view. + choices: ['enable','disable'] + default: 'enable' + route_distinguisher: + description: + - Configures a route distinguisher (RD) for a BD EVPN instance. + The format of an RD can be as follows + - 1) 2-byte AS number:4-byte user-defined number, for example, 1:3. An AS number is an integer ranging from + 0 to 65535, and a user-defined number is an integer ranging from 0 to 4294967295. The AS and user-defined + numbers cannot be both 0s. This means that an RD cannot be 0:0. + - 2) Integral 4-byte AS number:2-byte user-defined number, for example, 65537:3. An AS number is an integer + ranging from 65536 to 4294967295, and a user-defined number is an integer ranging from 0 to 65535. + - 3) 4-byte AS number in dotted notation:2-byte user-defined number, for example, 0.0:3 or 0.1:0. A 4-byte + AS number in dotted notation is in the format of x.y, where x and y are integers ranging from 0 to 65535. + - 4) A user-defined number is an integer ranging from 0 to 65535. The AS and user-defined numbers cannot be + both 0s. This means that an RD cannot be 0.0:0. + - 5) 32-bit IP address:2-byte user-defined number. For example, 192.168.122.15:1. An IP address ranges from + 0.0.0.0 to 255.255.255.255, and a user-defined number is an integer ranging from 0 to 65535. + - 6) 'auto' specifies the RD that is automatically generated. + vpn_target_both: + description: + - Add VPN targets to both the import and export VPN target lists of a BD EVPN instance. + The format is the same as route_distinguisher. + vpn_target_import: + description: + - Add VPN targets to the import VPN target list of a BD EVPN instance. + The format is the same as route_distinguisher. + required: true + vpn_target_export: + description: + - Add VPN targets to the export VPN target list of a BD EVPN instance. + The format is the same as route_distinguisher. + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: 'present' +''' + +EXAMPLES = ''' +- name: EVPN BD VNI test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure an EVPN instance for a VXLAN in BD view" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + evpn: enable + provider: "{{ cli }}" + + - name: "Configure a route distinguisher (RD) for a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + route_distinguisher: '22:22' + provider: "{{ cli }}" + + - name: "Configure VPN targets to both the import and export VPN target lists of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_both: 22:100,22:101 + provider: "{{ cli }}" + + - name: "Configure VPN targets to the import VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_import: 22:22,22:23 + provider: "{{ cli }}" + + - name: "Configure VPN targets to the export VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_export: 22:38,22:39 + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to both the import and export VPN target lists of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_both: '22:100' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to the import VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_import: '22:22' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to the export VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_export: '22:38' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure a route distinguisher (RD) of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + route_distinguisher: '22:22' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure an EVPN instance for a VXLAN in BD view" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + evpn: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "enable", + "route_distinguisher": "22:22", + "state": "present", + "vpn_target_both": [ + "22:100", + "22:101" + ], + "vpn_target_export": [ + "22:38", + "22:39" + ], + "vpn_target_import": [ + "22:22", + "22:23" + ] + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "disable", + "route_distinguisher": null, + "vpn_target_both": [], + "vpn_target_export": [], + "vpn_target_import": [] + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "enable", + "route_distinguisher": "22:22", + "vpn_target_both": [ + "22:100", + "22:101" + ], + "vpn_target_export": [ + "22:38", + "22:39" + ], + "vpn_target_import": [ + "22:22", + "22:23" + ] + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "bridge-domain 2", + " evpn", + " route-distinguisher 22:22", + " vpn-target 22:38 export-extcommunity", + " vpn-target 22:39 export-extcommunity", + " vpn-target 22:100 export-extcommunity", + " vpn-target 22:101 export-extcommunity", + " vpn-target 22:22 import-extcommunity", + " vpn-target 22:23 import-extcommunity", + " vpn-target 22:100 import-extcommunity", + " vpn-target 22:101 import-extcommunity" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +import copy +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VNI_BD = """ + + + + + + + + + + +""" + +CE_NC_GET_EVPN_CONFIG = """ + + + + + %s + %s + + + + + + + + + + + + + + + + + +""" + +CE_NC_DELETE_EVPN_CONFIG = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_EVPN_CONFIG_HEAD = """ + + + + + %s + %s +""" + +CE_NC_MERGE_EVPN_CONFIG_HEAD = """ + + + + + %s + %s +""" + +CE_NC_MERGE_EVPN_AUTORTS_HEAD = """ + +""" + +CE_NC_MERGE_EVPN_AUTORTS_TAIL = """ + +""" + +CE_NC_DELETE_EVPN_AUTORTS_CONTEXT = """ + + %s + +""" + +CE_NC_MERGE_EVPN_AUTORTS_CONTEXT = """ + + %s + +""" + +CE_NC_MERGE_EVPN_RTS_HEAD = """ + +""" + +CE_NC_MERGE_EVPN_RTS_TAIL = """ + +""" + +CE_NC_DELETE_EVPN_RTS_CONTEXT = """ + + %s + %s + +""" + +CE_NC_MERGE_EVPN_RTS_CONTEXT = """ + + %s + %s + +""" + +CE_NC_MERGE_EVPN_CONFIG_TAIL = """ + + + + +""" + + +def is_valid_value(vrf_targe_value): + """check whether VPN target value is valid""" + + each_num = None + if len(vrf_targe_value) > 21 or len(vrf_targe_value) < 3: + return False + if vrf_targe_value.find(':') == -1: + return False + elif vrf_targe_value == '0:0': + return False + elif vrf_targe_value == '0.0:0': + return False + else: + value_list = vrf_targe_value.split(':') + if value_list[0].find('.') != -1: + if not value_list[1].isdigit(): + return False + if int(value_list[1]) > 65535: + return False + value = value_list[0].split('.') + if len(value) == 4: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + elif len(value) == 2: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + else: + return False + elif not value_list[0].isdigit(): + return False + elif not value_list[1].isdigit(): + return False + elif int(value_list[0]) < 65536 and int(value_list[1]) < 4294967296: + return True + elif int(value_list[0]) > 65535 and int(value_list[0]) < 4294967296: + return bool(int(value_list[1]) < 65536) + else: + return False + + +class EvpnBd(object): + """Manage evpn instance in BD view""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # EVPN instance info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.evpn = self.module.params['evpn'] + self.route_distinguisher = self.module.params['route_distinguisher'] + self.vpn_target_both = self.module.params['vpn_target_both'] or list() + self.vpn_target_import = self.module.params[ + 'vpn_target_import'] or list() + self.vpn_target_export = self.module.params[ + 'vpn_target_export'] or list() + self.state = self.module.params['state'] + self.__string_to_lowercase__() + + self.commands = list() + self.evpn_info = dict() + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def __check_response__(self, xml_str, xml_name): + """Check if response message is already succeed""" + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def __string_to_lowercase__(self): + """Convert string to lowercase""" + + if self.route_distinguisher: + self.route_distinguisher = self.route_distinguisher.lower() + + if self.vpn_target_export: + for index, ele in enumerate(self.vpn_target_export): + self.vpn_target_export[index] = ele.lower() + + if self.vpn_target_import: + for index, ele in enumerate(self.vpn_target_import): + self.vpn_target_import[index] = ele.lower() + + if self.vpn_target_both: + for index, ele in enumerate(self.vpn_target_both): + self.vpn_target_both[index] = ele.lower() + + def get_all_evpn_rts(self, evpn_rts): + """Get all EVPN RTS""" + + rts = evpn_rts.findall("evpnRT") + if not rts: + return + + for ele in rts: + vrf_rttype = ele.find('vrfRTType') + vrf_rtvalue = ele.find('vrfRTValue') + + if vrf_rttype.text == 'export_extcommunity': + self.evpn_info['vpn_target_export'].append(vrf_rtvalue.text) + elif vrf_rttype.text == 'import_extcommunity': + self.evpn_info['vpn_target_import'].append(vrf_rtvalue.text) + + def get_all_evpn_autorts(self, evpn_autorts): + """"Get all EVPN AUTORTS""" + + autorts = evpn_autorts.findall("evpnAutoRT") + if not autorts: + return + + for autort in autorts: + vrf_rttype = autort.find('vrfRTType') + + if vrf_rttype.text == 'export_extcommunity': + self.evpn_info['vpn_target_export'].append('auto') + elif vrf_rttype.text == 'import_extcommunity': + self.evpn_info['vpn_target_import'].append('auto') + + def process_rts_info(self): + """Process RTS information""" + + if not self.evpn_info['vpn_target_export'] or\ + not self.evpn_info['vpn_target_import']: + return + + vpn_target_export = copy.deepcopy(self.evpn_info['vpn_target_export']) + for ele in vpn_target_export: + if ele in self.evpn_info['vpn_target_import']: + self.evpn_info['vpn_target_both'].append(ele) + self.evpn_info['vpn_target_export'].remove(ele) + self.evpn_info['vpn_target_import'].remove(ele) + + def get_evpn_instance_info(self): + """Get current EVPN instance information""" + + if not self.bridge_domain_id: + self.module.fail_json(msg='Error: The value of bridge_domain_id cannot be empty.') + + self.evpn_info['route_distinguisher'] = None + self.evpn_info['vpn_target_import'] = list() + self.evpn_info['vpn_target_export'] = list() + self.evpn_info['vpn_target_both'] = list() + self.evpn_info['evpn_inst'] = 'enable' + + xml_str = CE_NC_GET_EVPN_CONFIG % ( + self.bridge_domain_id, self.bridge_domain_id) + xml_str = get_nc_config(self.module, xml_str) + if "" in xml_str: + self.evpn_info['evpn_inst'] = 'disable' + return + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + evpn_inst = root.find("evpn/evpnInstances/evpnInstance") + if evpn_inst: + for eles in evpn_inst: + if eles.tag in ["evpnAutoRD", "evpnRD", "evpnRTs", "evpnAutoRTs"]: + if eles.tag == 'evpnAutoRD' and eles.text == 'true': + self.evpn_info['route_distinguisher'] = 'auto' + elif eles.tag == 'evpnRD' and self.evpn_info['route_distinguisher'] != 'auto': + self.evpn_info['route_distinguisher'] = eles.text + elif eles.tag == 'evpnRTs': + self.get_all_evpn_rts(eles) + elif eles.tag == 'evpnAutoRTs': + self.get_all_evpn_autorts(eles) + self.process_rts_info() + + def get_existing(self): + """Get existing config""" + + self.existing = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn_info['evpn_inst'], + route_distinguisher=self.evpn_info[ + 'route_distinguisher'], + vpn_target_both=self.evpn_info['vpn_target_both'], + vpn_target_import=self.evpn_info[ + 'vpn_target_import'], + vpn_target_export=self.evpn_info['vpn_target_export']) + + def get_proposed(self): + """Get proposed config""" + + self.proposed = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn, + route_distinguisher=self.route_distinguisher, + vpn_target_both=self.vpn_target_both, + vpn_target_import=self.vpn_target_import, + vpn_target_export=self.vpn_target_export, + state=self.state) + + def get_end_state(self): + """Get end config""" + + self.get_evpn_instance_info() + self.end_state = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn_info['evpn_inst'], + route_distinguisher=self.evpn_info[ + 'route_distinguisher'], + vpn_target_both=self.evpn_info[ + 'vpn_target_both'], + vpn_target_import=self.evpn_info[ + 'vpn_target_import'], + vpn_target_export=self.evpn_info['vpn_target_export']) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_vpn_target_exist(self, vpn_target_type): + """Judge whether proposed vpn target has existed""" + + vpn_target = list() + if vpn_target_type == 'vpn_target_import': + vpn_target.extend(self.existing['vpn_target_both']) + vpn_target.extend(self.existing['vpn_target_import']) + return set(self.proposed['vpn_target_import']).issubset(vpn_target) + elif vpn_target_type == 'vpn_target_export': + vpn_target.extend(self.existing['vpn_target_both']) + vpn_target.extend(self.existing['vpn_target_export']) + return set(self.proposed['vpn_target_export']).issubset(vpn_target) + + return False + + def judge_if_config_exist(self): + """Judge whether configuration has existed""" + + if self.state == 'absent': + if self.route_distinguisher or self.vpn_target_import or self.vpn_target_export or self.vpn_target_both: + return False + else: + return True + + if self.evpn_info['evpn_inst'] != self.evpn: + return False + + if self.evpn == 'disable' and self.evpn_info['evpn_inst'] == 'disable': + return True + + if self.proposed['bridge_domain_id'] != self.existing['bridge_domain_id']: + return False + + if self.proposed['route_distinguisher']: + if self.proposed['route_distinguisher'] != self.existing['route_distinguisher']: + return False + + if self.proposed['vpn_target_both']: + if not self.existing['vpn_target_both']: + return False + if not set(self.proposed['vpn_target_both']).issubset(self.existing['vpn_target_both']): + return False + + if self.proposed['vpn_target_import']: + if not self.judge_if_vpn_target_exist('vpn_target_import'): + return False + + if self.proposed['vpn_target_export']: + if not self.judge_if_vpn_target_exist('vpn_target_export'): + return False + + return True + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def unconfig_evpn_instance(self): + """Unconfigure EVPN instance""" + + self.updates_cmd.append("bridge-domain %s" % self.bridge_domain_id) + xml_str = CE_NC_MERGE_EVPN_CONFIG_HEAD % ( + self.bridge_domain_id, self.bridge_domain_id) + self.updates_cmd.append(" evpn") + + # unconfigure RD + if self.route_distinguisher: + if self.route_distinguisher.lower() == 'auto': + xml_str += 'false' + self.updates_cmd.append(" undo route-distinguisher auto") + else: + xml_str += '' + self.updates_cmd.append( + " undo route-distinguisher %s" % self.route_distinguisher) + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "UNDO_EVPN_BD_RD") + self.changed = True + return + + # process VPN target list + vpn_target_export = copy.deepcopy(self.vpn_target_export) + vpn_target_import = copy.deepcopy(self.vpn_target_import) + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in vpn_target_export: + vpn_target_export.append(ele) + if ele not in vpn_target_import: + vpn_target_import.append(ele) + + # unconfig EVPN auto RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() == 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_AUTORTS_CONTEXT % ( + 'export_extcommunity') + self.updates_cmd.append( + " undo vpn-target auto export-extcommunity") + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() == 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_AUTORTS_CONTEXT % ( + 'import_extcommunity') + self.updates_cmd.append( + " undo vpn-target auto import-extcommunity") + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_TAIL + + # unconfig EVPN RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() != 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_RTS_CONTEXT % ( + 'export_extcommunity', ele) + self.updates_cmd.append( + " undo vpn-target %s export-extcommunity" % ele) + + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() != 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_RTS_CONTEXT % ( + 'import_extcommunity', ele) + self.updates_cmd.append( + " undo vpn-target %s import-extcommunity" % ele) + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_TAIL + + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_VPN_TARGET_CONFIG") + self.changed = True + + def config_evpn_instance(self): + """Configure EVPN instance""" + + self.updates_cmd.append("bridge-domain %s" % self.bridge_domain_id) + + if self.evpn == 'disable': + xml_str = CE_NC_DELETE_EVPN_CONFIG % ( + self.bridge_domain_id, self.bridge_domain_id) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_CONFIG") + self.updates_cmd.append(" undo evpn") + self.changed = True + return + + xml_str = CE_NC_MERGE_EVPN_CONFIG_HEAD % ( + self.bridge_domain_id, self.bridge_domain_id) + self.updates_cmd.append(" evpn") + + # configure RD + if self.route_distinguisher: + if not self.existing['route_distinguisher']: + if self.route_distinguisher.lower() == 'auto': + xml_str += 'true' + self.updates_cmd.append(" route-distinguisher auto") + else: + xml_str += '%s' % self.route_distinguisher + self.updates_cmd.append( + " route-distinguisher %s" % self.route_distinguisher) + + # process VPN target list + vpn_target_export = copy.deepcopy(self.vpn_target_export) + vpn_target_import = copy.deepcopy(self.vpn_target_import) + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in vpn_target_export: + vpn_target_export.append(ele) + if ele not in vpn_target_import: + vpn_target_import.append(ele) + + # config EVPN auto RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() == 'auto' and \ + (not self.is_vpn_target_exist('export_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_AUTORTS_CONTEXT % ( + 'export_extcommunity') + self.updates_cmd.append( + " vpn-target auto export-extcommunity") + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() == 'auto' and \ + (not self.is_vpn_target_exist('import_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_AUTORTS_CONTEXT % ( + 'import_extcommunity') + self.updates_cmd.append( + " vpn-target auto import-extcommunity") + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_TAIL + + # config EVPN RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() != 'auto' and \ + (not self.is_vpn_target_exist('export_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_RTS_CONTEXT % ( + 'export_extcommunity', ele) + self.updates_cmd.append( + " vpn-target %s export-extcommunity" % ele) + + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() != 'auto' and \ + (not self.is_vpn_target_exist('import_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_RTS_CONTEXT % ( + 'import_extcommunity', ele) + self.updates_cmd.append( + " vpn-target %s import-extcommunity" % ele) + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_TAIL + + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_CONFIG") + self.changed = True + + def is_vpn_target_exist(self, target_type, value): + """Judge whether VPN target has existed""" + + if target_type == 'export_extcommunity': + if (value not in self.existing['vpn_target_export']) and\ + (value not in self.existing['vpn_target_both']): + return False + return True + + if target_type == 'import_extcommunity': + if (value not in self.existing['vpn_target_import']) and\ + (value not in self.existing['vpn_target_both']): + return False + return True + + return False + + def config_evnp_bd(self): + """Configure EVPN in BD view""" + + if not self.conf_exist: + if self.state == 'present': + self.config_evpn_instance() + else: + self.unconfig_evpn_instance() + + def process_input_params(self): + """Process input parameters""" + + if self.state == 'absent': + self.evpn = None + else: + if self.evpn == 'disable': + return + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele in self.vpn_target_export: + self.vpn_target_export.remove(ele) + if ele in self.vpn_target_import: + self.vpn_target_import.remove(ele) + + if self.vpn_target_export and self.vpn_target_import: + vpn_target_export = copy.deepcopy(self.vpn_target_export) + for ele in vpn_target_export: + if ele in self.vpn_target_import: + self.vpn_target_both.append(ele) + self.vpn_target_import.remove(ele) + self.vpn_target_export.remove(ele) + + def check_vpn_target_para(self): + """Check whether VPN target value is valid""" + + if self.route_distinguisher: + if self.route_distinguisher.lower() != 'auto' and\ + not is_valid_value(self.route_distinguisher): + self.module.fail_json( + msg='Error: Route distinguisher has invalid value %s.' % self.route_distinguisher) + + if self.vpn_target_export: + for ele in self.vpn_target_export: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + if self.vpn_target_import: + for ele in self.vpn_target_import: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + def check_undo_params_if_exist(self): + """Check whether all undo parameters is existed""" + + if self.vpn_target_import: + for ele in self.vpn_target_import: + if ele not in self.evpn_info['vpn_target_import'] and ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target import attribute value %s does not exist.' % ele) + + if self.vpn_target_export: + for ele in self.vpn_target_export: + if ele not in self.evpn_info['vpn_target_export'] and ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target export attribute value %s does not exist.' % ele) + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target export and import attribute value %s does not exist.' % ele) + + def check_params(self): + """Check all input params""" + + # bridge_domain_id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of bridge domain id is invalid.') + if int(self.bridge_domain_id) > 16777215 or int(self.bridge_domain_id) < 1: + self.module.fail_json( + msg='Error: The bridge domain id must be an integer between 1 and 16777215.') + + if self.state == 'absent': + self.check_undo_params_if_exist() + + # check bd whether binding the vxlan vni + self.check_vni_bd() + self.check_vpn_target_para() + + if self.state == 'absent': + if self.route_distinguisher: + if not self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Route distinguisher has not been configured.') + else: + if self.route_distinguisher != self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Current route distinguisher value is %s.' % + self.evpn_info['route_distinguisher']) + + if self.state == 'present': + if self.route_distinguisher: + if self.evpn_info['route_distinguisher'] and\ + self.route_distinguisher != self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Route distinguisher has already been configured.') + + def check_vni_bd(self): + """Check whether vxlan vni is configured in BD view""" + + xml_str = CE_NC_GET_VNI_BD + xml_str = get_nc_config(self.module, xml_str) + if "" in xml_str or not re.findall(r'\S+\s+%s' % self.bridge_domain_id, xml_str): + self.module.fail_json( + msg='Error: The vxlan vni is not configured or the bridge domain id is invalid.') + + def work(self): + """Execute task""" + + self.get_evpn_instance_info() + self.process_input_params() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_evnp_bd() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + bridge_domain_id=dict(required=True, type='str'), + evpn=dict(required=False, type='str', + default='enable', choices=['enable', 'disable']), + route_distinguisher=dict(required=False, type='str'), + vpn_target_both=dict(required=False, type='list'), + vpn_target_import=dict(required=False, type='list'), + vpn_target_export=dict(required=False, type='list'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + evpn_bd = EvpnBd(argument_spec) + evpn_bd.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp.py new file mode 100644 index 00000000..5141a415 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp.py @@ -0,0 +1,727 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bgp +short_description: Manages BGP EVPN configuration on HUAWEI CloudEngine switches. +description: + - This module offers the ability to configure a BGP EVPN peer relationship on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + bgp_instance: + description: + - Name of a BGP instance. The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + required: True + as_number: + description: + - Specifies integral AS number. The value is an integer ranging from 1 to 4294967295. + peer_address: + description: + - Specifies the IPv4 address of a BGP EVPN peer. The value is in dotted decimal notation. + peer_group_name: + description: + - Specify the name of a peer group that BGP peers need to join. + The value is a string of 1 to 47 case-sensitive characters, spaces not supported. + peer_enable: + description: + - Enable or disable a BGP device to exchange routes with a specified peer or peer group in the address + family view. + choices: ['true','false'] + advertise_router_type: + description: + - Configures a device to advertise routes to its BGP EVPN peers. + choices: ['arp','irb'] + vpn_name: + description: + - Associates a specified VPN instance with the IPv4 address family. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + advertise_l2vpn_evpn: + description: + - Enable or disable a device to advertise IP routes imported to a VPN instance to its EVPN instance. + choices: ['enable','disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Evpn bgp module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Enable peer address. + community.network.ce_evpn_bgp: + bgp_instance: 100 + peer_address: 1.1.1.1 + as_number: 100 + peer_enable: true + provider: "{{ cli }}" + + - name: Enable peer group arp. + community.network.ce_evpn_bgp: + bgp_instance: 100 + peer_group_name: aaa + advertise_router_type: arp + provider: "{{ cli }}" + + - name: Enable advertise l2vpn evpn. + community.network.ce_evpn_bgp: + bgp_instance: 100 + vpn_name: aaa + advertise_l2vpn_evpn: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"advertise_router_type": "arp", "bgp_instance": "100", "peer_group_name": "aaa", "state": "present"} +existing: + description: k/v pairs of existing rollback + returned: always + type: dict + sample: {"bgp_instance": "100", "peer_group_advertise_type": []} + +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 1.1.1.1 enable", + "peer aaa advertise arp"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"advertise_l2vpn_evpn": "enable", "bgp_instance": "100", "vpn_name": "aaa"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def is_config_exist(cmp_cfg, test_cfg): + """check configuration is exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def is_valid_as_number(as_number): + """check as-number is valid""" + + if as_number.isdigit(): + if int(as_number) > 4294967295 or int(as_number) < 1: + return False + return True + else: + if as_number.find('.') != -1: + number_list = as_number.split('.') + if len(number_list) != 2: + return False + if number_list[1] == 0: + return False + for each_num in number_list: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + + return False + + +class EvpnBgp(object): + """ + Manages evpn bgp configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.netconf = None + self.init_module() + + # module input info + self.as_number = self.module.params['as_number'] + self.bgp_instance = self.module.params['bgp_instance'] + self.peer_address = self.module.params['peer_address'] + self.peer_group_name = self.module.params['peer_group_name'] + self.peer_enable = self.module.params['peer_enable'] + self.advertise_router_type = self.module.params[ + 'advertise_router_type'] + self.vpn_name = self.module.params['vpn_name'] + self.advertise_l2vpn_evpn = self.module.params['advertise_l2vpn_evpn'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" + self.config_list = list() + self.l2vpn_evpn_exist = False + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_evpn_overlay_config(self): + """get evpn-overlay enable configuration""" + + cmd = "display current-configuration | include ^evpn-overlay enable" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + return out + + def get_current_config(self): + """get current configuration""" + + cmd = "display current-configuration | section include bgp %s" % self.bgp_instance + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + return out + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def check_params(self): + """Check all input params""" + + # as_number check + if not self.bgp_instance: + self.module.fail_json( + msg='Error: The bgp_instance can not be none.') + if not self.peer_enable and not self.advertise_router_type and not self.advertise_l2vpn_evpn: + self.module.fail_json( + msg='Error: The peer_enable, advertise_router_type, advertise_l2vpn_evpn ' + 'can not be none at the same time.') + if self.as_number: + if not is_valid_as_number(self.as_number): + self.module.fail_json( + msg='Error: The parameter of as_number %s is invalid.' % self.as_number) + # bgp_instance check + if self.bgp_instance: + if not is_valid_as_number(self.bgp_instance): + self.module.fail_json( + msg='Error: The parameter of bgp_instance %s is invalid.' % self.bgp_instance) + + # peer_address check + if self.peer_address: + if not is_valid_address(self.peer_address): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.peer_address) + + # peer_group_name check + if self.peer_group_name: + if len(self.peer_group_name) > 47 \ + or len(self.peer_group_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: peer group name is not in the range from 1 to 47.') + + # vpn_name check + if self.vpn_name: + if len(self.vpn_name) > 31 \ + or len(self.vpn_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: peer group name is not in the range from 1 to 31.') + + def get_proposed(self): + """get proposed info""" + + if self.as_number: + self.proposed["as_number"] = self.as_number + if self.bgp_instance: + self.proposed["bgp_instance"] = self.bgp_instance + if self.peer_address: + self.proposed["peer_address"] = self.peer_address + if self.peer_group_name: + self.proposed["peer_group_name"] = self.peer_group_name + if self.peer_enable: + self.proposed["peer_enable"] = self.peer_enable + if self.advertise_router_type: + self.proposed["advertise_router_type"] = self.advertise_router_type + if self.vpn_name: + self.proposed["vpn_name"] = self.vpn_name + if self.advertise_l2vpn_evpn: + self.proposed["advertise_l2vpn_evpn"] = self.advertise_l2vpn_evpn + if not self.peer_enable or not self.advertise_l2vpn_evpn: + if self.state: + self.proposed["state"] = self.state + + def get_peers_enable(self): + """get evpn peer address enable list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?as-number\s?(\S*)", self.config_list[0]) + if not get: + return None + else: + peers = list() + for item in get: + cmd = "peer %s enable" % item[0] + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peers.append( + dict(peer_address=item[0], as_number=item[1], peer_enable='true')) + else: + peers.append(dict(peer_address=item[0], as_number=item[1], peer_enable='false')) + return peers + + def get_peers_advertise_type(self): + """get evpn peer address advertise type list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?as-number\s?(\S*)", self.config_list[0]) + if not get: + return None + else: + peers = list() + for item in get: + cmd = "peer %s advertise arp" % item[0] + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item[0] + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peers.append(dict(peer_address=item[0], as_number=item[1], advertise_type='arp')) + if exist2: + peers.append(dict(peer_address=item[0], as_number=item[1], advertise_type='irb')) + return peers + + def get_peers_group_enable(self): + """get evpn peer group name enable list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get1 = re.findall( + r"group (\S+) external", self.config_list[0]) + + get2 = re.findall( + r"group (\S+) internal", self.config_list[0]) + + if not get1 and not get2: + return None + else: + peer_groups = list() + for item in get1: + cmd = "peer %s enable" % item + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peer_groups.append( + dict(peer_group_name=item, peer_enable='true')) + else: + peer_groups.append( + dict(peer_group_name=item, peer_enable='false')) + + for item in get2: + cmd = "peer %s enable" % item + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peer_groups.append( + dict(peer_group_name=item, peer_enable='true')) + else: + peer_groups.append( + dict(peer_group_name=item, peer_enable='false')) + return peer_groups + + def get_peer_groups_advertise_type(self): + """get evpn peer group name advertise type list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get1 = re.findall( + r"group (\S+) external", self.config_list[0]) + + get2 = re.findall( + r"group (\S+) internal", self.config_list[0]) + if not get1 and not get2: + return None + else: + peer_groups = list() + for item in get1: + cmd = "peer %s advertise arp" % item + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peer_groups.append( + dict(peer_group_name=item, advertise_type='arp')) + if exist2: + peer_groups.append( + dict(peer_group_name=item, advertise_type='irb')) + + for item in get2: + cmd = "peer %s advertise arp" % item + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peer_groups.append( + dict(peer_group_name=item, advertise_type='arp')) + if exist2: + peer_groups.append( + dict(peer_group_name=item, advertise_type='irb')) + return peer_groups + + def get_existing(self): + """get existing info""" + + if not self.config: + return + if self.bgp_instance: + self.existing["bgp_instance"] = self.bgp_instance + + if self.peer_address and self.peer_enable: + if self.l2vpn_evpn_exist: + self.existing["peer_address_enable"] = self.get_peers_enable() + + if self.peer_group_name and self.peer_enable: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_group_enable"] = self.get_peers_group_enable() + + if self.peer_address and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_address_advertise_type"] = self.get_peers_advertise_type() + + if self.peer_group_name and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_group_advertise_type"] = self.get_peer_groups_advertise_type() + + if self.advertise_l2vpn_evpn and self.vpn_name: + cmd = " ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["vpn_name"] = self.vpn_name + l2vpn_cmd = "advertise l2vpn evpn" + l2vpn_exist = is_config_exist(self.config, l2vpn_cmd) + if l2vpn_exist: + self.existing["advertise_l2vpn_evpn"] = 'enable' + else: + self.existing["advertise_l2vpn_evpn"] = 'disable' + + def get_end_state(self): + """get end state info""" + + self.config = self.get_current_config() + if not self.config: + return + + self.config_list = self.config.split('l2vpn-family evpn') + if len(self.config_list) == 2: + self.l2vpn_evpn_exist = True + + if self.bgp_instance: + self.end_state["bgp_instance"] = self.bgp_instance + + if self.peer_address and self.peer_enable: + if self.l2vpn_evpn_exist: + self.end_state["peer_address_enable"] = self.get_peers_enable() + + if self.peer_group_name and self.peer_enable: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_group_enable"] = self.get_peers_group_enable() + + if self.peer_address and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_address_advertise_type"] = self.get_peers_advertise_type() + + if self.peer_group_name and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_group_advertise_type"] = self.get_peer_groups_advertise_type() + + if self.advertise_l2vpn_evpn and self.vpn_name: + cmd = " ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if exist: + self.end_state["vpn_name"] = self.vpn_name + l2vpn_cmd = "advertise l2vpn evpn" + l2vpn_exist = is_config_exist(self.config, l2vpn_cmd) + if l2vpn_exist: + self.end_state["advertise_l2vpn_evpn"] = 'enable' + else: + self.end_state["advertise_l2vpn_evpn"] = 'disable' + + def config_peer(self): + """configure evpn bgp peer command""" + + if self.as_number and self.peer_address: + cmd = "peer %s as-number %s" % (self.peer_address, self.as_number) + exist = is_config_exist(self.config, cmd) + if not exist: + self.module.fail_json( + msg='Error: The peer session %s does not exist or the peer already ' + 'exists in another as-number.' % self.peer_address) + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "l2vpn-family evpn" + self.cli_add_command(cmd) + exist_l2vpn = is_config_exist(self.config, cmd) + if self.peer_enable: + cmd = "peer %s enable" % self.peer_address + if exist_l2vpn: + exist = is_config_exist(self.config_list[1], cmd) + if self.peer_enable == "true" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.peer_enable == "false" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + else: + self.cli_add_command(cmd) + self.changed = True + + if self.advertise_router_type: + cmd = "peer %s advertise %s" % ( + self.peer_address, self.advertise_router_type) + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.state == "absent" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + elif self.peer_group_name: + cmd_1 = "group %s external" % self.peer_group_name + exist_1 = is_config_exist(self.config, cmd_1) + cmd_2 = "group %s internal" % self.peer_group_name + exist_2 = is_config_exist(self.config, cmd_2) + exist = False + if exist_1: + exist = True + if exist_2: + exist = True + if not exist: + self.module.fail_json( + msg='Error: The peer-group %s does not exist.' % self.peer_group_name) + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "l2vpn-family evpn" + self.cli_add_command(cmd) + exist_l2vpn = is_config_exist(self.config, cmd) + if self.peer_enable: + cmd = "peer %s enable" % self.peer_group_name + if exist_l2vpn: + exist = is_config_exist(self.config_list[1], cmd) + if self.peer_enable == "true" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.peer_enable == "false" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + else: + self.cli_add_command(cmd) + self.changed = True + + if self.advertise_router_type: + cmd = "peer %s advertise %s" % ( + self.peer_group_name, self.advertise_router_type) + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.state == "absent" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + + def config_advertise_l2vpn_evpn(self): + """configure advertise l2vpn evpn""" + + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if not exist: + self.module.fail_json( + msg='Error: The VPN instance name %s does not exist.' % self.vpn_name) + config_vpn_list = self.config.split(cmd) + cmd = "ipv4-family vpn-instance" + exist_vpn = is_config_exist(config_vpn_list[1], cmd) + cmd_l2vpn = "advertise l2vpn evpn" + if exist_vpn: + config_vpn = config_vpn_list[1].split('ipv4-family vpn-instance') + exist_l2vpn = is_config_exist(config_vpn[0], cmd_l2vpn) + else: + exist_l2vpn = is_config_exist(config_vpn_list[1], cmd_l2vpn) + cmd = "advertise l2vpn evpn" + if self.advertise_l2vpn_evpn == "enable" and not exist_l2vpn: + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + self.cli_add_command(cmd) + cmd = "advertise l2vpn evpn" + self.cli_add_command(cmd) + self.changed = True + elif self.advertise_l2vpn_evpn == "disable" and exist_l2vpn: + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + self.cli_add_command(cmd) + cmd = "advertise l2vpn evpn" + self.cli_add_command(cmd, undo=True) + self.changed = True + + def work(self): + """worker""" + + self.check_params() + evpn_config = self.get_evpn_overlay_config() + if not evpn_config: + self.module.fail_json( + msg="Error: evpn-overlay enable is not configured.") + self.config = self.get_current_config() + if not self.config: + self.module.fail_json( + msg="Error: Bgp instance %s does not exist." % self.bgp_instance) + + self.config_list = self.config.split('l2vpn-family evpn') + if len(self.config_list) == 2: + self.l2vpn_evpn_exist = True + self.get_existing() + self.get_proposed() + + if self.peer_enable or self.advertise_router_type: + self.config_peer() + + if self.advertise_l2vpn_evpn: + self.config_advertise_l2vpn_evpn() + if self.commands: + self.cli_load_config(self.commands) + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bgp_instance=dict(required=True, type='str'), + as_number=dict(required=False, type='str'), + peer_address=dict(required=False, type='str'), + peer_group_name=dict(required=False, type='str'), + peer_enable=dict(required=False, type='str', choices=[ + 'true', 'false']), + advertise_router_type=dict(required=False, type='str', choices=[ + 'arp', 'irb']), + + vpn_name=dict(required=False, type='str'), + advertise_l2vpn_evpn=dict(required=False, type='str', choices=[ + 'enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = EvpnBgp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp_rr.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp_rr.py new file mode 100644 index 00000000..a8b5a5b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_bgp_rr.py @@ -0,0 +1,527 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bgp_rr +short_description: Manages RR for the VXLAN Network on HUAWEI CloudEngine switches. +description: + - Configure an RR in BGP-EVPN address family view on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that BGP view is existed. + - The peer, peer_type, and reflect_client arguments must all exist or not exist. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + as_number: + description: + - Specifies the number of the AS, in integer format. + The value is an integer that ranges from 1 to 4294967295. + required: true + bgp_instance: + description: + - Specifies the name of a BGP instance. + The value of instance-name can be an integer 1 or a string of 1 to 31. + bgp_evpn_enable: + description: + - Enable or disable the BGP-EVPN address family. + choices: ['enable','disable'] + default: 'enable' + peer_type: + description: + - Specify the peer type. + choices: ['group_name','ipv4_address'] + peer: + description: + - Specifies the IPv4 address or the group name of a peer. + reflect_client: + description: + - Configure the local device as the route reflector and the peer or peer group as the client of the route reflector. + choices: ['enable','disable'] + policy_vpn_target: + description: + - Enable or disable the VPN-Target filtering. + choices: ['enable','disable'] +''' + +EXAMPLES = ''' +- name: BGP RR test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure BGP-EVPN address family view and ensure that BGP view has existed." + community.network.ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + provider: "{{ cli }}" + + - name: "Configure reflect client and ensure peer has existed." + community.network.ce_evpn_bgp_rr: + as_number: 20 + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + provider: "{{ cli }}" + + - name: "Configure the VPN-Target filtering." + community.network.ce_evpn_bgp_rr: + as_number: 20 + policy_vpn_target: enable + provider: "{{ cli }}" + + - name: "Configure an RR in BGP-EVPN address family view." + community.network.ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + policy_vpn_target: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "disable", + "bgp_instance": null, + "peer": null, + "peer_type": null, + "policy_vpn_target": "disable", + "reflect_client": "disable" + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "bgp 20", + " l2vpn-family evpn", + " peer 192.8.3.3 enable", + " peer 192.8.3.3 reflect-client", + " undo policy vpn-target" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +class EvpnBgpRr(object): + """Manage RR in BGP-EVPN address family view""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # RR configuration parameters + self.as_number = self.module.params['as_number'] + self.bgp_instance = self.module.params['bgp_instance'] + self.peer_type = self.module.params['peer_type'] + self.peer = self.module.params['peer'] + self.bgp_evpn_enable = self.module.params['bgp_evpn_enable'] + self.reflect_client = self.module.params['reflect_client'] + self.policy_vpn_target = self.module.params['policy_vpn_target'] + + self.commands = list() + self.config = None + self.bgp_evpn_config = "" + self.cur_config = dict() + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """Load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def is_bgp_view_exist(self): + """Judge whether BGP view has existed""" + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + + return is_config_exist(self.config, view_cmd) + + def is_l2vpn_family_evpn_exist(self): + """Judge whether BGP-EVPN address family view has existed""" + + view_cmd = "l2vpn-family evpn" + return is_config_exist(self.config, view_cmd) + + def is_reflect_client_exist(self): + """Judge whether reflect client is configured""" + + view_cmd = "peer %s reflect-client" % self.peer + return is_config_exist(self.bgp_evpn_config, view_cmd) + + def is_policy_vpn_target_exist(self): + """Judge whether the VPN-Target filtering is enabled""" + + view_cmd = "undo policy vpn-target" + if is_config_exist(self.bgp_evpn_config, view_cmd): + return False + else: + return True + + def get_config_in_bgp_view(self): + """Get configuration in BGP view""" + + cmd = "display current-configuration | section include" + if self.as_number: + if self.bgp_instance: + cmd += " bgp %s instance %s" % (self.as_number, + self.bgp_instance) + else: + cmd += " bgp %s" % self.as_number + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = out.strip() if out else "" + if cmd == config: + return '' + + return config + + def get_config_in_bgp_evpn_view(self): + """Get configuration in BGP_EVPN view""" + + self.bgp_evpn_config = "" + if not self.config: + return "" + + index = self.config.find("l2vpn-family evpn") + if index == -1: + return "" + + return self.config[index:] + + def get_current_config(self): + """Get current configuration""" + + if not self.as_number: + self.module.fail_json(msg='Error: The value of as-number cannot be empty.') + + self.cur_config['bgp_exist'] = False + self.cur_config['bgp_evpn_enable'] = 'disable' + self.cur_config['reflect_client'] = 'disable' + self.cur_config['policy_vpn_target'] = 'disable' + self.cur_config['peer_type'] = None + self.cur_config['peer'] = None + + self.config = self.get_config_in_bgp_view() + + if not self.is_bgp_view_exist(): + return + self.cur_config['bgp_exist'] = True + + if not self.is_l2vpn_family_evpn_exist(): + return + self.cur_config['bgp_evpn_enable'] = 'enable' + + self.bgp_evpn_config = self.get_config_in_bgp_evpn_view() + if self.is_reflect_client_exist(): + self.cur_config['reflect_client'] = 'enable' + self.cur_config['peer_type'] = self.peer_type + self.cur_config['peer'] = self.peer + + if self.is_policy_vpn_target_exist(): + self.cur_config['policy_vpn_target'] = 'enable' + + def get_existing(self): + """Get existing config""" + + self.existing = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config[ + 'policy_vpn_target']) + + def get_proposed(self): + """Get proposed config""" + + self.proposed = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.peer_type, + peer=self.peer, + bgp_evpn_enable=self.bgp_evpn_enable, + reflect_client=self.reflect_client, + policy_vpn_target=self.policy_vpn_target) + + def get_end_state(self): + """Get end config""" + + self.get_current_config() + self.end_state = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config['policy_vpn_target']) + if self.end_state == self.existing: + self.changed = False + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """Judge whether configuration has existed""" + + if self.bgp_evpn_enable and self.bgp_evpn_enable != self.cur_config['bgp_evpn_enable']: + return False + + if self.bgp_evpn_enable == 'disable' and self.cur_config['bgp_evpn_enable'] == 'disable': + return True + + if self.reflect_client and self.reflect_client == 'enable': + if self.peer_type and self.peer_type != self.cur_config['peer_type']: + return False + if self.peer and self.peer != self.cur_config['peer']: + return False + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + return False + + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + return False + + return True + + def cli_add_command(self, command, undo=False): + """Add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_rr(self): + """Configure RR""" + + if self.conf_exist: + return + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + self.cli_add_command(view_cmd) + + if self.bgp_evpn_enable == 'disable': + self.cli_add_command("undo l2vpn-family evpn") + else: + self.cli_add_command("l2vpn-family evpn") + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + if self.reflect_client == 'enable': + self.cli_add_command("peer %s enable" % self.peer) + self.cli_add_command( + "peer %s reflect-client" % self.peer) + else: + self.cli_add_command( + "undo peer %s reflect-client" % self.peer) + self.cli_add_command("undo peer %s enable" % self.peer) + if self.cur_config['bgp_evpn_enable'] == 'enable': + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + if self.policy_vpn_target == 'enable': + self.cli_add_command("policy vpn-target") + else: + self.cli_add_command("undo policy vpn-target") + else: + if self.policy_vpn_target and self.policy_vpn_target == 'disable': + self.cli_add_command("undo policy vpn-target") + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def check_is_ipv4_addr(self): + """Check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + + return bool(re.match(ipv4_regex, self.peer)) + + def check_params(self): + """Check all input params""" + + if self.cur_config['bgp_exist'] == 'false': + self.module.fail_json(msg="Error: BGP view does not exist.") + + if self.bgp_instance: + if len(self.bgp_instance) < 1 or len(self.bgp_instance) > 31: + self.module.fail_json( + msg="Error: The length of BGP instance-name must be between 1 or a string of 1 to and 31.") + + if self.as_number: + if len(self.as_number) > 11 or len(self.as_number) == 0: + self.module.fail_json( + msg='Error: The len of as_number %s is out of [1 - 11].' % self.as_number) + + tmp_dict1 = dict(peer_type=self.peer_type, + peer=self.peer, + reflect_client=self.reflect_client) + tmp_dict2 = dict((k, v) + for k, v in tmp_dict1.items() if v is not None) + if len(tmp_dict2) != 0 and len(tmp_dict2) != 3: + self.module.fail_json( + msg='Error: The peer, peer_type, and reflect_client arguments must all exist or not exist.') + + if self.peer_type: + if self.peer_type == 'ipv4_address' and not self.check_is_ipv4_addr(): + self.module.fail_json(msg='Error: Illegal IPv4 address.') + elif self.peer_type == 'group_name' and self.check_is_ipv4_addr(): + self.module.fail_json( + msg='Error: Ip address cannot be configured as group-name.') + + def work(self): + """Execute task""" + + self.get_current_config() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_rr() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + as_number=dict(required=True, type='str'), + bgp_instance=dict(required=False, type='str'), + bgp_evpn_enable=dict(required=False, type='str', + default='enable', choices=['enable', 'disable']), + peer_type=dict(required=False, type='str', choices=[ + 'group_name', 'ipv4_address']), + peer=dict(required=False, type='str'), + reflect_client=dict(required=False, type='str', + choices=['enable', 'disable']), + policy_vpn_target=dict(required=False, choices=['enable', 'disable']), + ) + argument_spec.update(ce_argument_spec) + evpn_bgp_rr = EvpnBgpRr(argument_spec) + evpn_bgp_rr.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_global.py new file mode 100644 index 00000000..c60adb13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_evpn_global.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_global +short_description: Manages global configuration of EVPN on HUAWEI CloudEngine switches. +description: + - Manages global configuration of EVPN on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Before configuring evpn_overlay_enable=disable, delete other EVPN configurations. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + evpn_overlay_enable: + description: + - Configure EVPN as the VXLAN control plane. + required: true + choices: ['enable','disable'] +''' + +EXAMPLES = ''' +- name: Evpn global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure EVPN as the VXLAN control plan + community.network.ce_evpn_global: + evpn_overlay_enable: enable + provider: "{{ cli }}" + + - name: Undo EVPN as the VXLAN control plan + community.network.ce_evpn_global: + evpn_overlay_enable: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "evpn_overlay_enable": "enable" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "evpn_overlay_enable": "disable" + } +end_state: + description: k/v pairs of end attributes on the interface + returned: always + type: dict + sample: { + "evpn_overlay_enable": "enable" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "evpn-overlay enable", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class EvpnGlobal(object): + """Manage global configuration of EVPN""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # EVPN global configuration parameters + self.overlay_enable = self.module.params['evpn_overlay_enable'] + + self.commands = list() + self.global_info = dict() + self.conf_exist = False + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def get_evpn_global_info(self): + """ get current EVPN global configuration""" + + self.global_info['evpnOverLay'] = 'disable' + cmd = "display current-configuration | include ^evpn-overlay enable" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + if out: + self.global_info['evpnOverLay'] = 'enable' + + def get_existing(self): + """get existing config""" + self.existing = dict( + evpn_overlay_enable=self.global_info['evpnOverLay']) + + def get_proposed(self): + """get proposed config""" + self.proposed = dict(evpn_overlay_enable=self.overlay_enable) + + def get_end_state(self): + """get end config""" + self.get_evpn_global_info() + self.end_state = dict( + evpn_overlay_enable=self.global_info['evpnOverLay']) + + def show_result(self): + """ show result""" + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """ judge whether configuration has existed""" + if self.overlay_enable == self.global_info['evpnOverLay']: + return True + + return False + + def config_evnp_global(self): + """ set global EVPN configuration""" + if not self.conf_exist: + if self.overlay_enable == 'enable': + self.cli_add_command('evpn-overlay enable') + else: + self.cli_add_command('evpn-overlay enable', True) + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def work(self): + """execute task""" + self.get_evpn_global_info() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_evnp_global() + + self.get_end_state() + self.show_result() + + +def main(): + """main function entry""" + + argument_spec = dict( + evpn_overlay_enable=dict( + required=True, type='str', choices=['enable', 'disable']), + ) + argument_spec.update(ce_argument_spec) + evpn_global = EvpnGlobal(argument_spec) + evpn_global.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_facts.py new file mode 100644 index 00000000..6d9c0bab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_facts.py @@ -0,0 +1,414 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_facts +author: "wangdezhuang (@QijunPan)" +short_description: Gets facts about HUAWEI CloudEngine switches. +description: + - Collects facts from CloudEngine devices running the CloudEngine + operating system. Fact collection is supported over Cli + transport. This module prepends all of the base network fact keys + with C(ansible_net_). The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine facts test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Gather_subset is all" + community.network.ce_facts: + gather_subset: all + provider: "{{ cli }}" + + - name: "Collect only the config facts" + community.network.ce_facts: + gather_subset: config + provider: "{{ cli }}" + + - name: "Do not collect hardware facts" + community.network.ce_facts: + gather_subset: "!hardware" + provider: "{{ cli }}" +""" + +RETURN = """ +gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +BIOS Version: + description: The BIOS version running on the remote device + returned: always + type: str +Board Type: + description: The board type of the remote device + returned: always + type: str +CPLD1 Version: + description: The CPLD1 Version running the remote device + returned: always + type: str +CPLD2 Version: + description: The CPLD2 Version running the remote device + returned: always + type: str +MAB Version: + description: The MAB Version running the remote device + returned: always + type: str +PCB Version: + description: The PCB Version running the remote device + returned: always + type: str +hostname: + description: The hostname of the remote device + returned: always + type: str + +# hardware +FAN: + description: The fan state on the device + returned: when hardware is configured + type: str +PWR: + description: The power state on the device + returned: when hardware is configured + type: str +filesystems: + description: The filesystems on the device + returned: when hardware is configured + type: str +flash_free: + description: The flash free space on the device + returned: when hardware is configured + type: str +flash_total: + description: The flash total space on the device + returned: when hardware is configured + type: str +memory_free: + description: The memory free space on the remote device + returned: when hardware is configured + type: str +memory_total: + description: The memory total space on the remote device + returned: when hardware is configured + type: str + +# config +config: + description: The current system configuration on the device + returned: when config is configured + type: str + +# interfaces +all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + +import re + +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import run_commands +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS)) + + +class Default(FactsBase): + """ Class default """ + + COMMANDS = [ + 'display version', + 'display current-configuration | include sysname' + ] + + def populate(self): + """ Populate method """ + + super(Default, self).populate() + + data = self.responses[0] + if data: + version = data.split("\n") + for item in version: + if re.findall(r"^\d+\S\s+", item.strip()): + tmp_item = item.split() + tmp_key = tmp_item[1] + " " + tmp_item[2] + if len(tmp_item) > 5: + self.facts[tmp_key] = " ".join(tmp_item[4:]) + else: + self.facts[tmp_key] = tmp_item[4] + + data = self.responses[1] + if data: + tmp_value = re.findall(r'sysname (.*)', data) + self.facts['hostname'] = tmp_value[0] + + +class Config(FactsBase): + """ Class config """ + + COMMANDS = [ + 'display current-configuration configuration system' + ] + + def populate(self): + """ Populate method """ + + super(Config, self).populate() + + data = self.responses[0] + if data: + self.facts['config'] = data.split("\n") + + +class Hardware(FactsBase): + """ Class hardware """ + + COMMANDS = [ + 'dir', + 'display memory', + 'display device' + ] + + def populate(self): + """ Populate method """ + + super(Hardware, self).populate() + + data = self.responses[0] + if data: + self.facts['filesystems'] = re.findall(r'Directory of (.*)/', data)[0] + self.facts['flash_total'] = re.findall(r'(.*) total', data)[0].replace(",", "") + self.facts['flash_free'] = re.findall(r'total \((.*) free\)', data)[0].replace(",", "") + + data = self.responses[1] + if data: + memory_total = re.findall(r'Total Memory Used: (.*) Kbytes', data)[0] + use_percent = re.findall(r'Memory Using Percentage: (.*)%', data)[0] + memory_free = str(int(memory_total) - int(memory_total) * int(use_percent) / 100) + self.facts['memory_total'] = memory_total + " Kb" + self.facts['memory_free'] = memory_free + " Kb" + + data = self.responses[2] + if data: + device_info = data.split("\n") + tmp_device_info = device_info[4:-1] + for item in tmp_device_info: + tmp_item = item.split() + if len(tmp_item) == 8: + self.facts[tmp_item[2]] = tmp_item[6] + elif len(tmp_item) == 7: + self.facts[tmp_item[0]] = tmp_item[5] + + +class Interfaces(FactsBase): + """ Class interfaces """ + + COMMANDS = [ + 'display interface brief', + 'display ip interface brief', + 'display lldp neighbor brief' + ] + + def populate(self): + """ Populate method""" + + interface_dict = dict() + ipv4_addr_dict = dict() + neighbors_dict = dict() + + super(Interfaces, self).populate() + + data = self.responses[0] + begin = False + if data: + interface_info = data.split("\n") + for item in interface_info: + if begin: + tmp_item = item.split() + interface_dict[tmp_item[0]] = tmp_item[1] + + if re.findall(r"^Interface", item.strip()): + begin = True + + self.facts['interfaces'] = interface_dict + + data = self.responses[1] + if data: + ipv4_addr = data.split("\n") + tmp_ipv4 = ipv4_addr[11:] + for item in tmp_ipv4: + tmp_item = item.split() + ipv4_addr_dict[tmp_item[0]] = tmp_item[1] + self.facts['all_ipv4_addresses'] = ipv4_addr_dict + + data = self.responses[2] + if data: + neighbors = data.split("\n") + tmp_neighbors = neighbors[2:] + for item in tmp_neighbors: + tmp_item = item.split() + if len(tmp_item) > 3: + neighbors_dict[tmp_item[0]] = tmp_item[3] + else: + neighbors_dict[tmp_item[0]] = None + self.facts['neighbors'] = neighbors_dict + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """ Module main """ + + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + # this is to maintain capability with nxos_facts 2.1 + if key.startswith('_'): + ansible_facts[key[1:]] = value + else: + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_file_copy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_file_copy.py new file mode 100644 index 00000000..8007960b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_file_copy.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_file_copy +short_description: Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches. +description: + - Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches. +author: + - Zhou Zhijin (@QijunPan) +notes: + - The feature must be enabled with feature scp-server. + - If the file is already present, no transfer will take place. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +requirements: + - paramiko +options: + local_file: + description: + - Path to local file. Local directory must exist. + The maximum length of I(local_file) is C(4096). + required: true + remote_file: + description: + - Remote file path of the copy. Remote directories must exist. + If omitted, the name of the local file will be used. + The maximum length of I(remote_file) is C(4096). + file_system: + description: + - The remote file system of the device. If omitted, + devices that support a I(file_system) parameter will use + their default values. + File system indicates the storage medium and can be set to as follows, + 1) C(flash) is root directory of the flash memory on the master MPU. + 2) C(slave#flash) is root directory of the flash memory on the slave MPU. + If no slave MPU exists, this drive is unavailable. + 3) C(chassis ID/slot number#flash) is root directory of the flash memory on + a device in a stack. For example, C(1/5#flash) indicates the flash memory + whose chassis ID is 1 and slot number is 5. + default: 'flash:' +''' + +EXAMPLES = ''' +- name: File copy test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Copy a local file to remote device" + community.network.ce_file_copy: + local_file: /usr/vrpcfg.cfg + remote_file: /vrpcfg.cfg + file_system: 'flash:' + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +transfer_result: + description: information about transfer result. + returned: always + type: str + sample: 'The local file has been successfully transferred to the device.' +local_file: + description: The path of the local file. + returned: always + type: str + sample: '/usr/work/vrpcfg.zip' +remote_file: + description: The path of the remote file. + returned: always + type: str + sample: '/vrpcfg.zip' +''' + +import re +import os +import sys +import time +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_v6_address + +try: + import paramiko + HAS_PARAMIKO = True +except ImportError: + HAS_PARAMIKO = False + +try: + from scp import SCPClient + HAS_SCP = True +except ImportError: + HAS_SCP = False + +CE_NC_GET_DISK_INFO = """ + + + + + + + + + + + + +""" + +CE_NC_GET_FILE_INFO = """ + + + + + %s + %s + + + + + +""" + +CE_NC_GET_SCP_ENABLE = """ + + + + +""" + + +def get_cli_exception(exc=None): + """Get cli exception message""" + + msg = list() + if not exc: + exc = sys.exc_info[1] + if exc: + errs = str(exc).split("\r\n") + for err in errs: + if not err: + continue + if "matched error in response:" in err: + continue + if " at '^' position" in err: + err = err.replace(" at '^' position", "") + if err.replace(" ", "") == "^": + continue + if len(err) > 2 and err[0] in ["<", "["] and err[-1] in [">", "]"]: + continue + if err[-1] == ".": + err = err[:-1] + if err.replace(" ", "") == "": + continue + msg.append(err) + else: + msg = ["Error: Fail to get cli exception message."] + + while msg[-1][-1] == ' ': + msg[-1] = msg[-1][:-1] + + if msg[-1][-1] != ".": + msg[-1] += "." + + return ", ".join(msg).capitalize() + + +class FileCopy(object): + """File copy function class""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # file copy parameters + self.local_file = self.module.params['local_file'] + self.remote_file = self.module.params['remote_file'] + self.file_system = self.module.params['file_system'] + self.host_is_ipv6 = validate_ip_v6_address(self.module.params['provider']['host']) + + # state + self.transfer_result = None + self.changed = False + + def init_module(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def remote_file_exists(self, dst, file_system='flash:'): + """Remote file whether exists""" + + full_path = file_system + dst + file_name = os.path.basename(full_path) + file_path = os.path.dirname(full_path) + file_path = file_path + '/' + xml_str = CE_NC_GET_FILE_INFO % (file_name, file_path) + ret_xml = get_nc_config(self.module, xml_str) + if "" in ret_xml: + return False, 0 + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get file info + root = ElementTree.fromstring(xml_str) + topo = root.find("vfm/dirs/dir") + if topo is None: + return False, 0 + + for eles in topo: + if eles.tag in ["DirSize"]: + return True, int(eles.text.replace(',', '')) + + return False, 0 + + def local_file_exists(self): + """Local file whether exists""" + + return os.path.isfile(self.local_file) + + def enough_space(self): + """Whether device has enough space""" + + xml_str = CE_NC_GET_DISK_INFO + ret_xml = get_nc_config(self.module, xml_str) + if "" in ret_xml: + return + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + topo = root.find("vfm/dfs/df/freeSize") + kbytes_free = topo.text + + file_size = os.path.getsize(self.local_file) + if int(kbytes_free) * 1024 > file_size: + return True + + return False + + def transfer_file(self, dest): + """Begin to transfer file by scp""" + + if not self.local_file_exists(): + self.module.fail_json( + msg='Could not transfer file. Local file doesn\'t exist.') + + if not self.enough_space(): + self.module.fail_json( + msg='Could not transfer file. Not enough space on device.') + + hostname = self.module.params['provider']['host'] + username = self.module.params['provider']['username'] + password = self.module.params['provider']['password'] + port = self.module.params['provider']['port'] + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=hostname, username=username, password=password, port=port) + full_remote_path = '{0}{1}'.format(self.file_system, dest) + scp = SCPClient(ssh.get_transport()) + try: + scp.put(self.local_file, full_remote_path) + except Exception: + time.sleep(10) + file_exists, temp_size = self.remote_file_exists( + dest, self.file_system) + file_size = os.path.getsize(self.local_file) + if file_exists and int(temp_size) == int(file_size): + pass + else: + scp.close() + self.module.fail_json(msg='Could not transfer file. There was an error ' + 'during transfer. Please make sure the format of ' + 'input parameters is right.') + scp.close() + return True + + def get_scp_enable(self): + """Get scp enable state""" + + ret_xml = '' + try: + ret_xml = get_nc_config(self.module, CE_NC_GET_SCP_ENABLE) + except ConnectionError: + self.module.fail_json(msg='Error: The NETCONF API of scp_enable is not supported.') + + if "" in ret_xml: + return False + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get file info + root = ElementTree.fromstring(xml_str) + topo1 = root.find("sshs/sshServer/scpEnable") + topo2 = root.find("sshs/sshServerEnable/scpIpv4Enable") + topo3 = root.find("sshs/sshServerEnable/scpIpv6Enable") + if topo1 is not None: + return str(topo1.text).strip().lower() == 'enable' + elif self.host_is_ipv6 and topo3 is not None: + return str(topo3.text).strip().lower() == 'enable' + elif topo2 is not None: + return str(topo2.text).strip().lower() == 'enable' + return False + + def work(self): + """Execute task """ + + if not HAS_SCP: + self.module.fail_json( + msg="'Error: No scp package, please install it.'") + + if not HAS_PARAMIKO: + self.module.fail_json( + msg="'Error: No paramiko package, please install it.'") + + if self.local_file and len(self.local_file) > 4096: + self.module.fail_json( + msg="'Error: The maximum length of local_file is 4096.'") + + if self.remote_file and len(self.remote_file) > 4096: + self.module.fail_json( + msg="'Error: The maximum length of remote_file is 4096.'") + + scp_enable = self.get_scp_enable() + if not scp_enable: + if self.host_is_ipv6: + self.module.fail_json( + msg="'Error: Please ensure ipv6 SCP server are enabled.'") + else: + self.module.fail_json( + msg="'Error: Please ensure ipv4 SCP server are enabled.'") + + if not os.path.isfile(self.local_file): + self.module.fail_json( + msg="Local file {0} not found".format(self.local_file)) + + dest = self.remote_file or ('/' + os.path.basename(self.local_file)) + remote_exists, file_size = self.remote_file_exists( + dest, file_system=self.file_system) + if remote_exists and (os.path.getsize(self.local_file) != file_size): + remote_exists = False + + if not remote_exists: + self.changed = True + file_exists = False + else: + file_exists = True + self.transfer_result = 'The local file already exists on the device.' + + if not file_exists: + self.transfer_file(dest) + self.transfer_result = 'The local file has been successfully transferred to the device.' + + if self.remote_file is None: + self.remote_file = '/' + os.path.basename(self.local_file) + + self.module.exit_json( + changed=self.changed, + transfer_result=self.transfer_result, + local_file=self.local_file, + remote_file=self.remote_file, + file_system=self.file_system) + + +def main(): + """Main function entry""" + + argument_spec = dict( + local_file=dict(required=True), + remote_file=dict(required=False), + file_system=dict(required=False, default='flash:') + ) + argument_spec.update(ce_argument_spec) + filecopy_obj = FileCopy(argument_spec) + filecopy_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_debug.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_debug.py new file mode 100644 index 00000000..bdd8ee1a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_debug.py @@ -0,0 +1,613 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_debug +short_description: Manages information center debug configuration on HUAWEI CloudEngine switches. +description: + - Manages information center debug configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + debug_time_stamp: + description: + - Timestamp type of debugging information. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 'shortdate_second', + 'shortdate_tenthsecond', 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond'] + module_name: + description: + - Module name of the rule. + The value is a string of 1 to 31 case-insensitive characters. The default value is default. + Please use lower-case letter, such as [aaa, acl, arp, bfd]. + channel_id: + description: + - Number of a channel. + The value is an integer ranging from 0 to 9. The default value is 0. + debug_enable: + description: + - Whether a device is enabled to output debugging information. + default: no_use + choices: ['no_use','true','false'] + debug_level: + description: + - Debug level permitted to output. + choices: ['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center debug test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config debug time stamp" + community.network.ce_info_center_debug: + state: present + debug_time_stamp: date_boot + provider: "{{ cli }}" + + - name: "Undo debug time stamp" + community.network.ce_info_center_debug: + state: absent + debug_time_stamp: date_boot + provider: "{{ cli }}" + + - name: "Config debug module log level" + community.network.ce_info_center_debug: + state: present + module_name: aaa + channel_id: 1 + debug_enable: true + debug_level: error + provider: "{{ cli }}" + + - name: "Undo debug module log level" + community.network.ce_info_center_debug: + state: absent + module_name: aaa + channel_id: 1 + debug_enable: true + debug_level: error + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"state": "present", "debug_time_stamp": "date_boot"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"debugTimeStamp": "DATE_MILLISECOND"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"debugTimeStamp": "DATE_BOOT"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center timestamp debugging boot"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get info center debug global +CE_GET_DEBUG_GLOBAL_HEADER = """ + + + +""" +CE_GET_DEBUG_GLOBAL_TAIL = """ + + + +""" +# merge info center debug global +CE_MERGE_DEBUG_GLOBAL_HEADER = """ + + + +""" +CE_MERGE_DEBUG_GLOBAL_TAIL = """ + + + +""" + +# get info center debug source +CE_GET_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_GET_DEBUG_SOURCE_TAIL = """ + + + + +""" +# merge info center debug source +CE_MERGE_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_MERGE_DEBUG_SOURCE_TAIL = """ + + + + +""" +# delete info center debug source +CE_DELETE_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_DELETE_DEBUG_SOURCE_TAIL = """ + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_DBG_STATE = {"0": "true", + "1": "true", + "2": "false", + "3": "false", + "4": "false", + "5": "false", + "6": "false", + "7": "false", + "8": "false", + "9": "false"} + +CHANNEL_DEFAULT_DBG_LEVEL = {"0": "debugging", + "1": "debugging", + "2": "debugging", + "3": "debugging", + "4": "debugging", + "5": "debugging", + "6": "debugging", + "7": "debugging", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterDebug(object): + """ Manages info center debug configuration """ + + def __init__(self, **kwargs): + """ Init function """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.debug_time_stamp = self.module.params['debug_time_stamp'] or None + self.module_name = self.module.params['module_name'] or None + self.channel_id = self.module.params['channel_id'] or None + self.debug_enable = self.module.params['debug_enable'] + self.debug_level = self.module.params['debug_level'] or None + + # cur config + self.cur_global_cfg = dict() + self.cur_source_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_global_args(self): + """ Check global args """ + + need_cfg = False + find_flag = False + self.cur_global_cfg["global_cfg"] = [] + + if self.debug_time_stamp: + + conf_str = CE_GET_DEBUG_GLOBAL_HEADER + conf_str += "" + conf_str += CE_GET_DEBUG_GLOBAL_TAIL + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + find_flag = False + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_cfg = root.findall("syslog/globalParam") + if global_cfg: + for tmp in global_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["debugTimeStamp"]: + tmp_dict[site.tag] = site.text + + self.cur_global_cfg["global_cfg"].append(tmp_dict) + + if self.cur_global_cfg["global_cfg"]: + for tmp in self.cur_global_cfg["global_cfg"]: + find_flag = True + + if tmp.get("debugTimeStamp").lower() != self.debug_time_stamp: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_global_cfg["need_cfg"] = need_cfg + + def check_source_args(self): + """ Check source args """ + + need_cfg = False + find_flag = False + self.cur_source_cfg["source_cfg"] = [] + + if self.module_name: + if len(self.module_name) < 1 or len(self.module_name) > 31: + self.module.fail_json( + msg='Error: The module_name is out of [1 - 31].') + + if not self.channel_id: + self.module.fail_json( + msg='Error: Please input channel_id at the same time.') + + if self.channel_id: + if self.channel_id.isdigit(): + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg='Error: The value of channel_id is out of [0 - 9].') + else: + self.module.fail_json( + msg='Error: The channel_id is not digit.') + + conf_str = CE_GET_DEBUG_SOURCE_HEADER + + if self.module_name != "default": + conf_str += "%s" % self.module_name.upper() + else: + conf_str += "default" + + if self.channel_id: + conf_str += "" + if self.debug_enable != 'no_use': + conf_str += "" + if self.debug_level: + conf_str += "" + + conf_str += CE_GET_DEBUG_SOURCE_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + find_flag = False + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + source_cfg = root.findall("syslog/icSources/icSource") + if source_cfg: + for tmp in source_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["moduleName", "icChannelId", "dbgEnFlg", "dbgEnLevel"]: + tmp_dict[site.tag] = site.text + + self.cur_source_cfg["source_cfg"].append(tmp_dict) + + if self.cur_source_cfg["source_cfg"]: + for tmp in self.cur_source_cfg["source_cfg"]: + find_flag = True + + if self.module_name and tmp.get("moduleName").lower() != self.module_name.lower(): + find_flag = False + if self.channel_id and tmp.get("icChannelId") != self.channel_id: + find_flag = False + if self.debug_enable != 'no_use' and tmp.get("dbgEnFlg") != self.debug_enable: + find_flag = False + if self.debug_level and tmp.get("dbgEnLevel") != self.debug_level: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_source_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed """ + + self.proposed["state"] = self.state + + if self.debug_time_stamp: + self.proposed["debug_time_stamp"] = self.debug_time_stamp + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.debug_enable != 'no_use': + self.proposed["debug_enable"] = self.debug_enable + if self.debug_level: + self.proposed["debug_level"] = self.debug_level + + def get_existing(self): + """ Get existing """ + + if self.cur_global_cfg["global_cfg"]: + self.existing["global_cfg"] = self.cur_global_cfg["global_cfg"] + if self.cur_source_cfg["source_cfg"]: + self.existing["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def get_end_state(self): + """ Get end state """ + + self.check_global_args() + if self.cur_global_cfg["global_cfg"]: + self.end_state["global_cfg"] = self.cur_global_cfg["global_cfg"] + + self.check_source_args() + if self.cur_source_cfg["source_cfg"]: + self.end_state["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def merge_debug_global(self): + """ Merge debug global """ + + conf_str = CE_MERGE_DEBUG_GLOBAL_HEADER + + if self.debug_time_stamp: + conf_str += "%s" % self.debug_time_stamp.upper() + + conf_str += CE_MERGE_DEBUG_GLOBAL_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge debug global failed.') + + if self.debug_time_stamp: + cmd = "info-center timestamp debugging " + TIME_STAMP_DICT.get(self.debug_time_stamp) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_debug_global(self): + """ Delete debug global """ + + conf_str = CE_MERGE_DEBUG_GLOBAL_HEADER + + if self.debug_time_stamp: + conf_str += "DATE_MILLISECOND" + + conf_str += CE_MERGE_DEBUG_GLOBAL_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: delete debug global failed.') + + if self.debug_time_stamp: + cmd = "undo info-center timestamp debugging" + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_debug_source(self): + """ Merge debug source """ + + conf_str = CE_MERGE_DEBUG_SOURCE_HEADER + + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.debug_enable != 'no_use': + conf_str += "%s" % self.debug_enable + if self.debug_level: + conf_str += "%s" % self.debug_level + + conf_str += CE_MERGE_DEBUG_SOURCE_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge debug source failed.') + + cmd = "info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.debug_enable != 'no_use': + if self.debug_enable == "true": + cmd += " debug state on" + else: + cmd += " debug state off" + if self.debug_level: + cmd += " level %s" % self.debug_level + + self.updates_cmd.append(cmd) + self.changed = True + + def delete_debug_source(self): + """ Delete debug source """ + + if self.debug_enable == 'no_use' and not self.debug_level: + conf_str = CE_DELETE_DEBUG_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + conf_str += CE_DELETE_DEBUG_SOURCE_TAIL + else: + conf_str = CE_MERGE_DEBUG_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.debug_enable != 'no_use': + conf_str += "%s" % CHANNEL_DEFAULT_DBG_STATE.get(self.channel_id) + if self.debug_level: + conf_str += "%s" % CHANNEL_DEFAULT_DBG_LEVEL.get(self.channel_id) + conf_str += CE_MERGE_DEBUG_SOURCE_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete debug source failed.') + + cmd = "undo info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.debug_enable != 'no_use': + cmd += " debug state" + if self.debug_level: + cmd += " level" + + self.updates_cmd.append(cmd) + self.changed = True + + def work(self): + """ work function """ + + self.check_global_args() + self.check_source_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_global_cfg["need_cfg"]: + self.merge_debug_global() + if self.cur_source_cfg["need_cfg"]: + self.merge_debug_source() + + else: + if self.cur_global_cfg["need_cfg"]: + self.delete_debug_global() + if self.cur_source_cfg["need_cfg"]: + self.delete_debug_source() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + debug_time_stamp=dict(choices=['date_boot', 'date_second', 'date_tenthsecond', + 'date_millisecond', 'shortdate_second', 'shortdate_tenthsecond', + 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond']), + module_name=dict(type='str'), + channel_id=dict(type='str'), + debug_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + debug_level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterDebug(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_global.py new file mode 100644 index 00000000..64fad157 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_global.py @@ -0,0 +1,1721 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_global +short_description: Manages outputting logs on HUAWEI CloudEngine switches. +description: + - This module offers the ability to be output to the log buffer, log file, console, terminal, or log host on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + info_center_enable: + description: + - Whether the info-center function is enabled. The value is of the Boolean type. + choices: ['true','false'] + packet_priority: + description: + - Set the priority of the syslog packet.The value is an integer ranging from 0 to 7. The default value is 0. + suppress_enable: + description: + - Whether a device is enabled to suppress duplicate statistics. The value is of the Boolean type. + choices: [ 'false', 'true' ] + logfile_max_num: + description: + - Maximum number of log files of the same type. The default value is 200. + - The value range for log files is[3, 500], for security files is [1, 3],and for operation files is [1, 7]. + logfile_max_size: + description: + - Maximum size (in MB) of a log file. The default value is 32. + - The value range for log files is [4, 8, 16, 32], for security files is [1, 4], + - and for operation files is [1, 4]. + default: 32 + choices: ['4', '8', '16', '32'] + channel_id: + description: + - Number for channel. The value is an integer ranging from 0 to 9. The default value is 0. + channel_cfg_name: + description: + - Channel name.The value is a string of 1 to 30 case-sensitive characters. The default value is console. + default: console + channel_out_direct: + description: + - Direction of information output. + choices: ['console','monitor','trapbuffer','logbuffer','snmp','logfile'] + filter_feature_name: + description: + - Feature name of the filtered log. The value is a string of 1 to 31 case-insensitive characters. + filter_log_name: + description: + - Name of the filtered log. The value is a string of 1 to 63 case-sensitive characters. + ip_type: + description: + - Log server address type, IPv4 or IPv6. + choices: ['ipv4','ipv6'] + server_ip: + description: + - Log server address, IPv4 or IPv6 type. The value is a string of 0 to 255 characters. + The value can be an valid IPv4 or IPv6 address. + server_domain: + description: + - Server name. The value is a string of 1 to 255 case-sensitive characters. + is_default_vpn: + description: + - Use the default VPN or not. + type: bool + default: 'no' + vrf_name: + description: + - VPN name on a log server. The value is a string of 1 to 31 case-sensitive characters. + The default value is _public_. + level: + description: + - Level of logs saved on a log server. + choices: ['emergencies','alert','critical','error','warning','notification','informational','debugging'] + server_port: + description: + - Number of a port sending logs.The value is an integer ranging from 1 to 65535. + For UDP, the default value is 514. For TCP, the default value is 601. For TSL, the default value is 6514. + facility: + description: + - Log record tool. + choices: ['local0','local1','local2','local3','local4','local5','local6','local7'] + channel_name: + description: + - Channel name. The value is a string of 1 to 30 case-sensitive characters. + timestamp: + description: + - Log server timestamp. The value is of the enumerated type and case-sensitive. + choices: ['UTC', 'localtime'] + transport_mode: + description: + - Transport mode. The value is of the enumerated type and case-sensitive. + choices: ['tcp','udp'] + ssl_policy_name: + description: + - SSL policy name. The value is a string of 1 to 23 case-sensitive characters. + source_ip: + description: + - Log source ip address, IPv4 or IPv6 type. The value is a string of 0 to 255. + The value can be an valid IPv4 or IPv6 address. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Info center global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config info-center enable + community.network.ce_info_center_global: + info_center_enable: true + state: present + provider: "{{ cli }}" + + - name: Config statistic-suppress enable + community.network.ce_info_center_global: + suppress_enable: true + state: present + provider: "{{ cli }}" + + - name: Config info-center syslog packet-priority 1 + community.network.ce_info_center_global: + packet_priority: 2 + state: present + provider: "{{ cli }}" + + - name: Config info-center channel 1 name aaa + community.network.ce_info_center_global: + channel_id: 1 + channel_cfg_name: aaa + state: present + provider: "{{ cli }}" + + - name: Config info-center logfile size 10 + community.network.ce_info_center_global: + logfile_max_num: 10 + state: present + provider: "{{ cli }}" + + - name: Config info-center console channel 1 + community.network.ce_info_center_global: + channel_out_direct: console + channel_id: 1 + state: present + provider: "{{ cli }}" + + - name: Config info-center filter-id bymodule-alias snmp snmp_ipunlock + community.network.ce_info_center_global: + filter_feature_name: SNMP + filter_log_name: SNMP_IPLOCK + state: present + provider: "{{ cli }}" + + + - name: Config info-center max-logfile-number 16 + community.network.ce_info_center_global: + logfile_max_size: 16 + state: present + provider: "{{ cli }}" + + - name: Config syslog loghost domain. + community.network.ce_info_center_global: + server_domain: aaa + vrf_name: aaa + channel_id: 1 + transport_mode: tcp + facility: local4 + server_port: 100 + level: alert + timestamp: UTC + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"channel_id": "1", "facility": "local4", "is_default_vpn": True, "level": "alert", "server_domain": "aaa", + "server_port": "100", "state": "present", "timestamp": "localtime", "transport_mode": "tcp"} +existing: + description: k/v pairs of existing rollback + returned: always + type: dict + sample: + "server_domain_info": [ + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "false", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": "gmc", + "timestamp": "UTC", + "transportMode": "tcp", + "vrfName": "aaa" + } + ] +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: + "server_domain_info": [ + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "true", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": null, + "timestamp": "localtime", + "transportMode": "tcp", + "vrfName": "_public_" + }, + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "false", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": "gmc", + "timestamp": "UTC", + "transportMode": "tcp", + "vrfName": "aaa" + } + ] +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center loghost domain aaa level alert port 100 facility local4 channel 1 localtime transport tcp"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config, check_ip_addr + + +CE_NC_GET_CENTER_GLOBAL_INFO_HEADER = """ + + + +""" +CE_NC_GET_CENTER_GLOBAL_INFO_TAIL = """ + + + +""" + +CE_NC_MERGE_CENTER_GLOBAL_INFO_HEADER = """ + + + +""" + +CE_NC_MERGE_CENTER_GLOBAL_INFO_TAIL = """ + + + +""" + +CE_NC_GET_LOG_FILE_INFO_HEADER = """ + + + + +""" +CE_NC_GET_LOG_FILE_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_LOG_FILE_INFO_HEADER = """ + + + + +""" + +CE_NC_MERGE_LOG_FILE_INFO_TAIL = """ + + + + +""" + + +CE_NC_GET_CHANNEL_INFO = """ + + + + + %s + + + + + +""" + +CE_NC_MERGE_CHANNEL_INFO_HEADER = """ + + + + +""" +CE_NC_MERGE_CHANNEL_INFO_TAIL = """ + + + + +""" + +CE_NC_GET_CHANNEL_DIRECT_INFO = """ + + + + + %s + + + + + +""" +CE_NC_MERGE_CHANNEL_DIRECT_HEADER = """ + + + + +""" + +CE_NC_MERGE_CHANNEL_DIRECT_TAIL = """ + + + + +""" + +CE_NC_GET_FILTER_INFO = """ + + + + + + + + + + +""" + +CE_NC_CREATE_CHANNEL_FILTER_HEADER = """ + + + + + +""" +CE_NC_CREATE_CHANNEL_FILTER_TAIL = """ + + + + +""" +CE_NC_DELETE_CHANNEL_FILTER_HEADER = """ + + + + + +""" +CE_NC_DELETE_CHANNEL_FILTER_TAIL = """ + + + + +""" + +CE_NC_GET_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_GET_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_MERGE_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_MERGE_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_DELETE_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_DELETE_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_GET_SERVER_DNS_INFO_HEADER = """ + + + + +""" + +CE_NC_GET_SERVER_DNS_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_SERVER_DNS_INFO_HEADER = """ + + + + + %s + %s + %s +""" +CE_NC_MERGE_SERVER_DNS_INFO_TAIL = """ + + + + +""" + +CE_NC_DELETE_SERVER_DNS_INFO_HEADER = """ + + + + + %s + %s + %s +""" +CE_NC_DELETE_SERVER_DNS_INFO_TAIL = """ + + + + +""" + + +def get_out_direct_default(out_direct): + """get default out direct""" + + outdict = {"console": "1", "monitor": "2", "trapbuffer": "3", + "logbuffer": "4", "snmp": "5", "logfile": "6"} + channel_id_default = outdict.get(out_direct) + return channel_id_default + + +def get_channel_name_default(channel_id): + """get default out direct""" + + channel_dict = {"0": "console", "1": "monitor", "2": "loghost", "3": "trapbuffer", "4": "logbuffer", + "5": "snmpagent", "6": "channel6", "7": "channel7", "8": "channel8", "9": "channel9"} + channel_name_default = channel_dict.get(channel_id) + return channel_name_default + + +class InfoCenterGlobal(object): + """ + Manages info center global configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.info_center_enable = self.module.params['info_center_enable'] or None + self.packet_priority = self.module.params['packet_priority'] or None + self.suppress_enable = self.module.params['suppress_enable'] or None + self.logfile_max_num = self.module.params['logfile_max_num'] or None + self.logfile_max_size = self.module.params['logfile_max_size'] or None + self.channel_id = self.module.params['channel_id'] or None + self.channel_cfg_name = self.module.params['channel_cfg_name'] or None + self.channel_out_direct = self.module.params['channel_out_direct'] or None + self.filter_feature_name = self.module.params['filter_feature_name'] or None + self.filter_log_name = self.module.params['filter_log_name'] or None + self.ip_type = self.module.params['ip_type'] or None + self.server_ip = self.module.params['server_ip'] or None + self.server_domain = self.module.params['server_domain'] or None + self.is_default_vpn = self.module.params['is_default_vpn'] or None + self.vrf_name = self.module.params['vrf_name'] or None + self.level = self.module.params['level'] or None + self.server_port = self.module.params['server_port'] or None + self.facility = self.module.params['facility'] or None + self.channel_name = self.module.params['channel_name'] or None + self.timestamp = self.module.params['timestamp'] or None + self.transport_mode = self.module.params['transport_mode'] or None + self.ssl_policy_name = self.module.params['ssl_policy_name'] or None + self.source_ip = self.module.params['source_ip'] or None + self.state = self.module.params['state'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # syslog info + self.cur_global_info = None + self.cur_logfile_info = None + self.channel_info = None + self.channel_direct_info = None + self.filter_info = None + self.server_ip_info = None + self.server_domain_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed.""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_channel_dict(self): + """ get channel attributes dict.""" + + channel_info = dict() + # get channel info + conf_str = CE_NC_GET_CHANNEL_INFO % self.channel_id + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return channel_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + channel_info["channelInfos"] = list() + channels = root.findall("syslog/icChannels/icChannel") + if channels: + for channel in channels: + channel_dict = dict() + for ele in channel: + if ele.tag in ["icChnlId", "icChnlCfgName"]: + channel_dict[ele.tag] = ele.text + channel_info["channelInfos"].append(channel_dict) + return channel_info + + def is_exist_channel_id_name(self, channel_id, channel_name): + """if channel id exist""" + + if not self.channel_info: + return False + + for id2name in self.channel_info["channelInfos"]: + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] == channel_name: + return True + return False + + def config_merge_syslog_channel(self, channel_id, channel_name): + """config channel id""" + + if not self.is_exist_channel_id_name(channel_id, channel_name): + conf_str = CE_NC_MERGE_CHANNEL_INFO_HEADER + if channel_id: + conf_str += "%s" % channel_id + if channel_name: + conf_str += "%s" % channel_name + + conf_str += CE_NC_MERGE_CHANNEL_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel id failed.') + + self.updates_cmd.append( + "info-center channel %s name %s" % (channel_id, channel_name)) + self.changed = True + + def delete_merge_syslog_channel(self, channel_id, channel_name): + """delete channel id""" + + change_flag = False + + if channel_name: + for id2name in self.channel_info["channelInfos"]: + channel_default_name = get_channel_name_default( + id2name["icChnlId"]) + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] == channel_name: + channel_name = channel_default_name + change_flag = True + + if not channel_name: + for id2name in self.channel_info["channelInfos"]: + channel_default_name = get_channel_name_default( + id2name["icChnlId"]) + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] != channel_default_name: + channel_name = channel_default_name + change_flag = True + if change_flag: + conf_str = CE_NC_MERGE_CHANNEL_INFO_HEADER + if channel_id: + conf_str += "%s" % channel_id + if channel_name: + conf_str += "%s" % channel_name + + conf_str += CE_NC_MERGE_CHANNEL_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel id failed.') + + self.updates_cmd.append("undo info-center channel %s" % channel_id) + self.changed = True + + def get_channel_direct_dict(self): + """ get channel direct attributes dict.""" + + channel_direct_info = dict() + # get channel direct info + conf_str = CE_NC_GET_CHANNEL_DIRECT_INFO % self.channel_out_direct + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return channel_direct_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + channel_direct_info["channelDirectInfos"] = list() + dir_channels = root.findall("syslog/icDirChannels/icDirChannel") + if dir_channels: + for ic_dir_channel in dir_channels: + channel_direct_dict = dict() + for ele in ic_dir_channel: + if ele.tag in ["icOutDirect", "icCfgChnlId"]: + channel_direct_dict[ele.tag] = ele.text + channel_direct_info["channelDirectInfos"].append( + channel_direct_dict) + return channel_direct_info + + def is_exist_out_direct(self, out_direct, channel_id): + """if channel out direct exist""" + + if not self.channel_direct_info: + return False + + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] == channel_id: + return True + return False + + def config_merge_out_direct(self, out_direct, channel_id): + """config out direct""" + + if not self.is_exist_out_direct(out_direct, channel_id): + conf_str = CE_NC_MERGE_CHANNEL_DIRECT_HEADER + if out_direct: + conf_str += "%s" % out_direct + if channel_id: + conf_str += "%s" % channel_id + + conf_str += CE_NC_MERGE_CHANNEL_DIRECT_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + + self.updates_cmd.append( + "info-center %s channel %s" % (out_direct, channel_id)) + self.changed = True + + def delete_merge_out_direct(self, out_direct, channel_id): + """delete out direct""" + + change_flag = False + channel_id_default = get_out_direct_default(out_direct) + if channel_id: + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] == channel_id: + if channel_id != channel_id_default: + channel_id = channel_id_default + change_flag = True + + if not channel_id: + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] != channel_id_default: + channel_id = channel_id_default + change_flag = True + + if change_flag: + conf_str = CE_NC_MERGE_CHANNEL_DIRECT_HEADER + if out_direct: + conf_str += "%s" % out_direct + if channel_id: + conf_str += "%s" % channel_id + + conf_str += CE_NC_MERGE_CHANNEL_DIRECT_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + + self.updates_cmd.append("undo info-center logfile channel") + self.changed = True + + def get_filter_dict(self): + """ get syslog filter attributes dict.""" + + filter_info = dict() + # get filter info + conf_str = CE_NC_GET_FILTER_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return filter_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + filter_info["filterInfos"] = list() + ic_filters = root.findall("syslog/icFilters/icFilter") + if ic_filters: + for ic_filter in ic_filters: + filter_dict = dict() + for ele in ic_filter: + if ele.tag in ["icFeatureName", "icFilterLogName"]: + filter_dict[ele.tag] = ele.text + filter_info["filterInfos"].append(filter_dict) + return filter_info + + def is_exist_filter(self, filter_feature_name, filter_log_name): + """if filter info exist""" + + if not self.filter_info: + return False + for id2name in self.filter_info["filterInfos"]: + if id2name["icFeatureName"] == filter_feature_name and id2name["icFilterLogName"] == filter_log_name: + return True + return False + + def config_merge_filter(self, filter_feature_name, filter_log_name): + """config filter""" + + if not self.is_exist_filter(filter_feature_name, filter_log_name): + conf_str = CE_NC_CREATE_CHANNEL_FILTER_HEADER + conf_str += "true" + if filter_feature_name: + conf_str += "%s" % filter_feature_name + if filter_log_name: + conf_str += "%s" % filter_log_name + + conf_str += CE_NC_CREATE_CHANNEL_FILTER_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge syslog filter failed.') + + self.updates_cmd.append("info-center filter-id bymodule-alias %s %s" + % (filter_feature_name, filter_log_name)) + self.changed = True + + def delete_merge_filter(self, filter_feature_name, filter_log_name): + """delete filter""" + + change_flag = False + if self.is_exist_filter(filter_feature_name, filter_log_name): + for id2name in self.filter_info["filterInfos"]: + if id2name["icFeatureName"] == filter_feature_name and id2name["icFilterLogName"] == filter_log_name: + change_flag = True + if change_flag: + conf_str = CE_NC_DELETE_CHANNEL_FILTER_HEADER + conf_str += "true" + if filter_feature_name: + conf_str += "%s" % filter_feature_name + if filter_log_name: + conf_str += "%s" % filter_log_name + + conf_str += CE_NC_DELETE_CHANNEL_FILTER_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + self.updates_cmd.append("undo info-center filter-id bymodule-alias %s %s" + % (filter_feature_name, filter_log_name)) + self.changed = True + + def get_server_ip_dict(self): + """ get server ip attributes dict.""" + + server_ip_info = dict() + # get server ip info + is_default_vpn = "false" + if not self.is_default_vpn: + self.is_default_vpn = False + if self.is_default_vpn is True: + is_default_vpn = "true" + if not self.vrf_name: + self.vrf_name = "_public_" + conf_str = CE_NC_GET_SERVER_IP_INFO_HEADER % ( + self.ip_type, self.server_ip, self.vrf_name, is_default_vpn) + conf_str += CE_NC_GET_SERVER_IP_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return server_ip_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + server_ip_info["serverIpInfos"] = list() + syslog_servers = root.findall("syslog/syslogServers/syslogServer") + if syslog_servers: + for syslog_server in syslog_servers: + server_dict = dict() + for ele in syslog_server: + if ele.tag in ["ipType", "serverIp", "vrfName", "level", "serverPort", "facility", "chnlId", + "chnlName", "timestamp", "transportMode", "sslPolicyName", "isDefaultVpn", + "sourceIP", "isBriefFmt"]: + server_dict[ele.tag] = ele.text + server_ip_info["serverIpInfos"].append(server_dict) + return server_ip_info + + def config_merge_loghost(self): + """config loghost ip or dns""" + + conf_str = "" + is_default_vpn = "false" + if self.is_default_vpn is True: + is_default_vpn = "true" + if self.ip_type: + conf_str = CE_NC_MERGE_SERVER_IP_INFO_HEADER % (self.ip_type, self.server_ip, self.vrf_name, + is_default_vpn) + elif self.server_domain: + conf_str = CE_NC_MERGE_SERVER_DNS_INFO_HEADER % ( + self.server_domain, self.vrf_name, is_default_vpn) + if self.level: + conf_str += "%s" % self.level + if self.server_port: + conf_str += "%s" % self.server_port + if self.facility: + conf_str += "%s" % self.facility + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.channel_name: + conf_str += "%s" % self.channel_name + if self.timestamp: + conf_str += "%s" % self.timestamp + if self.transport_mode: + conf_str += "%s" % self.transport_mode + if self.ssl_policy_name: + conf_str += "%s" % self.ssl_policy_name + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.ip_type: + conf_str += CE_NC_MERGE_SERVER_IP_INFO_TAIL + elif self.server_domain: + conf_str += CE_NC_MERGE_SERVER_DNS_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge server loghost failed.') + + cmd = "info-center loghost" + if self.ip_type == "ipv4" and self.server_ip: + cmd += " %s" % self.server_ip + if self.ip_type == "ipv6" and self.server_ip: + cmd += " ipv6 %s" % self.server_ip + if self.server_domain: + cmd += " domain %s" % self.server_domain + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.channel_name: + cmd += " channel %s" % self.channel_name + if self.vrf_name: + if self.vrf_name != "_public_": + cmd += " vpn-instance %s" % self.vrf_name + if self.source_ip: + cmd += " source-ip %s" % self.source_ip + if self.facility: + cmd += " facility %s" % self.facility + if self.server_port: + cmd += " port %s" % self.server_port + if self.level: + cmd += " level %s" % self.level + if self.timestamp: + if self.timestamp == "localtime": + cmd += " local-time" + else: + cmd += " utc" + if self.transport_mode: + cmd += " transport %s" % self.transport_mode + if self.ssl_policy_name: + cmd += " ssl-policy %s" % self.ssl_policy_name + self.updates_cmd.append(cmd) + self.changed = True + + def delete_merge_loghost(self): + """delete loghost ip or dns""" + + conf_str = "" + is_default_vpn = "false" + if self.is_default_vpn is True: + is_default_vpn = "true" + if self.ip_type: + conf_str = CE_NC_DELETE_SERVER_IP_INFO_HEADER % (self.ip_type, self.server_ip, self.vrf_name, + is_default_vpn) + elif self.server_domain: + conf_str = CE_NC_DELETE_SERVER_DNS_INFO_HEADER % ( + self.server_domain, self.vrf_name, is_default_vpn) + if self.level: + conf_str += "%s" % self.level + if self.server_port: + conf_str += "%s" % self.server_port + if self.facility: + conf_str += "%s" % self.facility + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.channel_name: + conf_str += "%s" % self.channel_name + if self.timestamp: + conf_str += "%s" % self.timestamp + if self.transport_mode: + conf_str += "%s" % self.transport_mode + if self.ssl_policy_name: + conf_str += "%s" % self.ssl_policy_name + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.ip_type: + conf_str += CE_NC_DELETE_SERVER_IP_INFO_TAIL + elif self.server_domain: + conf_str += CE_NC_DELETE_SERVER_DNS_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge server loghost failed.') + + cmd = "undo info-center loghost" + if self.ip_type == "ipv4" and self.server_ip: + cmd += " %s" % self.server_ip + if self.ip_type == "ipv6" and self.server_ip: + cmd += " ipv6 %s" % self.server_ip + if self.server_domain: + cmd += " domain %s" % self.server_domain + if self.vrf_name: + if self.vrf_name != "_public_": + cmd += " vpn-instance %s" % self.vrf_name + self.updates_cmd.append(cmd) + self.changed = True + + def get_server_domain_dict(self): + """ get server domain attributes dict""" + + server_domain_info = dict() + # get server domain info + if not self.is_default_vpn: + self.is_default_vpn = False + if not self.vrf_name: + self.vrf_name = "_public_" + conf_str = CE_NC_GET_SERVER_DNS_INFO_HEADER + conf_str += CE_NC_GET_SERVER_DNS_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return server_domain_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + server_domain_info["serverAddressInfos"] = list() + syslog_dnss = root.findall("syslog/syslogDNSs/syslogDNS") + if syslog_dnss: + for syslog_dns in syslog_dnss: + dns_dict = dict() + for ele in syslog_dns: + if ele.tag in ["serverDomain", "vrfName", "level", "serverPort", "facility", "chnlId", + "chnlName", "timestamp", "transportMode", "sslPolicyName", "isDefaultVpn", + "sourceIP", "isBriefFmt"]: + dns_dict[ele.tag] = ele.text + server_domain_info["serverAddressInfos"].append(dns_dict) + + return server_domain_info + + def check_need_loghost_cfg(self): + """ check need cfg""" + + need_cfg = False + find_flag = False + if self.ip_type and self.server_ip: + if self.server_ip_info: + for tmp in self.server_ip_info["serverIpInfos"]: + find_flag = True + if self.ip_type and tmp.get("ipType") != self.ip_type: + find_flag = False + if self.server_ip and tmp.get("serverIp") != self.server_ip: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.level and tmp.get("level") != self.level: + find_flag = False + if self.server_port and tmp.get("serverPort") != self.server_port: + find_flag = False + if self.facility and tmp.get("facility") != self.facility: + find_flag = False + if self.channel_id and tmp.get("chnlId") != self.channel_id: + find_flag = False + if self.channel_name and tmp.get("chnlName") != self.channel_name: + find_flag = False + if self.timestamp and tmp.get("timestamp") != self.timestamp: + find_flag = False + if self.transport_mode and tmp.get("transportMode") != self.transport_mode: + find_flag = False + if self.ssl_policy_name and tmp.get("sslPolicyName") != self.ssl_policy_name: + find_flag = False + if self.source_ip and tmp.get("sourceIP") != self.source_ip: + find_flag = False + if find_flag: + break + elif self.server_domain: + if self.server_domain_info: + for tmp in self.server_domain_info["serverAddressInfos"]: + find_flag = True + if self.server_domain and tmp.get("serverDomain") != self.server_domain: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.level and tmp.get("level") != self.level: + find_flag = False + if self.server_port and tmp.get("serverPort") != self.server_port: + find_flag = False + if self.facility and tmp.get("facility") != self.facility: + find_flag = False + if self.channel_id and tmp.get("chnlId") != self.channel_id: + find_flag = False + if self.channel_name and tmp.get("chnlName") != self.channel_name: + find_flag = False + if self.timestamp and tmp.get("timestamp") != self.timestamp: + find_flag = False + if self.transport_mode and tmp.get("transportMode") != self.transport_mode: + find_flag = False + if self.ssl_policy_name and tmp.get("sslPolicyName") != self.ssl_policy_name: + find_flag = False + if self.source_ip and tmp.get("sourceIP") != self.source_ip: + find_flag = False + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + return need_cfg + + def get_syslog_global(self): + """get syslog global attributes""" + + cur_global_info = dict() + conf_str = CE_NC_GET_CENTER_GLOBAL_INFO_HEADER + if self.info_center_enable: + conf_str += "" + if self.packet_priority: + conf_str += "" + if self.suppress_enable: + conf_str += "" + conf_str += CE_NC_GET_CENTER_GLOBAL_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return cur_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "syslog/globalParam") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["icEnable", "packetPriority", "suppressEnable"]: + cur_global_info[site.tag] = site.text + return cur_global_info + + def merge_syslog_global(self): + """config global""" + + conf_str = CE_NC_MERGE_CENTER_GLOBAL_INFO_HEADER + if self.info_center_enable: + conf_str += "%s" % self.info_center_enable + if self.packet_priority: + if self.state == "present": + packet_priority = self.packet_priority + else: + packet_priority = 0 + conf_str += "%s" % packet_priority + if self.suppress_enable: + conf_str += "%s" % self.suppress_enable + + conf_str += CE_NC_MERGE_CENTER_GLOBAL_INFO_TAIL + + if self.info_center_enable == "true" and self.cur_global_info["icEnable"] != self.info_center_enable: + cmd = "info-center enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.suppress_enable == "true" and self.cur_global_info["suppressEnable"] != self.suppress_enable: + cmd = "info-center statistic-suppress enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.info_center_enable == "false" and self.cur_global_info["icEnable"] != self.info_center_enable: + cmd = "undo info-center enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.suppress_enable == "false" and self.cur_global_info["suppressEnable"] != self.suppress_enable: + cmd = "undo info-center statistic-suppress enable" + self.updates_cmd.append(cmd) + self.changed = True + + if self.state == "present": + if self.packet_priority: + if self.cur_global_info["packetPriority"] != self.packet_priority: + cmd = "info-center syslog packet-priority %s" % self.packet_priority + self.updates_cmd.append(cmd) + self.changed = True + if self.state == "absent": + if self.packet_priority: + if self.cur_global_info["packetPriority"] == self.packet_priority: + cmd = "undo info-center syslog packet-priority %s" % self.packet_priority + self.updates_cmd.append(cmd) + self.changed = True + if self.changed: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge syslog global failed.') + + def get_syslog_logfile(self): + """get syslog logfile""" + + cur_logfile_info = dict() + conf_str = CE_NC_GET_LOG_FILE_INFO_HEADER + conf_str += "log" + if self.logfile_max_num: + conf_str += "" + if self.logfile_max_size: + conf_str += "" + conf_str += CE_NC_GET_LOG_FILE_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return cur_logfile_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + logfile_info = root.findall( + "syslog/icLogFileInfos/icLogFileInfo") + if logfile_info: + for tmp in logfile_info: + for site in tmp: + if site.tag in ["maxFileNum", "maxFileSize"]: + cur_logfile_info[site.tag] = site.text + return cur_logfile_info + + def merge_syslog_logfile(self): + """config logfile""" + + logfile_max_num = "200" + conf_str = CE_NC_MERGE_LOG_FILE_INFO_HEADER + if self.logfile_max_num: + if self.state == "present": + logfile_max_num = self.logfile_max_num + else: + if self.logfile_max_num != "200" and self.cur_logfile_info["maxFileNum"] == self.logfile_max_num: + logfile_max_num = "200" + conf_str += "%s" % logfile_max_num + + if self.logfile_max_size: + logfile_max_size = "32" + if self.state == "present": + logfile_max_size = self.logfile_max_size + else: + if self.logfile_max_size != "32" and self.cur_logfile_info["maxFileSize"] == self.logfile_max_size: + logfile_max_size = "32" + conf_str += "%s" % logfile_max_size + + conf_str += "log" + conf_str += CE_NC_MERGE_LOG_FILE_INFO_TAIL + + if self.state == "present": + if self.logfile_max_num: + if self.cur_logfile_info["maxFileNum"] != self.logfile_max_num: + cmd = "info-center max-logfile-number %s" % self.logfile_max_num + self.updates_cmd.append(cmd) + self.changed = True + if self.logfile_max_size: + if self.cur_logfile_info["maxFileSize"] != self.logfile_max_size: + cmd = "info-center logfile size %s" % self.logfile_max_size + self.updates_cmd.append(cmd) + self.changed = True + if self.state == "absent": + if self.logfile_max_num and self.logfile_max_num != "200": + if self.cur_logfile_info["maxFileNum"] == self.logfile_max_num: + cmd = "undo info-center max-logfile-number" + self.updates_cmd.append(cmd) + self.changed = True + if self.logfile_max_size and self.logfile_max_size != "32": + if self.cur_logfile_info["maxFileSize"] == self.logfile_max_size: + cmd = "undo info-center logfile size" + self.updates_cmd.append(cmd) + self.changed = True + + if self.changed: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog logfile failed.') + + def check_params(self): + """Check all input params""" + + # packet_priority check + if self.packet_priority: + if not self.packet_priority.isdigit(): + self.module.fail_json( + msg='Error: The parameter of packet priority is invalid.') + if int(self.packet_priority) > 7 or int(self.packet_priority) < 0: + self.module.fail_json( + msg='Error: The packet priority must be an integer between 0 and 7.') + + # logfile_max_num check + if self.logfile_max_num: + if not self.logfile_max_num.isdigit(): + self.module.fail_json( + msg='Error: The parameter of logfile_max_num is invalid.') + if int(self.logfile_max_num) > 500 or int(self.logfile_max_num) < 3: + self.module.fail_json( + msg='Error: The logfile_max_num must be an integer between 3 and 500.') + + # channel_id check + if self.channel_id: + if not self.channel_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of channel_id is invalid.') + if int(self.channel_id) > 9 or int(self.channel_id) < 0: + self.module.fail_json( + msg='Error: The channel_id must be an integer between 0 and 9.') + + # channel_cfg_name check + if self.channel_cfg_name: + if len(self.channel_cfg_name) > 30 \ + or len(self.channel_cfg_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: channel_cfg_name is not in the range from 1 to 30.') + + # filter_feature_name check + if self.filter_feature_name: + if len(self.filter_feature_name) > 31 \ + or len(self.filter_feature_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: filter_feature_name is not in the range from 1 to 31.') + + # filter_log_name check + if self.filter_log_name: + if len(self.filter_log_name) > 63 \ + or len(self.filter_log_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: filter_log_name is not in the range from 1 to 63.') + + # server_ip check + if self.server_ip: + if not check_ip_addr(self.server_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address' % self.server_ip) + # source_ip check + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address' % self.source_ip) + + # server_domain check + if self.server_domain: + if len(self.server_domain) > 255 \ + or len(self.server_domain.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: server_domain is not in the range from 1 to 255.') + + # vrf_name check + if self.vrf_name: + if len(self.vrf_name) > 31 \ + or len(self.vrf_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: vrf_name is not in the range from 1 to 31.') + + # server_port check + if self.server_port: + if not self.server_port.isdigit(): + self.module.fail_json( + msg='Error: The parameter of server_port is invalid.') + if int(self.server_port) > 65535 or int(self.server_port) < 1: + self.module.fail_json( + msg='Error: The server_port must be an integer between 1 and 65535.') + + # channel_name check + if self.channel_name: + if len(self.channel_name) > 31 \ + or len(self.channel_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: channel_name is not in the range from 1 to 30.') + + # ssl_policy_name check + if self.ssl_policy_name: + if len(self.ssl_policy_name) > 23 \ + or len(self.ssl_policy_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: ssl_policy_name is not in the range from 1 to 23.') + + def get_proposed(self): + """get proposed info""" + + if self.info_center_enable: + self.proposed["info_center_enable"] = self.info_center_enable + if self.packet_priority: + self.proposed["packet_priority"] = self.packet_priority + if self.suppress_enable: + self.proposed["suppress_enable"] = self.suppress_enable + if self.logfile_max_num: + self.proposed["logfile_max_num"] = self.logfile_max_num + if self.logfile_max_size: + self.proposed["logfile_max_size"] = self.logfile_max_size + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.channel_cfg_name: + self.proposed["channel_cfg_name"] = self.channel_cfg_name + if self.channel_out_direct: + self.proposed["channel_out_direct"] = self.channel_out_direct + if self.filter_feature_name: + self.proposed["filter_feature_name"] = self.filter_feature_name + if self.filter_log_name: + self.proposed["filter_log_name"] = self.filter_log_name + if self.ip_type: + self.proposed["ip_type"] = self.ip_type + if self.server_ip: + self.proposed["server_ip"] = self.server_ip + if self.server_domain: + self.proposed["server_domain"] = self.server_domain + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.level: + self.proposed["level"] = self.level + if self.server_port: + self.proposed["server_port"] = self.server_port + if self.facility: + self.proposed["facility"] = self.facility + if self.channel_name: + self.proposed["channel_name"] = self.channel_name + if self.timestamp: + self.proposed["timestamp"] = self.timestamp + if self.ssl_policy_name: + self.proposed["ssl_policy_name"] = self.ssl_policy_name + if self.transport_mode: + self.proposed["transport_mode"] = self.transport_mode + if self.is_default_vpn: + self.proposed["is_default_vpn"] = self.is_default_vpn + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.info_center_enable: + self.existing["info_center_enable"] = self.cur_global_info[ + "icEnable"] + if self.packet_priority: + self.existing["packet_priority"] = self.cur_global_info[ + "packetPriority"] + if self.suppress_enable: + self.existing["suppress_enable"] = self.cur_global_info[ + "suppressEnable"] + if self.logfile_max_num: + self.existing["logfile_max_num"] = self.cur_logfile_info[ + "maxFileNum"] + if self.logfile_max_size: + self.existing["logfile_max_size"] = self.cur_logfile_info[ + "maxFileSize"] + + if self.channel_id and self.channel_cfg_name: + if self.channel_info: + self.existing["channel_id_info"] = self.channel_info[ + "channelInfos"] + if self.channel_out_direct and self.channel_id: + if self.channel_direct_info: + self.existing["channel_out_direct_info"] = self.channel_direct_info[ + "channelDirectInfos"] + if self.filter_feature_name and self.filter_log_name: + if self.filter_info: + self.existing["filter_id_info"] = self.filter_info[ + "filterInfos"] + if self.ip_type: + if self.server_ip_info: + self.existing["server_ip_info"] = self.server_ip_info[ + "serverIpInfos"] + + if self.server_domain: + if self.server_domain_info: + self.existing["server_domain_info"] = self.server_domain_info[ + "serverAddressInfos"] + + def get_end_state(self): + """get end state info""" + + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.cur_global_info = self.get_syslog_global() + if self.logfile_max_num or self.logfile_max_size: + self.cur_logfile_info = self.get_syslog_logfile() + if self.channel_id and self.channel_cfg_name: + self.channel_info = self.get_channel_dict() + if self.channel_out_direct and self.channel_id: + self.channel_direct_info = self.get_channel_direct_dict() + if self.filter_feature_name and self.filter_log_name: + self.filter_info = self.get_filter_dict() + if self.ip_type: + self.server_ip_info = self.get_server_ip_dict() + if self.server_domain: + self.server_domain_info = self.get_server_domain_dict() + + if self.info_center_enable: + self.end_state[ + "info_center_enable"] = self.cur_global_info["icEnable"] + if self.packet_priority: + self.end_state["packet_priority"] = self.cur_global_info[ + "packetPriority"] + if self.suppress_enable: + self.end_state["suppress_enable"] = self.cur_global_info[ + "suppressEnable"] + if self.logfile_max_num: + self.end_state["logfile_max_num"] = self.cur_logfile_info[ + "maxFileNum"] + if self.logfile_max_size: + self.end_state["logfile_max_size"] = self.cur_logfile_info[ + "maxFileSize"] + + if self.channel_id and self.channel_cfg_name: + if self.channel_info: + self.end_state["channel_id_info"] = self.channel_info[ + "channelInfos"] + + if self.channel_out_direct and self.channel_id: + if self.channel_direct_info: + self.end_state["channel_out_direct_info"] = self.channel_direct_info[ + "channelDirectInfos"] + + if self.filter_feature_name and self.filter_log_name: + if self.filter_info: + self.end_state["filter_id_info"] = self.filter_info[ + "filterInfos"] + + if self.ip_type: + if self.server_ip_info: + self.end_state["server_ip_info"] = self.server_ip_info[ + "serverIpInfos"] + + if self.server_domain: + if self.server_domain_info: + self.end_state["server_domain_info"] = self.server_domain_info[ + "serverAddressInfos"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.cur_global_info = self.get_syslog_global() + if self.logfile_max_num or self.logfile_max_size: + self.cur_logfile_info = self.get_syslog_logfile() + if self.channel_id: + self.channel_info = self.get_channel_dict() + if self.channel_out_direct: + self.channel_direct_info = self.get_channel_direct_dict() + if self.filter_feature_name and self.filter_log_name: + self.filter_info = self.get_filter_dict() + if self.ip_type: + self.server_ip_info = self.get_server_ip_dict() + if self.server_domain: + self.server_domain_info = self.get_server_domain_dict() + self.get_existing() + self.get_proposed() + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.merge_syslog_global() + + if self.logfile_max_num or self.logfile_max_size: + self.merge_syslog_logfile() + + if self.server_ip: + if not self.ip_type: + self.module.fail_json( + msg='Error: ip_type and server_ip must be exist at the same time.') + if self.ip_type: + if not self.server_ip: + self.module.fail_json( + msg='Error: ip_type and server_ip must be exist at the same time.') + + if self.ip_type or self.server_domain or self.channel_id or self.filter_feature_name: + if self.ip_type and self.server_domain: + self.module.fail_json( + msg='Error: ip_type and server_domain can not be exist at the same time.') + if self.channel_id and self.channel_name: + self.module.fail_json( + msg='Error: channel_id and channel_name can not be exist at the same time.') + if self.ssl_policy_name: + if self.transport_mode == "udp": + self.module.fail_json( + msg='Error: transport_mode: udp does not support ssl_policy.') + if not self.transport_mode: + self.module.fail_json( + msg='Error: transport_mode, ssl_policy_name must be exist at the same time.') + if self.ip_type == "ipv6": + if self.vrf_name and self.vrf_name != "_public_": + self.module.fail_json( + msg='Error: ipType:ipv6 only support default vpn:_public_.') + if self.is_default_vpn is True: + if self.vrf_name: + if self.vrf_name != "_public_": + self.module.fail_json( + msg='Error: vrf_name should be _public_ when is_default_vpn is True.') + else: + self.vrf_name = "_public_" + else: + if self.vrf_name == "_public_": + self.module.fail_json( + msg='Error: The default vpn value is _public_, but is_default_vpn is False.') + if self.state == "present": + # info-center channel channel-number name channel-name + if self.channel_id and self.channel_cfg_name: + self.config_merge_syslog_channel( + self.channel_id, self.channel_cfg_name) + # info-center { console | logfile | monitor | snmp | logbuffer + # | trapbuffer } channel channel-number + if self.channel_out_direct and self.channel_id: + self.config_merge_out_direct( + self.channel_out_direct, self.channel_id) + # info-center filter-id bymodule-alias modname alias + if self.filter_feature_name and self.filter_log_name: + self.config_merge_filter( + self.filter_feature_name, self.filter_log_name) + if self.ip_type and self.server_ip: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.config_merge_loghost() + if self.server_domain: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.config_merge_loghost() + + elif self.state == "absent": + if self.channel_id: + self.delete_merge_syslog_channel( + self.channel_id, self.channel_cfg_name) + if self.channel_out_direct: + self.delete_merge_out_direct( + self.channel_out_direct, self.channel_id) + if self.filter_feature_name and self.filter_log_name: + self.delete_merge_filter( + self.filter_feature_name, self.filter_log_name) + if self.ip_type and self.server_ip: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.delete_merge_loghost() + if self.server_domain: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.delete_merge_loghost() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + info_center_enable=dict(choices=['true', 'false']), + packet_priority=dict(type='str'), + suppress_enable=dict(choices=['true', 'false']), + logfile_max_num=dict(type='str'), + logfile_max_size=dict(choices=['4', '8', '16', '32']), + channel_id=dict(type='str'), + channel_cfg_name=dict(type='str'), + channel_out_direct=dict(choices=['console', 'monitor', + 'trapbuffer', 'logbuffer', 'snmp', 'logfile']), + filter_feature_name=dict(type='str'), + filter_log_name=dict(type='str'), + ip_type=dict(choices=['ipv4', 'ipv6']), + server_ip=dict(type='str'), + server_domain=dict(type='str'), + is_default_vpn=dict(default=False, type='bool'), + vrf_name=dict(type='str'), + level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']), + server_port=dict(type='str'), + facility=dict(choices=['local0', 'local1', 'local2', + 'local3', 'local4', 'local5', 'local6', 'local7']), + channel_name=dict(type='str'), + timestamp=dict(choices=['UTC', 'localtime']), + transport_mode=dict(choices=['tcp', 'udp']), + ssl_policy_name=dict(type='str'), + source_ip=dict(type='str'), + state=dict(choices=['present', 'absent'], default='present') + + ) + argument_spec.update(ce_argument_spec) + module = InfoCenterGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_log.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_log.py new file mode 100644 index 00000000..9bbdf2c9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_log.py @@ -0,0 +1,544 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_log +short_description: Manages information center log configuration on HUAWEI CloudEngine switches. +description: + - Setting the Timestamp Format of Logs. + Configuring the Device to Output Logs to the Log Buffer. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + log_time_stamp: + description: + - Sets the timestamp format of logs. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', + 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', + 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond'] + log_buff_enable: + description: + - Enables the Switch to send logs to the log buffer. + default: no_use + choices: ['no_use','true', 'false'] + log_buff_size: + description: + - Specifies the maximum number of logs in the log buffer. + The value is an integer that ranges from 0 to 10240. If logbuffer-size is 0, logs are not displayed. + module_name: + description: + - Specifies the name of a module. + The value is a module name in registration logs. + channel_id: + description: + - Specifies a channel ID. + The value is an integer ranging from 0 to 9. + log_enable: + description: + - Indicates whether log filtering is enabled. + default: no_use + choices: ['no_use','true', 'false'] + log_level: + description: + - Specifies a log severity. + choices: ['emergencies', 'alert', 'critical', 'error', + 'warning', 'notification', 'informational', 'debugging'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center log test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Setting the timestamp format of logs" + community.network.ce_info_center_log: + log_time_stamp: date_tenthsecond + provider: "{{ cli }}" + + - name: "Enabled to output information to the log buffer" + community.network.ce_info_center_log: + log_buff_enable: true + provider: "{{ cli }}" + + - name: "Set the maximum number of logs in the log buffer" + community.network.ce_info_center_log: + log_buff_size: 100 + provider: "{{ cli }}" + + - name: "Set a rule for outputting logs to a channel" + community.network.ce_info_center_log: + module_name: aaa + channel_id: 1 + log_enable: true + log_level: critical + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_tenthsecond", "state": "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_second"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_tenthsecond"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["info-center timestamp log date precision-time tenth-second"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_LOG = """ + + + + + + + + + + %s + %s + + + + + + + +""" + +CE_NC_GET_LOG_GLOBAL = """ + + + + + + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_LOG_STATE = {"0": "true", + "1": "true", + "2": "true", + "3": "false", + "4": "true", + "5": "false", + "6": "true", + "7": "true", + "8": "true", + "9": "true"} + +CHANNEL_DEFAULT_LOG_LEVEL = {"0": "warning", + "1": "warning", + "2": "informational", + "3": "informational", + "4": "warning", + "5": "debugging", + "6": "debugging", + "7": "warning", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterLog(object): + """ + Manages information center log configuration + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.log_time_stamp = self.module.params['log_time_stamp'] + self.log_buff_enable = self.module.params['log_buff_enable'] + self.log_buff_size = self.module.params['log_buff_size'] + self.module_name = self.module.params['module_name'] + self.channel_id = self.module.params['channel_id'] + self.log_enable = self.module.params['log_enable'] + self.log_level = self.module.params['log_level'] + self.state = self.module.params['state'] + + # state + self.log_dict = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_log_dict(self): + """ log config dict""" + + log_dict = dict() + if self.module_name: + if self.module_name.lower() == "default": + conf_str = CE_NC_GET_LOG % (self.module_name.lower(), self.channel_id) + else: + conf_str = CE_NC_GET_LOG % (self.module_name.upper(), self.channel_id) + else: + conf_str = CE_NC_GET_LOG_GLOBAL + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return log_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get global param info + glb = root.find("syslog/globalParam") + if glb: + for attr in glb: + if attr.tag in ["bufferSize", "logTimeStamp", "icLogBuffEn"]: + log_dict[attr.tag] = attr.text + + # get info-center source info + log_dict["source"] = dict() + src = root.find("syslog/icSources/icSource") + if src: + for attr in src: + if attr.tag in ["moduleName", "icChannelId", "icChannelName", "logEnFlg", "logEnLevel"]: + log_dict["source"][attr.tag] = attr.text + + return log_dict + + def config_log_global(self): + """config log global param""" + + xml_str = '' + if self.log_time_stamp: + if self.state == "present" and self.log_time_stamp.upper() != self.log_dict.get("logTimeStamp"): + xml_str += '%s' % self.log_time_stamp.upper() + self.updates_cmd.append( + "info-center timestamp log %s" % TIME_STAMP_DICT.get(self.log_time_stamp)) + elif self.state == "absent" and self.log_time_stamp.upper() == self.log_dict.get("logTimeStamp"): + xml_str += 'DATE_SECOND' # set default + self.updates_cmd.append("undo info-center timestamp log") + else: + pass + + if self.log_buff_enable != 'no_use': + if self.log_dict.get("icLogBuffEn") != self.log_buff_enable: + xml_str += '%s' % self.log_buff_enable + if self.log_buff_enable == "true": + self.updates_cmd.append("info-center logbuffer") + else: + self.updates_cmd.append("undo info-center logbuffer") + + if self.log_buff_size: + if self.state == "present" and self.log_dict.get("bufferSize") != self.log_buff_size: + xml_str += '%s' % self.log_buff_size + self.updates_cmd.append( + "info-center logbuffer size %s" % self.log_buff_size) + elif self.state == "absent" and self.log_dict.get("bufferSize") == self.log_buff_size: + xml_str += '512' + self.updates_cmd.append("undo info-center logbuffer size") + + if xml_str == '': + return "" + else: + xml_str += '' + return xml_str + + def config_log_soruce(self): + """config info-center sources""" + + xml_str = '' + if not self.module_name or not self.channel_id: + return xml_str + + source = self.log_dict["source"] + if self.state == "present": + xml_str = '' + cmd = 'info-center source %s channel %s log' % ( + self.module_name, self.channel_id) + else: + if not source or self.module_name != source.get("moduleName").lower() or \ + self.channel_id != source.get("icChannelId"): + return '' + + if self.log_enable == 'no_use' and not self.log_level: + xml_str = '' + else: + xml_str = '' + cmd = 'undo info-center source %s channel %s log' % ( + self.module_name, self.channel_id) + + xml_str += '%s%s' % ( + self.module_name, self.channel_id) + + # log_enable + if self.log_enable != 'no_use': + if self.state == "present" and (not source or self.log_enable != source.get("logEnFlg")): + xml_str += '%s' % self.log_enable + if self.log_enable == "true": + cmd += ' state on' + else: + cmd += ' state off' + elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): + xml_str += '%s' % CHANNEL_DEFAULT_LOG_STATE.get(self.channel_id) + cmd += ' state' + + # log_level + if self.log_level: + if self.state == "present" and (not source or self.log_level != source.get("logEnLevel")): + xml_str += '%s' % self.log_level + cmd += ' level %s' % self.log_level + elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): + xml_str += '%s' % CHANNEL_DEFAULT_LOG_LEVEL.get(self.channel_id) + cmd += ' level' + + if xml_str.endswith(""): + if self.log_enable == 'no_use' and not self.log_level and self.state == "absent": + xml_str += '' + self.updates_cmd.append(cmd) + return xml_str + else: + return '' + else: + xml_str += '' + self.updates_cmd.append(cmd) + return xml_str + + def netconf_load_config(self, xml_str): + """load log config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + recv_xml = set_nc_config(self.module, xml_cfg) + self.check_response(recv_xml, "SET_LOG") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check log_buff_size ranges from 0 to 10240 + if self.log_buff_size: + if not self.log_buff_size.isdigit(): + self.module.fail_json( + msg="Error: log_buff_size is not digit.") + if int(self.log_buff_size) < 0 or int(self.log_buff_size) > 10240: + self.module.fail_json( + msg="Error: log_buff_size is not ranges from 0 to 10240.") + + # check channel_id ranging from 0 to 9 + if self.channel_id: + if not self.channel_id.isdigit(): + self.module.fail_json(msg="Error: channel_id is not digit.") + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg="Error: channel_id is not ranges from 0 to 9.") + + # module_name and channel_id must be set at the same time + if bool(self.module_name) != bool(self.channel_id): + self.module.fail_json( + msg="Error: module_name and channel_id must be set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.log_time_stamp: + self.proposed["log_time_stamp"] = self.log_time_stamp + if self.log_buff_enable != 'no_use': + self.proposed["log_buff_enable"] = self.log_buff_enable + if self.log_buff_size: + self.proposed["log_buff_size"] = self.log_buff_size + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.log_enable != 'no_use': + self.proposed["log_enable"] = self.log_enable + if self.log_level: + self.proposed["log_level"] = self.log_level + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.log_dict: + return + + if self.log_time_stamp: + self.existing["log_time_stamp"] = self.log_dict.get("logTimeStamp").lower() + if self.log_buff_enable != 'no_use': + self.existing["log_buff_enable"] = self.log_dict.get("icLogBuffEn") + if self.log_buff_size: + self.existing["log_buff_size"] = self.log_dict.get("bufferSize") + if self.module_name: + self.existing["source"] = self.log_dict.get("source") + + def get_end_state(self): + """get end state info""" + + log_dict = self.get_log_dict() + if not log_dict: + return + + if self.log_time_stamp: + self.end_state["log_time_stamp"] = log_dict.get("logTimeStamp").lower() + if self.log_buff_enable != 'no_use': + self.end_state["log_buff_enable"] = log_dict.get("icLogBuffEn") + if self.log_buff_size: + self.end_state["log_buff_size"] = log_dict.get("bufferSize") + if self.module_name: + self.end_state["source"] = log_dict.get("source") + + def work(self): + """worker""" + + self.check_params() + self.log_dict = self.get_log_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.log_time_stamp or self.log_buff_enable != 'no_use' or self.log_buff_size: + xml_str += self.config_log_global() + + if self.module_name: + xml_str += self.config_log_soruce() + + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + log_time_stamp=dict(required=False, type='str', + choices=['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', + 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', + 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond']), + log_buff_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + log_buff_size=dict(required=False, type='str'), + module_name=dict(required=False, type='str'), + channel_id=dict(required=False, type='str'), + log_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + log_level=dict(required=False, type='str', + choices=['emergencies', 'alert', 'critical', 'error', + 'warning', 'notification', 'informational', 'debugging']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterLog(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_trap.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_trap.py new file mode 100644 index 00000000..165ef23b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_info_center_trap.py @@ -0,0 +1,693 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_trap +short_description: Manages information center trap configuration on HUAWEI CloudEngine switches. +description: + - Manages information center trap configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + trap_time_stamp: + description: + - Timestamp format of alarm information. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 'shortdate_second', + 'shortdate_tenthsecond', 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond'] + trap_buff_enable: + description: + - Whether a trap buffer is enabled to output information. + default: no_use + choices: ['no_use','true','false'] + trap_buff_size: + description: + - Size of a trap buffer. + The value is an integer ranging from 0 to 1024. The default value is 256. + module_name: + description: + - Module name of the rule. + The value is a string of 1 to 31 case-insensitive characters. The default value is default. + Please use lower-case letter, such as [aaa, acl, arp, bfd]. + channel_id: + description: + - Number of a channel. + The value is an integer ranging from 0 to 9. The default value is 0. + trap_enable: + description: + - Whether a device is enabled to output alarms. + default: no_use + choices: ['no_use','true','false'] + trap_level: + description: + - Trap level permitted to output. + choices: ['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center trap test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config trap buffer" + community.network.ce_info_center_trap: + state: present + trap_buff_enable: true + trap_buff_size: 768 + provider: "{{ cli }}" + + - name: "Undo trap buffer" + community.network.ce_info_center_trap: + state: absent + trap_buff_enable: true + trap_buff_size: 768 + provider: "{{ cli }}" + + - name: "Config trap module log level" + community.network.ce_info_center_trap: + state: present + module_name: aaa + channel_id: 1 + trap_enable: true + trap_level: error + provider: "{{ cli }}" + + - name: "Undo trap module log level" + community.network.ce_info_center_trap: + state: absent + module_name: aaa + channel_id: 1 + trap_enable: true + trap_level: error + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"state": "present", "trap_buff_enable": "true", "trap_buff_size": "768"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"icTrapBuffEn": "false", "trapBuffSize": "256"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"icTrapBuffEn": "true", "trapBuffSize": "768"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center trapbuffer", "info-center trapbuffer size 768"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get info center trap global +CE_GET_TRAP_GLOBAL_HEADER = """ + + + +""" +CE_GET_TRAP_GLOBAL_TAIL = """ + + + +""" +# merge info center trap global +CE_MERGE_TRAP_GLOBAL_HEADER = """ + + + +""" +CE_MERGE_TRAP_GLOBAL_TAIL = """ + + + +""" + +# get info center trap source +CE_GET_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_GET_TRAP_SOURCE_TAIL = """ + + + + +""" +# merge info center trap source +CE_MERGE_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_MERGE_TRAP_SOURCE_TAIL = """ + + + + +""" +# delete info center trap source +CE_DELETE_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_DELETE_TRAP_SOURCE_TAIL = """ + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_TRAP_STATE = {"0": "true", + "1": "true", + "2": "true", + "3": "true", + "4": "false", + "5": "true", + "6": "true", + "7": "true", + "8": "true", + "9": "true"} + +CHANNEL_DEFAULT_TRAP_LEVEL = {"0": "debugging", + "1": "debugging", + "2": "debugging", + "3": "debugging", + "4": "debugging", + "5": "debugging", + "6": "debugging", + "7": "debugging", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterTrap(object): + """ Manages info center trap configuration """ + + def __init__(self, **kwargs): + """ Init function """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.trap_time_stamp = self.module.params['trap_time_stamp'] or None + self.trap_buff_enable = self.module.params['trap_buff_enable'] + self.trap_buff_size = self.module.params['trap_buff_size'] or None + self.module_name = self.module.params['module_name'] or None + self.channel_id = self.module.params['channel_id'] or None + self.trap_enable = self.module.params['trap_enable'] + self.trap_level = self.module.params['trap_level'] or None + + # cur config + self.cur_global_cfg = dict() + self.cur_source_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Netconf get config """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Netconf set config """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def check_global_args(self): + """ Check global args """ + + need_cfg = False + find_flag = False + self.cur_global_cfg["global_cfg"] = [] + + if self.trap_time_stamp or self.trap_buff_enable != 'no_use' or self.trap_buff_size: + if self.trap_buff_size: + if self.trap_buff_size.isdigit(): + if int(self.trap_buff_size) < 0 or int(self.trap_buff_size) > 1024: + self.module.fail_json( + msg='Error: The value of trap_buff_size is out of [0 - 1024].') + else: + self.module.fail_json( + msg='Error: The trap_buff_size is not digit.') + + conf_str = CE_GET_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "" + if self.trap_buff_enable != 'no_use': + conf_str += "" + if self.trap_buff_size: + conf_str += "" + + conf_str += CE_GET_TRAP_GLOBAL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_cfg = root.findall("syslog/globalParam") + if global_cfg: + for tmp in global_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["trapTimeStamp", "icTrapBuffEn", "trapBuffSize"]: + tmp_dict[site.tag] = site.text + + self.cur_global_cfg["global_cfg"].append(tmp_dict) + + if self.cur_global_cfg["global_cfg"]: + for tmp in self.cur_global_cfg["global_cfg"]: + find_flag = True + + if self.trap_time_stamp and tmp.get("trapTimeStamp").lower() != self.trap_time_stamp: + find_flag = False + if self.trap_buff_enable != 'no_use' and tmp.get("icTrapBuffEn") != self.trap_buff_enable: + find_flag = False + if self.trap_buff_size and tmp.get("trapBuffSize") != self.trap_buff_size: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_global_cfg["need_cfg"] = need_cfg + + def check_source_args(self): + """ Check source args """ + + need_cfg = False + find_flag = False + self.cur_source_cfg["source_cfg"] = list() + + if self.module_name: + if len(self.module_name) < 1 or len(self.module_name) > 31: + self.module.fail_json( + msg='Error: The module_name is out of [1 - 31].') + + if not self.channel_id: + self.module.fail_json( + msg='Error: Please input channel_id at the same time.') + + if self.channel_id: + if self.channel_id.isdigit(): + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg='Error: The value of channel_id is out of [0 - 9].') + else: + self.module.fail_json( + msg='Error: The channel_id is not digit.') + + conf_str = CE_GET_TRAP_SOURCE_HEADER + + if self.module_name != "default": + conf_str += "%s" % self.module_name.upper() + else: + conf_str += "default" + + if self.channel_id: + conf_str += "" + if self.trap_enable != 'no_use': + conf_str += "" + if self.trap_level: + conf_str += "" + + conf_str += CE_GET_TRAP_SOURCE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + source_cfg = root.findall("syslog/icSources/icSource") + if source_cfg: + for tmp in source_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["moduleName", "icChannelId", "trapEnFlg", "trapEnLevel"]: + tmp_dict[site.tag] = site.text + + self.cur_source_cfg["source_cfg"].append(tmp_dict) + + if self.cur_source_cfg["source_cfg"]: + for tmp in self.cur_source_cfg["source_cfg"]: + find_flag = True + + if self.module_name and tmp.get("moduleName").lower() != self.module_name.lower(): + find_flag = False + if self.channel_id and tmp.get("icChannelId") != self.channel_id: + find_flag = False + if self.trap_enable != 'no_use' and tmp.get("trapEnFlg") != self.trap_enable: + find_flag = False + if self.trap_level and tmp.get("trapEnLevel") != self.trap_level: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_source_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed """ + + self.proposed["state"] = self.state + + if self.trap_time_stamp: + self.proposed["trap_time_stamp"] = self.trap_time_stamp + if self.trap_buff_enable != 'no_use': + self.proposed["trap_buff_enable"] = self.trap_buff_enable + if self.trap_buff_size: + self.proposed["trap_buff_size"] = self.trap_buff_size + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.trap_enable != 'no_use': + self.proposed["trap_enable"] = self.trap_enable + if self.trap_level: + self.proposed["trap_level"] = self.trap_level + + def get_existing(self): + """ Get existing """ + + if self.cur_global_cfg["global_cfg"]: + self.existing["global_cfg"] = self.cur_global_cfg["global_cfg"] + if self.cur_source_cfg["source_cfg"]: + self.existing["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def get_end_state(self): + """ Get end state """ + + self.check_global_args() + if self.cur_global_cfg["global_cfg"]: + self.end_state["global_cfg"] = self.cur_global_cfg["global_cfg"] + + self.check_source_args() + if self.cur_source_cfg["source_cfg"]: + self.end_state["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def merge_trap_global(self): + """ Merge trap global """ + + conf_str = CE_MERGE_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "%s" % self.trap_time_stamp.upper() + if self.trap_buff_enable != 'no_use': + conf_str += "%s" % self.trap_buff_enable + if self.trap_buff_size: + conf_str += "%s" % self.trap_buff_size + + conf_str += CE_MERGE_TRAP_GLOBAL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge trap global failed.') + + if self.trap_time_stamp: + cmd = "info-center timestamp trap " + TIME_STAMP_DICT.get(self.trap_time_stamp) + self.updates_cmd.append(cmd) + if self.trap_buff_enable != 'no_use': + if self.trap_buff_enable == "true": + cmd = "info-center trapbuffer" + else: + cmd = "undo info-center trapbuffer" + self.updates_cmd.append(cmd) + if self.trap_buff_size: + cmd = "info-center trapbuffer size %s" % self.trap_buff_size + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_trap_global(self): + """ Delete trap global """ + + conf_str = CE_MERGE_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "DATE_SECOND" + if self.trap_buff_enable != 'no_use': + conf_str += "false" + if self.trap_buff_size: + conf_str += "256" + + conf_str += CE_MERGE_TRAP_GLOBAL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: delete trap global failed.') + + if self.trap_time_stamp: + cmd = "undo info-center timestamp trap" + self.updates_cmd.append(cmd) + if self.trap_buff_enable != 'no_use': + cmd = "undo info-center trapbuffer" + self.updates_cmd.append(cmd) + if self.trap_buff_size: + cmd = "undo info-center trapbuffer size" + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_trap_source(self): + """ Merge trap source """ + + conf_str = CE_MERGE_TRAP_SOURCE_HEADER + + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.trap_enable != 'no_use': + conf_str += "%s" % self.trap_enable + if self.trap_level: + conf_str += "%s" % self.trap_level + + conf_str += CE_MERGE_TRAP_SOURCE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge trap source failed.') + + cmd = "info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.trap_enable != 'no_use': + if self.trap_enable == "true": + cmd += " trap state on" + else: + cmd += " trap state off" + if self.trap_level: + cmd += " level %s" % self.trap_level + + self.updates_cmd.append(cmd) + self.changed = True + + def delete_trap_source(self): + """ Delete trap source """ + + if self.trap_enable == 'no_use' and not self.trap_level: + conf_str = CE_DELETE_TRAP_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + conf_str += CE_DELETE_TRAP_SOURCE_TAIL + else: + conf_str = CE_MERGE_TRAP_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.trap_enable != 'no_use': + conf_str += "%s" % CHANNEL_DEFAULT_TRAP_STATE.get(self.channel_id) + if self.trap_level: + conf_str += "%s" % CHANNEL_DEFAULT_TRAP_LEVEL.get(self.channel_id) + conf_str += CE_MERGE_TRAP_SOURCE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete trap source failed.') + + cmd = "undo info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.trap_enable != 'no_use': + cmd += " trap state" + if self.trap_level: + cmd += " level" + + self.updates_cmd.append(cmd) + self.changed = True + + def work(self): + """ work function """ + + self.check_global_args() + self.check_source_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_global_cfg["need_cfg"]: + self.merge_trap_global() + if self.cur_source_cfg["need_cfg"]: + self.merge_trap_source() + + else: + if self.cur_global_cfg["need_cfg"]: + self.delete_trap_global() + if self.cur_source_cfg["need_cfg"]: + self.delete_trap_source() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + trap_time_stamp=dict(choices=['date_boot', 'date_second', 'date_tenthsecond', + 'date_millisecond', 'shortdate_second', 'shortdate_tenthsecond', + 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond']), + trap_buff_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + trap_buff_size=dict(type='str'), + module_name=dict(type='str'), + channel_id=dict(type='str'), + trap_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + trap_level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterTrap(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface.py new file mode 100644 index 00000000..95bab788 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface.py @@ -0,0 +1,891 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_interface +short_description: Manages physical attributes of interfaces on HUAWEI CloudEngine switches. +description: + - Manages physical attributes of interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module is also used to create logical interfaces such as + vlanif and loopbacks. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/10, Tunnel1. + interface_type: + description: + - Interface type to be configured from the device. + choices: ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'vlanif', 'loopback', 'meth', + 'eth-trunk', 'nve', 'tunnel', 'ethernet', 'fcoe-port', 'fabric-port', 'stack-port', 'null'] + admin_state: + description: + - Specifies the interface management status. + The value is an enumerated type. + up, An interface is in the administrative Up state. + down, An interface is in the administrative Down state. + choices: ['up', 'down'] + description: + description: + - Specifies an interface description. + The value is a string of 1 to 242 case-sensitive characters, + spaces supported but question marks (?) not supported. + mode: + description: + - Manage Layer 2 or Layer 3 state of the interface. + choices: ['layer2', 'layer3'] + l2sub: + description: + - Specifies whether the interface is a Layer 2 sub-interface. + type: bool + default: 'no' + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent', 'default'] +''' + +EXAMPLES = ''' +- name: Interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure an interface is a Layer 3 port and that it has the proper description + community.network.ce_interface: + interface: 10GE1/0/22 + description: 'Configured by Ansible' + mode: layer3 + provider: '{{ cli }}' + + - name: Admin down an interface + community.network.ce_interface: + interface: 10GE1/0/22 + admin_state: down + provider: '{{ cli }}' + + - name: Remove all tunnel interfaces + community.network.ce_interface: + interface_type: tunnel + state: absent + provider: '{{ cli }}' + + - name: Remove all logical interfaces + community.network.ce_interface: + interface_type: '{{ item }}' + state: absent + provider: '{{ cli }}' + with_items: + - loopback + - eth-trunk + - nve + + - name: Admin up all 10GE interfaces + community.network.ce_interface: + interface_type: 10GE + admin_state: up + provider: '{{ cli }}' +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"interface": "10GE1/0/10", "admin_state": "down"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"admin_state": "up", "description": "None", + "interface": "10GE1/0/10", "mode": "layer2"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"admin_state": "down", "description": "None", + "interface": "10GE1/0/10", "mode": "layer2"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/10", "shutdown"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_INTFS = """ + + + + + + %s + + + + + + + + + +""" + + +CE_NC_GET_INTF = """ + + + + + %s + + + + + + + + + + +""" + +CE_NC_XML_CREATE_INTF = """ + + + + %s + %s + + + +""" + +CE_NC_XML_CREATE_INTF_L2SUB = """ + + + + %s + %s + true + + + +""" + +CE_NC_XML_DELETE_INTF = """ + + + + %s + + + +""" + + +CE_NC_XML_MERGE_INTF_DES = """ + + + + %s + %s + + + +""" +CE_NC_XML_MERGE_INTF_STATUS = """ + + + + %s + %s + + + +""" + +CE_NC_XML_MERGE_INTF_L2ENABLE = """ + + + + %s + %s + + + +""" + +ADMIN_STATE_TYPE = ('ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', + 'vlanif', 'meth', 'eth-trunk', 'vbdif', 'tunnel', + 'ethernet', 'stack-port') + +SWITCH_PORT_TYPE = ('ge', '10ge', '25ge', + '4x10ge', '40ge', '100ge', 'eth-trunk') + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_admin_state_enable(iftype): + """admin state disable: loopback nve""" + + return bool(iftype in ADMIN_STATE_TYPE) + + +def is_portswitch_enalbe(iftype): + """"is portswitch? """ + + return bool(iftype in SWITCH_PORT_TYPE) + + +class Interface(object): + """Manages physical attributes of interfaces.""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface info + self.interface = self.module.params['interface'] + self.interface_type = self.module.params['interface_type'] + self.admin_state = self.module.params['admin_state'] + self.description = self.module.params['description'] + self.mode = self.module.params['mode'] + self.l2sub = self.module.params['l2sub'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intfs_info = dict() # all type interface info + self.intf_info = dict() # one interface info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interfaces_dict(self): + """ get interfaces attributes dict.""" + + intfs_info = dict() + conf_str = CE_NC_GET_INTFS % self.interface_type + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return intfs_info + + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + intfs = root.findall("ifm/interfaces/") + if intfs: + for intf in intfs: + intf_type = intf.find("ifPhyType").text.lower() + if intf_type: + if not intfs_info.get(intf_type): + intfs_info[intf_type] = list() + intf_info = dict() + for tmp in intf: + intf_info[tmp.tag] = tmp.text + intfs_info[intf_type].append(intf_info) + return intfs_info + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_INTF % ifname + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return intf_info + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + intfs = root.findall("ifm/interfaces/interface/") + if intfs: + for intf in intfs: + intf_info[intf.tag] = intf.text + return intf_info + + def create_interface(self, ifname, description, admin_state, mode, l2sub): + """Create interface.""" + + if l2sub: + self.updates_cmd.append("interface %s mode l2" % ifname) + else: + self.updates_cmd.append("interface %s" % ifname) + + if not description: + description = '' + else: + self.updates_cmd.append("description %s" % description) + + if l2sub: + xmlstr = CE_NC_XML_CREATE_INTF_L2SUB % (ifname, description) + else: + xmlstr = CE_NC_XML_CREATE_INTF % (ifname, description) + if admin_state and is_admin_state_enable(self.intf_type): + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, admin_state) + if admin_state == 'up': + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + if mode and is_portswitch_enalbe(self.intf_type): + if mode == "layer2": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append('portswitch') + elif mode == "layer3": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'disable') + self.updates_cmd.append('undo portswitch') + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "CREATE_INTF") + self.changed = True + + def delete_interface(self, ifname): + """ Delete interface.""" + + xmlstr = CE_NC_XML_DELETE_INTF % ifname + conf_str = ' ' + xmlstr + ' ' + self.updates_cmd.append('undo interface %s' % ifname) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_INTF") + self.changed = True + + def delete_interfaces(self, iftype): + """ Delete interfaces with type.""" + + xmlstr = '' + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + xmlstr += CE_NC_XML_DELETE_INTF % intf['ifName'] + self.updates_cmd.append('undo interface %s' % intf['ifName']) + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_INTFS") + self.changed = True + + def merge_interface(self, ifname, description, admin_state, mode): + """ Merge interface attributes.""" + + xmlstr = '' + change = False + self.updates_cmd.append("interface %s" % ifname) + if description and self.intf_info["ifDescr"] != description: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (ifname, description) + self.updates_cmd.append("description %s" % description) + change = True + + if admin_state and is_admin_state_enable(self.intf_type) \ + and self.intf_info["ifAdminStatus"] != admin_state: + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, admin_state) + change = True + if admin_state == "up": + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + + if is_portswitch_enalbe(self.intf_type): + if mode == "layer2" and self.intf_info["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append("portswitch") + change = True + elif mode == "layer3" \ + and self.intf_info["isL2SwitchPort"] != "false": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'disable') + self.updates_cmd.append("undo portswitch") + change = True + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_INTF_ATTR") + self.changed = True + + def merge_interfaces(self, iftype, description, admin_state, mode): + """ Merge interface attributes by type.""" + + xmlstr = '' + change = False + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + if_change = False + self.updates_cmd.append("interface %s" % intf['ifName']) + if description and intf["ifDescr"] != description: + xmlstr += CE_NC_XML_MERGE_INTF_DES % ( + intf['ifName'], description) + self.updates_cmd.append("description %s" % description) + if_change = True + if admin_state and is_admin_state_enable(self.intf_type)\ + and intf["ifAdminStatus"] != admin_state: + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % ( + intf['ifName'], admin_state) + if_change = True + if admin_state == "up": + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + + if is_portswitch_enalbe(self.intf_type): + if mode == "layer2" \ + and intf["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % ( + intf['ifName'], 'enable') + self.updates_cmd.append("portswitch") + if_change = True + elif mode == "layer3" \ + and intf["isL2SwitchPort"] != "false": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % ( + intf['ifName'], 'disable') + self.updates_cmd.append("undo portswitch") + if_change = True + + if if_change: + change = True + else: + self.updates_cmd.pop() + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_INTFS_ATTR") + self.changed = True + + def default_interface(self, ifname): + """default_interface""" + + change = False + xmlstr = "" + self.updates_cmd.append("interface %s" % ifname) + # set description default + if self.intf_info["ifDescr"]: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (ifname, '') + self.updates_cmd.append("undo description") + change = True + + # set admin_status default + if is_admin_state_enable(self.intf_type) \ + and self.intf_info["ifAdminStatus"] != 'up': + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, 'up') + self.updates_cmd.append("undo shutdown") + change = True + + # set portswitch default + if is_portswitch_enalbe(self.intf_type) \ + and self.intf_info["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append("portswitch") + change = True + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "SET_INTF_DEFAULT") + self.changed = True + + def default_interfaces(self, iftype): + """ Set interface config to default by type.""" + + change = False + xmlstr = '' + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + if_change = False + self.updates_cmd.append("interface %s" % intf['ifName']) + + # set description default + if intf['ifDescr']: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (intf['ifName'], '') + self.updates_cmd.append("undo description") + if_change = True + + # set admin_status default + if is_admin_state_enable(self.intf_type) and intf["ifAdminStatus"] != 'up': + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (intf['ifName'], 'up') + self.updates_cmd.append("undo shutdown") + if_change = True + + # set portswitch default + if is_portswitch_enalbe(self.intf_type) and intf["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (intf['ifName'], 'enable') + self.updates_cmd.append("portswitch") + if_change = True + + if if_change: + change = True + else: + self.updates_cmd.pop() + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "SET_INTFS_DEFAULT") + self.changed = True + + def check_params(self): + """Check all input params""" + + if not self.interface and not self.interface_type: + self.module.fail_json( + msg='Error: Interface or interface_type must be set.') + if self.interface and self.interface_type: + self.module.fail_json( + msg='Error: Interface or interface_type' + ' can not be set at the same time.') + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: interface name of %s' + ' is error.' % self.interface) + + elif self.interface_type: + self.intf_type = get_interface_type(self.interface_type) + if not self.intf_type or self.intf_type != self.interface_type.replace(" ", "").lower(): + self.module.fail_json( + msg='Error: interface type of %s' + ' is error.' % self.interface_type) + + if not self.intf_type: + self.module.fail_json( + msg='Error: interface or interface type %s is error.') + + # shutdown check + if not is_admin_state_enable(self.intf_type) \ + and self.state == "present" and self.admin_state == "down": + self.module.fail_json( + msg='Error: The %s interface can not' + ' be shutdown.' % self.intf_type) + + # port switch mode check + if not is_portswitch_enalbe(self.intf_type)\ + and self.mode and self.state == "present": + self.module.fail_json( + msg='Error: The %s interface can not manage' + ' Layer 2 or Layer 3 state.' % self.intf_type) + + # check description len + if self.description: + if len(self.description) > 242 \ + or len(self.description.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: interface description ' + 'is not in the range from 1 to 242.') + # check l2sub flag + if self.l2sub: + if not self.interface: + self.module.fail_json(msg='Error: L2sub flag can not be set when there no interface set with.') + if self.interface.count(".") != 1: + self.module.fail_json(msg='Error: Interface name is invalid, it is not sub-interface.') + + def get_proposed(self): + """get_proposed""" + + self.proposed['state'] = self.state + if self.interface: + self.proposed["interface"] = self.interface + if self.interface_type: + self.proposed["interface_type"] = self.interface_type + + if self.state == 'present': + if self.description: + self.proposed["description"] = self.description + if self.mode: + self.proposed["mode"] = self.mode + if self.admin_state: + self.proposed["admin_state"] = self.admin_state + self.proposed["l2sub"] = self.l2sub + + elif self.state == 'default': + if self.description: + self.proposed["description"] = "" + if is_admin_state_enable(self.intf_type) and self.admin_state: + self.proposed["admin_state"] = self.admin_state + if is_portswitch_enalbe(self.intf_type) and self.mode: + self.proposed["mode"] = self.mode + + def get_existing(self): + """get_existing""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + if is_admin_state_enable(self.intf_type): + self.existing["admin_state"] = self.intf_info["ifAdminStatus"] + self.existing["description"] = self.intf_info["ifDescr"] + if is_portswitch_enalbe(self.intf_type): + if self.intf_info["isL2SwitchPort"] == "true": + self.existing["mode"] = "layer2" + else: + self.existing["mode"] = "layer3" + + if self.intfs_info: + intfs = self.intfs_info.get(self.intf_type.lower()) + for intf in intfs: + intf_para = dict() + if intf["ifAdminStatus"]: + intf_para["admin_state"] = intf["ifAdminStatus"] + intf_para["description"] = intf["ifDescr"] + + if intf["isL2SwitchPort"] == "true": + intf_para["mode"] = "layer2" + else: + intf_para["mode"] = "layer3" + self.existing[intf["ifName"]] = intf_para + + def get_end_state(self): + """get_end_state""" + if self.interface: + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + if is_admin_state_enable(self.intf_type): + self.end_state["admin_state"] = end_info["ifAdminStatus"] + self.end_state["description"] = end_info["ifDescr"] + if is_portswitch_enalbe(self.intf_type): + if end_info["isL2SwitchPort"] == "true": + self.end_state["mode"] = "layer2" + else: + self.end_state["mode"] = "layer3" + + if self.interface_type: + end_info = self.get_interfaces_dict() + intfs = end_info.get(self.intf_type.lower()) + for intf in intfs: + intf_para = dict() + if intf["ifAdminStatus"]: + intf_para["admin_state"] = intf["ifAdminStatus"] + intf_para["description"] = intf["ifDescr"] + + if intf["isL2SwitchPort"] == "true": + intf_para["mode"] = "layer2" + else: + intf_para["mode"] = "layer3" + self.end_state[intf["ifName"]] = intf_para + + def work(self): + """worker""" + + self.check_params() + + # single interface config + if self.interface: + self.intf_info = self.get_interface_dict(self.interface) + self.get_existing() + if self.state == 'present': + if not self.intf_info: + # create interface + self.create_interface(self.interface, + self.description, + self.admin_state, + self.mode, + self.l2sub) + else: + # merge interface + if self.description or self.admin_state or self.mode: + self.merge_interface(self.interface, + self.description, + self.admin_state, + self.mode) + + elif self.state == 'absent': + if self.intf_info: + # delete interface + self.delete_interface(self.interface) + else: + # interface does not exist + self.module.fail_json( + msg='Error: interface does not exist.') + + else: # default + if not self.intf_info: + # error, interface does not exist + self.module.fail_json( + msg='Error: interface does not exist.') + else: + self.default_interface(self.interface) + + # interface type config + else: + self.intfs_info = self.get_interfaces_dict() + self.get_existing() + if self.state == 'present': + if self.intfs_info.get(self.intf_type.lower()): + if self.description or self.admin_state or self.mode: + self.merge_interfaces(self.intf_type, + self.description, + self.admin_state, + self.mode) + elif self.state == 'absent': + # delete all interface of this type + if self.intfs_info.get(self.intf_type.lower()): + self.delete_interfaces(self.intf_type) + + else: + # set interfaces config to default + if self.intfs_info.get(self.intf_type.lower()): + self.default_interfaces(self.intf_type) + else: + self.module.fail_json( + msg='Error: no interface in this type.') + + self.get_proposed() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + interface=dict(required=False, type='str'), + admin_state=dict(choices=['up', 'down'], required=False), + description=dict(required=False, default=None), + mode=dict(choices=['layer2', 'layer3'], required=False), + interface_type=dict(required=False), + l2sub=dict(required=False, default=False, type='bool'), + state=dict(choices=['absent', 'present', 'default'], + default='present', required=False), + ) + + argument_spec.update(ce_argument_spec) + interface = Interface(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface_ospf.py new file mode 100644 index 00000000..2c724817 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_interface_ospf.py @@ -0,0 +1,793 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_interface_ospf +short_description: Manages configuration of an OSPF interface instanceon HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF interface instanceon HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/10. + required: true + process_id: + description: + - Specifies a process ID. + The value is an integer ranging from 1 to 4294967295. + required: true + area: + description: + - Ospf area associated with this ospf process. + Valid values are a string, formatted as an IP address + (i.e. "0.0.0.0") or as an integer between 1 and 4294967295. + required: true + cost: + description: + - The cost associated with this interface. + Valid values are an integer in the range from 1 to 65535. + hello_interval: + description: + - Time between sending successive hello packets. + Valid values are an integer in the range from 1 to 65535. + dead_interval: + description: + - Time interval an ospf neighbor waits for a hello + packet before tearing down adjacencies. Valid values are an + integer in the range from 1 to 235926000. + silent_interface: + description: + - Setting to true will prevent this interface from receiving + HELLO packets. Valid values are 'true' and 'false'. + type: bool + default: 'no' + auth_mode: + description: + - Specifies the authentication type. + choices: ['none', 'null', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'] + auth_text_simple: + description: + - Specifies a password for simple authentication. + The value is a string of 1 to 8 characters. + auth_key_id: + description: + - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5. + Valid value is an integer is in the range from 1 to 255. + auth_text_md5: + description: + - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication. + The value is a string of 1 to 255 case-sensitive characters, spaces not supported. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Eth_trunk module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Enables OSPF and sets the cost on an interface + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + cost: 100 + provider: '{{ cli }}' + + - name: Sets the dead interval of the OSPF neighbor + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + dead_interval: 100 + provider: '{{ cli }}' + + - name: Sets the interval for sending Hello packets on an interface + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + hello_interval: 2 + provider: '{{ cli }}' + + - name: Disables an interface from receiving and sending OSPF packets + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + silent_interface: true + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100", "interface": "10GE1/0/30", "cost": "100"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100", "interface": "10GE1/0/30", + "cost": "100", "dead_interval": "40", "hello_interval": "10", + "silent_interface": "false", "auth_mode": "none"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/30", + "ospf enable 1 area 0.0.0.100", + "ospf cost 100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF = """ + + + + + + %s + + + + + %s + + + %s + + + + + + + + + + + + + + + + + + +""" + +CE_NC_XML_BUILD_PROCESS = """ + + + + + + %s + + + %s + %s + + + + + + + +""" + +CE_NC_XML_BUILD_MERGE_INTF = """ + + + %s + + +""" + +CE_NC_XML_BUILD_DELETE_INTF = """ + + + %s + + +""" +CE_NC_XML_SET_IF_NAME = """ + %s +""" + +CE_NC_XML_SET_HELLO = """ + %s +""" + +CE_NC_XML_SET_DEAD = """ + %s +""" + +CE_NC_XML_SET_SILENT = """ + %s +""" + +CE_NC_XML_SET_COST = """ + %s +""" + +CE_NC_XML_SET_AUTH_MODE = """ + %s +""" + + +CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """ + %s +""" + +CE_NC_XML_SET_AUTH_MD5 = """ + %s + %s +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if not addr: + return False + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class InterfaceOSPF(object): + """ + Manages configuration of an OSPF interface instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.interface = self.module.params['interface'] + self.process_id = self.module.params['process_id'] + self.area = self.module.params['area'] + self.cost = self.module.params['cost'] + self.hello_interval = self.module.params['hello_interval'] + self.dead_interval = self.module.params['dead_interval'] + self.silent_interface = self.module.params['silent_interface'] + self.auth_mode = self.module.params['auth_mode'] + self.auth_text_simple = self.module.params['auth_text_simple'] + self.auth_key_id = self.module.params['auth_key_id'] + self.auth_text_md5 = self.module.params['auth_text_md5'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def netconf_set_config(self, xml_str, xml_name): + """netconf set config""" + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_area_ip(self): + """convert integer to ip address""" + + if not self.area.isdigit(): + return self.area + + addr_int = ['0'] * 4 + addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF) + addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF) + addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF) + addr_int[3] = str(int(self.area) & 0xFF) + + return '.'.join(addr_int) + + def get_ospf_dict(self): + """ get one ospf attributes dict.""" + + ospf_info = dict() + conf_str = CE_NC_GET_OSPF % ( + self.process_id, self.get_area_ip(), self.interface) + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return ospf_info + + xml_str = rcv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get process base info + root = ElementTree.fromstring(xml_str) + ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite") + if not ospfsite: + self.module.fail_json(msg="Error: ospf process does not exist.") + + for site in ospfsite: + if site.tag in ["processId", "routerId", "vrfName"]: + ospf_info[site.tag] = site.text + + # get areas info + ospf_info["areaId"] = "" + areas = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area") + if areas: + for area in areas: + if area.tag == "areaId": + ospf_info["areaId"] = area.text + break + + # get interface info + ospf_info["interface"] = dict() + intf = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area/interfaces/interface") + if intf: + for attr in intf: + if attr.tag in ["ifName", "networkType", + "helloInterval", "deadInterval", + "silentEnable", "configCost", + "authenticationMode", "authTextSimple", + "keyId", "authTextMd5"]: + ospf_info["interface"][attr.tag] = attr.text + + return ospf_info + + def set_ospf_interface(self): + """set interface ospf enable, and set its ospf attributes""" + + xml_intf = CE_NC_XML_SET_IF_NAME % self.interface + + # ospf view + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + if self.silent_interface: + xml_intf += CE_NC_XML_SET_SILENT % str(self.silent_interface).lower() + if self.silent_interface: + self.updates_cmd.append("silent-interface %s" % self.interface) + else: + self.updates_cmd.append("undo silent-interface %s" % self.interface) + + # interface view + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("ospf enable %s area %s" % ( + self.process_id, self.get_area_ip())) + if self.cost: + xml_intf += CE_NC_XML_SET_COST % self.cost + self.updates_cmd.append("ospf cost %s" % self.cost) + if self.hello_interval: + xml_intf += CE_NC_XML_SET_HELLO % self.hello_interval + self.updates_cmd.append("ospf timer hello %s" % + self.hello_interval) + if self.dead_interval: + xml_intf += CE_NC_XML_SET_DEAD % self.dead_interval + self.updates_cmd.append("ospf timer dead %s" % self.dead_interval) + if self.auth_mode: + xml_intf += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo ospf authentication-mode") + else: + self.updates_cmd.append("ospf authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_intf += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s" + % (self.auth_mode, self.auth_text_simple)) + elif self.auth_mode in ["hmac-sha256", "md5", "hmac-md5"] and self.auth_key_id: + xml_intf += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s %s" + % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + else: + pass + + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + (CE_NC_XML_BUILD_MERGE_INTF % xml_intf)) + self.netconf_set_config(xml_str, "SET_INTERFACE_OSPF") + self.changed = True + + def merge_ospf_interface(self): + """merge interface ospf attributes""" + + intf_dict = self.ospf_info["interface"] + + # ospf view + xml_ospf = "" + if intf_dict.get("silentEnable") != str(self.silent_interface).lower(): + xml_ospf += CE_NC_XML_SET_SILENT % str(self.silent_interface).lower() + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + if self.silent_interface: + self.updates_cmd.append("silent-interface %s" % self.interface) + else: + self.updates_cmd.append("undo silent-interface %s" % self.interface) + + # interface view + xml_intf = "" + self.updates_cmd.append("interface %s" % self.interface) + if self.cost and intf_dict.get("configCost") != self.cost: + xml_intf += CE_NC_XML_SET_COST % self.cost + self.updates_cmd.append("ospf cost %s" % self.cost) + if self.hello_interval and intf_dict.get("helloInterval") != self.hello_interval: + xml_intf += CE_NC_XML_SET_HELLO % self.hello_interval + self.updates_cmd.append("ospf timer hello %s" % + self.hello_interval) + if self.dead_interval and intf_dict.get("deadInterval") != self.dead_interval: + xml_intf += CE_NC_XML_SET_DEAD % self.dead_interval + self.updates_cmd.append("ospf timer dead %s" % self.dead_interval) + if self.auth_mode: + # NOTE: for security, authentication config will always be update + xml_intf += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo ospf authentication-mode") + else: + self.updates_cmd.append("ospf authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_intf += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s" + % (self.auth_mode, self.auth_text_simple)) + elif self.auth_mode in ["hmac-sha256", "md5", "hmac-md5"] and self.auth_key_id: + xml_intf += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s %s" + % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + else: + pass + if not xml_intf: + self.updates_cmd.pop() # remove command: interface + + if not xml_ospf and not xml_intf: + return + + xml_sum = CE_NC_XML_SET_IF_NAME % self.interface + xml_sum += xml_ospf + xml_intf + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + (CE_NC_XML_BUILD_MERGE_INTF % xml_sum)) + self.netconf_set_config(xml_str, "MERGE_INTERFACE_OSPF") + self.changed = True + + def unset_ospf_interface(self): + """set interface ospf disable, and all its ospf attributes will be removed""" + + intf_dict = self.ospf_info["interface"] + xml_sum = "" + xml_intf = CE_NC_XML_SET_IF_NAME % self.interface + if intf_dict.get("silentEnable") == "true": + xml_sum += CE_NC_XML_BUILD_MERGE_INTF % ( + xml_intf + (CE_NC_XML_SET_SILENT % "false")) + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + self.updates_cmd.append( + "undo silent-interface %s" % self.interface) + + xml_sum += CE_NC_XML_BUILD_DELETE_INTF % xml_intf + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + xml_sum) + self.netconf_set_config(xml_str, "DELETE_INTERFACE_OSPF") + self.updates_cmd.append("undo ospf cost") + self.updates_cmd.append("undo ospf timer hello") + self.updates_cmd.append("undo ospf timer dead") + self.updates_cmd.append("undo ospf authentication-mode") + self.updates_cmd.append("undo ospf enable %s area %s" % ( + self.process_id, self.get_area_ip())) + self.changed = True + + def check_params(self): + """Check all input params""" + + self.interface = self.interface.replace(" ", "").upper() + + # interface check + if not get_interface_type(self.interface): + self.module.fail_json(msg="Error: interface is invalid.") + + # process_id check + if not self.process_id.isdigit(): + self.module.fail_json(msg="Error: process_id is not digit.") + if int(self.process_id) < 1 or int(self.process_id) > 4294967295: + self.module.fail_json(msg="Error: process_id must be an integer between 1 and 4294967295.") + + # area check + if self.area.isdigit(): + if int(self.area) < 0 or int(self.area) > 4294967295: + self.module.fail_json(msg="Error: area id (Integer) must be between 0 and 4294967295.") + else: + if not is_valid_v4addr(self.area): + self.module.fail_json(msg="Error: area id is invalid.") + + # area authentication check + if self.state == "present": + if self.auth_mode: + if self.auth_mode == "simple": + if self.auth_text_simple and len(self.auth_text_simple) > 8: + self.module.fail_json( + msg="Error: auth_text_simple is not in the range from 1 to 8.") + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and not self.auth_text_md5: + self.module.fail_json( + msg='Error: auth_key_id and auth_text_md5 should be set at the same time.') + if not self.auth_key_id and self.auth_text_md5: + self.module.fail_json( + msg='Error: auth_key_id and auth_text_md5 should be set at the same time.') + if self.auth_key_id: + if not self.auth_key_id.isdigit(): + self.module.fail_json( + msg="Error: auth_key_id is not digit.") + if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255: + self.module.fail_json( + msg="Error: auth_key_id is not in the range from 1 to 255.") + if self.auth_text_md5 and len(self.auth_text_md5) > 255: + self.module.fail_json( + msg="Error: auth_text_md5 is not in the range from 1 to 255.") + # cost check + if self.cost: + if not self.cost.isdigit(): + self.module.fail_json(msg="Error: cost is not digit.") + if int(self.cost) < 1 or int(self.cost) > 65535: + self.module.fail_json( + msg="Error: cost is not in the range from 1 to 65535") + + # hello_interval check + if self.hello_interval: + if not self.hello_interval.isdigit(): + self.module.fail_json( + msg="Error: hello_interval is not digit.") + if int(self.hello_interval) < 1 or int(self.hello_interval) > 65535: + self.module.fail_json( + msg="Error: hello_interval is not in the range from 1 to 65535") + + # dead_interval check + if self.dead_interval: + if not self.dead_interval.isdigit(): + self.module.fail_json(msg="Error: dead_interval is not digit.") + if int(self.dead_interval) < 1 or int(self.dead_interval) > 235926000: + self.module.fail_json( + msg="Error: dead_interval is not in the range from 1 to 235926000") + + def get_proposed(self): + """get proposed info""" + + self.proposed["interface"] = self.interface + self.proposed["process_id"] = self.process_id + self.proposed["area"] = self.get_area_ip() + self.proposed["cost"] = self.cost + self.proposed["hello_interval"] = self.hello_interval + self.proposed["dead_interval"] = self.dead_interval + self.proposed["silent_interface"] = self.silent_interface + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.auth_mode == "simple": + self.proposed["auth_text_simple"] = self.auth_text_simple + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + self.proposed["auth_key_id"] = self.auth_key_id + self.proposed["auth_text_md5"] = self.auth_text_md5 + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.ospf_info: + return + + if self.ospf_info["interface"]: + self.existing["interface"] = self.interface + self.existing["cost"] = self.ospf_info["interface"].get("configCost") + self.existing["hello_interval"] = self.ospf_info["interface"].get("helloInterval") + self.existing["dead_interval"] = self.ospf_info["interface"].get("deadInterval") + self.existing["silent_interface"] = self.ospf_info["interface"].get("silentEnable") + self.existing["auth_mode"] = self.ospf_info["interface"].get("authenticationMode") + self.existing["auth_text_simple"] = self.ospf_info["interface"].get("authTextSimple") + self.existing["auth_key_id"] = self.ospf_info["interface"].get("keyId") + self.existing["auth_text_md5"] = self.ospf_info["interface"].get("authTextMd5") + self.existing["process_id"] = self.ospf_info["processId"] + self.existing["area"] = self.ospf_info["areaId"] + + def get_end_state(self): + """get end state info""" + + ospf_info = self.get_ospf_dict() + if not ospf_info: + return + + if ospf_info["interface"]: + self.end_state["interface"] = self.interface + self.end_state["cost"] = ospf_info["interface"].get("configCost") + self.end_state["hello_interval"] = ospf_info["interface"].get("helloInterval") + self.end_state["dead_interval"] = ospf_info["interface"].get("deadInterval") + self.end_state["silent_interface"] = ospf_info["interface"].get("silentEnable") + self.end_state["auth_mode"] = ospf_info["interface"].get("authenticationMode") + self.end_state["auth_text_simple"] = ospf_info["interface"].get("authTextSimple") + self.end_state["auth_key_id"] = ospf_info["interface"].get("keyId") + self.end_state["auth_text_md5"] = ospf_info["interface"].get("authTextMd5") + self.end_state["process_id"] = ospf_info["processId"] + self.end_state["area"] = ospf_info["areaId"] + + def work(self): + """worker""" + + self.check_params() + self.ospf_info = self.get_ospf_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.ospf_info or not self.ospf_info["interface"]: + # create ospf area and set interface config + self.set_ospf_interface() + else: + # merge interface ospf area config + self.merge_ospf_interface() + else: + if self.ospf_info and self.ospf_info["interface"]: + # delete interface ospf area config + self.unset_ospf_interface() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + process_id=dict(required=True, type='str'), + area=dict(required=True, type='str'), + cost=dict(required=False, type='str'), + hello_interval=dict(required=False, type='str'), + dead_interval=dict(required=False, type='str'), + silent_interface=dict(required=False, default=False, type='bool'), + auth_mode=dict(required=False, + choices=['none', 'null', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'), + auth_text_simple=dict(required=False, type='str', no_log=True), + auth_key_id=dict(required=False, type='str'), + auth_text_md5=dict(required=False, type='str', no_log=True), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = InterfaceOSPF(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ip_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ip_interface.py new file mode 100644 index 00000000..149a427d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ip_interface.py @@ -0,0 +1,735 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ip_interface +short_description: Manages L3 attributes for IPv4 and IPv6 interfaces on HUAWEI CloudEngine switches. +description: + - Manages Layer 3 attributes for IPv4 and IPv6 interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - Interface must already be a L3 port when using this module. + - Logical interfaces (loopback, vlanif) must be created first. + - C(mask) must be inserted in decimal format (i.e. 24) for + both IPv6 and IPv4. + - A single interface can have multiple IPv6 configured. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/22, vlanif10. + required: true + addr: + description: + - IPv4 or IPv6 Address. + mask: + description: + - Subnet mask for IPv4 or IPv6 Address in decimal format. + version: + description: + - IP address version. + default: v4 + choices: ['v4','v6'] + ipv4_type: + description: + - Specifies an address type. + The value is an enumerated type. + main, primary IP address. + sub, secondary IP address. + default: main + choices: ['main','sub'] + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Ip_interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure ipv4 address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v4 + state: present + addr: 20.20.20.20 + mask: 24 + provider: '{{ cli }}' + + - name: Ensure ipv4 secondary address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v4 + state: present + addr: 30.30.30.30 + mask: 24 + ipv4_type: sub + provider: '{{ cli }}' + + - name: Ensure ipv6 is enabled on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v6 + state: present + provider: '{{ cli }}' + + - name: Ensure ipv6 address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v6 + state: present + addr: 2001::db8:800:200c:cccb + mask: 64 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addr": "20.20.20.20", "interface": "10GE1/0/22", "mask": "24"} +existing: + description: k/v pairs of existing IP attributes on the interface + returned: always + type: dict + sample: {"ipv4": [{"ifIpAddr": "11.11.11.11", "subnetMask": "255.255.0.0", "addrType": "main"}], + "interface": "10GE1/0/22"} +end_state: + description: k/v pairs of IP attributes after module execution + returned: always + type: dict + sample: {"ipv4": [{"ifIpAddr": "20.20.20.20", "subnetMask": "255.255.255.0", "addrType": "main"}], + "interface": "10GE1/0/22"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/22", "ip address 20.20.20.20 24"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_INTF = """ + + + + + %s + + + + + + + + + +""" + +CE_NC_ADD_IPV4 = """ + + + + + %s + + + + %s + %s + %s + + + + + + + +""" + +CE_NC_MERGE_IPV4 = """ + + + + + %s + + + + %s + %s + main + + + %s + %s + main + + + + + + + +""" + + +CE_NC_DEL_IPV4 = """ + + + + + %s + + + + %s + %s + %s + + + + + + + +""" + +CE_NC_ADD_IPV6 = """ + + + + + %s + + + + %s + %s + global + + + + + + + +""" + +CE_NC_DEL_IPV6 = """ + + + + + %s + + + + %s + %s + global + + + + + + + +""" + +CE_NC_MERGE_IPV6_ENABLE = """ + + + + + %s + + %s + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if not addr: + return False + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class IpInterface(object): + """ + Manages L3 attributes for IPv4 and IPv6 interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info] + self.interface = self.module.params['interface'] + self.addr = self.module.params['addr'] + self.mask = self.module.params['mask'] + self.version = self.module.params['version'] + self.ipv4_type = self.module.params['ipv4_type'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + # interface info + self.intf_info = dict() + self.intf_type = None + + def __init_module__(self): + """ init module """ + + required_if = [("version", "v4", ("addr", "mask"))] + required_together = [("addr", "mask")] + self.module = AnsibleModule( + argument_spec=self.spec, + required_if=required_if, + required_together=required_together, + supports_check_mode=True + ) + + def netconf_set_config(self, xml_str, xml_name): + """ netconf set config """ + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_INTF % ifname + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return intf_info + + # get interface base info + intf = re.findall( + r'.*(.*).*\s*' + r'(.*).*', rcv_xml) + + if intf: + intf_info = dict(ifName=intf[0][0], + isL2SwitchPort=intf[0][1]) + + # get interface ipv4 address info + ipv4_info = re.findall( + r'.*(.*).*\s*(.*)' + r'.*\s*(.*).*', rcv_xml) + intf_info["am4CfgAddr"] = list() + for info in ipv4_info: + intf_info["am4CfgAddr"].append( + dict(ifIpAddr=info[0], subnetMask=info[1], addrType=info[2])) + + # get interface ipv6 address info + ipv6_info = re.findall( + r'.*.*\s*(.*).*', rcv_xml) + if not ipv6_info: + self.module.fail_json(msg='Error: Fail to get interface %s IPv6 state.' % self.interface) + else: + intf_info["enableFlag"] = ipv6_info[0] + + # get interface ipv6 enable info + ipv6_info = re.findall( + r'.*(.*).*\s*(.*)' + r'.*\s*(.*).*', rcv_xml) + + intf_info["am6CfgAddr"] = list() + for info in ipv6_info: + intf_info["am6CfgAddr"].append( + dict(ifIp6Addr=info[0], addrPrefixLen=info[1], addrType6=info[2])) + + return intf_info + + def convert_len_to_mask(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='Error: IPv4 ipaddress mask length is invalid.') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def is_ipv4_exist(self, addr, maskstr, ipv4_type): + """"Check IPv4 address exist""" + + addrs = self.intf_info["am4CfgAddr"] + if not addrs: + return False + + for address in addrs: + if address["ifIpAddr"] == addr: + return address["subnetMask"] == maskstr and address["addrType"] == ipv4_type + return False + + def get_ipv4_main_addr(self): + """get IPv4 main address""" + + addrs = self.intf_info["am4CfgAddr"] + if not addrs: + return None + + for address in addrs: + if address["addrType"] == "main": + return address + + return None + + def is_ipv6_exist(self, addr, masklen): + """Check IPv6 address exist""" + + addrs = self.intf_info["am6CfgAddr"] + if not addrs: + return False + + for address in addrs: + if address["ifIp6Addr"] == addr.upper(): + if address["addrPrefixLen"] == masklen and address["addrType6"] == "global": + return True + else: + self.module.fail_json( + msg="Error: Input IPv6 address or mask is invalid.") + + return False + + def set_ipv4_addr(self, ifname, addr, mask, ipv4_type): + """Set interface IPv4 address""" + + if not addr or not mask or not type: + return + + maskstr = self.convert_len_to_mask(mask) + if self.state == "present": + if not self.is_ipv4_exist(addr, maskstr, ipv4_type): + # primary IP address + if ipv4_type == "main": + main_addr = self.get_ipv4_main_addr() + if not main_addr: + # no ipv4 main address in this interface + xml_str = CE_NC_ADD_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "ADD_IPV4_ADDR") + else: + # remove old address and set new + xml_str = CE_NC_MERGE_IPV4 % (ifname, main_addr["ifIpAddr"], + main_addr["subnetMask"], + addr, maskstr) + self.netconf_set_config(xml_str, "MERGE_IPV4_ADDR") + # secondary IP address + else: + xml_str = CE_NC_ADD_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "ADD_IPV4_ADDR") + + self.updates_cmd.append("interface %s" % ifname) + if ipv4_type == "main": + self.updates_cmd.append("ip address %s %s" % (addr, maskstr)) + else: + self.updates_cmd.append("ip address %s %s sub" % (addr, maskstr)) + self.changed = True + else: + if self.is_ipv4_exist(addr, maskstr, ipv4_type): + xml_str = CE_NC_DEL_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "DEL_IPV4_ADDR") + self.updates_cmd.append("interface %s" % ifname) + if ipv4_type == "main": + self.updates_cmd.append("undo ip address %s %s" % (addr, maskstr)) + else: + self.updates_cmd.append("undo ip address %s %s sub" % (addr, maskstr)) + self.changed = True + + def set_ipv6_addr(self, ifname, addr, mask): + """Set interface IPv6 address""" + + if not addr or not mask: + return + + if self.state == "present": + self.updates_cmd.append("interface %s" % ifname) + if self.intf_info["enableFlag"] == "false": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "true") + self.netconf_set_config(xml_str, "SET_IPV6_ENABLE") + self.updates_cmd.append("ipv6 enable") + self.changed = True + + if not self.is_ipv6_exist(addr, mask): + xml_str = CE_NC_ADD_IPV6 % (ifname, addr, mask) + self.netconf_set_config(xml_str, "ADD_IPV6_ADDR") + + self.updates_cmd.append("ipv6 address %s %s" % (addr, mask)) + self.changed = True + + if not self.changed: + self.updates_cmd.pop() + else: + if self.is_ipv6_exist(addr, mask): + xml_str = CE_NC_DEL_IPV6 % (ifname, addr, mask) + self.netconf_set_config(xml_str, "DEL_IPV6_ADDR") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append( + "undo ipv6 address %s %s" % (addr, mask)) + self.changed = True + + def set_ipv6_enable(self, ifname): + """Set interface IPv6 enable""" + + if self.state == "present": + if self.intf_info["enableFlag"] == "false": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "true") + self.netconf_set_config(xml_str, "SET_IPV6_ENABLE") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("ipv6 enable") + self.changed = True + else: + if self.intf_info["enableFlag"] == "true": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "false") + self.netconf_set_config(xml_str, "SET_IPV6_DISABLE") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("undo ipv6 enable") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check interface type + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + # ipv4 addr and mask check + if self.version == "v4": + if not is_valid_v4addr(self.addr): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.addr) + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: mask is invalid.') + if int(self.mask) > 32 or int(self.mask) < 1: + self.module.fail_json( + msg='Error: mask must be an integer between 1 and 32.') + + # ipv6 mask check + if self.version == "v6": + if self.addr: + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: mask is invalid.') + if int(self.mask) > 128 or int(self.mask) < 1: + self.module.fail_json( + msg='Error: mask must be an integer between 1 and 128.') + + # interface and layer3 check + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: interface %s does not exist.' % self.interface) + + if self.intf_info["isL2SwitchPort"] == "true": + self.module.fail_json(msg='Error: interface %s is layer2.' % self.interface) + + def get_proposed(self): + """get proposed info""" + + self.proposed["state"] = self.state + self.proposed["addr"] = self.addr + self.proposed["mask"] = self.mask + self.proposed["ipv4_type"] = self.ipv4_type + self.proposed["version"] = self.version + self.proposed["interface"] = self.interface + + def get_existing(self): + """get existing info""" + + self.existing["interface"] = self.interface + self.existing["ipv4addr"] = self.intf_info["am4CfgAddr"] + self.existing["ipv6addr"] = self.intf_info["am6CfgAddr"] + self.existing["ipv6enalbe"] = self.intf_info["enableFlag"] + + def get_end_state(self): + """get end state info""" + + intf_info = self.get_interface_dict(self.interface) + self.end_state["interface"] = self.interface + self.end_state["ipv4addr"] = intf_info["am4CfgAddr"] + self.end_state["ipv6addr"] = intf_info["am6CfgAddr"] + self.end_state["ipv6enalbe"] = intf_info["enableFlag"] + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.version == "v4": + self.set_ipv4_addr(self.interface, self.addr, self.mask, self.ipv4_type) + else: + if not self.addr and not self.mask: + self.set_ipv6_enable(self.interface) + else: + self.set_ipv6_addr(self.interface, self.addr, self.mask) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True), + addr=dict(required=False), + version=dict(required=False, choices=['v4', 'v6'], + default='v4'), + mask=dict(type='str', required=False), + ipv4_type=dict(required=False, choices=['main', 'sub'], default='main'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = IpInterface(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_instance.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_instance.py new file mode 100644 index 00000000..db9a7efc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_instance.py @@ -0,0 +1,327 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: ce_is_is_instance +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis process id configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id on HUAWEI CloudEngine devices. +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + instance_id: + description: + - Specifies the id of a isis process.The value is a number of 1 to 4294967295. + required: true + type: int + vpn_name: + description: + - VPN Instance, associate the VPN instance with the corresponding IS-IS process. + type: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + type: str + choices: ['present', 'absent'] +''' + +EXAMPLES = r''' + - name: Set isis process + community.network.ce_is_is_instance: + instance_id: 3 + state: present + + - name: Unset isis process + community.network.ce_is_is_instance: + instance_id: 3 + state: absent + + - name: Check isis process + community.network.ce_is_is_instance: + instance_id: 4294967296 + state: present + + - name: Set vpn name + community.network.ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1 + state: present + + - name: Check vpn name + community.network.ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1234567896321452212221556asdasdasdasdsadvdv + state: present +''' + +RETURN = r''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "instance_id": 1, + "vpn_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "instance_id": 1, + "vpn_name": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "isis 1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INSTANCE = """ + + + %s + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +class ISIS_Instance(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.vpn_name = self.module.params['vpn_name'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_isis_dict(self): + """isis config dict""" + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INSTANCE % self.instance_id)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get isis info + glb = root.find("isiscomm/isSites/isSite") + if glb: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures isis""" + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + + if self.state == "present": + xml_str = "%s" % self.instance_id + self.updates_cmd.append("isis %s" % self.instance_id) + + if self.vpn_name: + xml_str += "%s" % self.vpn_name + self.updates_cmd.append("vpn-instance %s" % self.vpn_name) + else: + # absent + if self.instance_id and str(self.instance_id) == instance.get("instanceId"): + xml_str = "%s" % self.instance_id + self.updates_cmd.append("undo isis %s" % self.instance_id) + + if self.state == "present": + return '' + xml_str + '' + else: + if xml_str: + return '' + xml_str + '' + + def netconf_load_config(self, xml_str): + """load isis config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check instance id + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check vpn_name + if self.vpn_name: + if not is_valid_ip_vpn(self.vpn_name): + self.module.fail_json(msg="Error: Session vpn_name is invalid.") + + def get_proposed(self): + """get proposed info""" + # base config + self.proposed["instance_id"] = self.instance_id + self.proposed["vpn_name"] = self.vpn_name + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + + self.end_state["instance"] = isis_dict.get("instance") + + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + cfg_str = self.config_session() + if cfg_str: + xml_str += cfg_str + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + argument_spec = dict( + instance_id=dict(required=True, type='int'), + vpn_name=dict(required=False, type='str'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_Instance(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_interface.py new file mode 100644 index 00000000..0574cfe5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_interface.py @@ -0,0 +1,785 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_is_is_interface +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis interface configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id on HUAWEI CloudEngine devices. +notes: + - Interface must already be a L3 port when using this module. + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + instance_id: + description: + - Specifies the id of a isis process. + The value is a number of 1 to 4294967295. + required: true + type: int + ifname: + description: + - A L3 interface. + required: true + type: str + leveltype: + description: + - level type for three types. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + level1dispriority: + description: + - the dispriority of the level1. + The value is a number of 1 to 127. + type: int + level2dispriority: + description: + - the dispriority of the level1. + The value is a number of 1 to 127. + type: int + silentenable: + description: + - enable the interface can send isis message. + The value is a bool type. + type: bool + silentcost: + description: + - Specifies whether the routing cost of the silent interface is 0. + The value is a bool type. + type: bool + typep2penable: + description: + - Simulate the network type of the interface as P2P. + The value is a bool type. + type: bool + snpacheck: + description: + - Enable SNPA check for LSPs and SNPs. + The value is a bool type. + type: bool + p2pnegotiationmode: + description: + - Set the P2P neighbor negotiation type. + type: str + choices: ['2_way', '3_way', '3_wayonly'] + p2ppeeripignore: + description: + - When the P2P hello packet is received, no IP address check is performed. + The value is a bool type. + type: bool + ppposicpcheckenable: + description: + - Interface for setting PPP link protocol to check OSICP negotiation status. + The value is a bool type. + type: bool + level1cost: + description: + - Specifies the link cost of the interface when performing Level-1 SPF calculation. + The value is a number of 0 to 16777215. + type: int + level2cost: + description: + - Specifies the link cost of the interface when performing Level-2 SPF calculation. + The value is a number of 0 to 16777215. + type: int + bfdstaticen: + description: + - Configure static BFD on a specific interface enabled with ISIS. + The value is a bool type. + type: bool + bfdblocken: + description: + - Blocking interfaces to dynamically create BFD features. + The value is a bool type. + type: bool + state: + description: + - Determines whether the config should be present or not on the device. + type: str + default: 'present' + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + - name: "create vlan and config vlanif" + ce_config: + lines: 'vlan {{ test_vlan_id }},quit,interface {{test_intf_vlanif}},ip address {{test_vlanif_ip}} 24' + match: none + + - name: "create eth-trunk and config eth-trunk" + ce_config: + lines: 'interface {{test_intf_trunk}},undo portswitch,ip address {{test_trunk_ip}} 24' + match: none + + - name: "create vpn instance" + ce_config: + lines: 'ip vpn-instance {{test_vpn}},ipv4-family' + match: none + + - name: Set isis circuit-level + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + leveltype: level_1_2 + state: present + + - name: Set isis level1dispriority + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + level1dispriority: 0 + state: present + + - name: Set isis level2dispriority + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + level2dispriority: 0 + state: present + + - name: Set isis silentenable + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + silentenable: true + state: present + + - name: Set vpn name + ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1 + state: present +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "addr_type": null, + "create_type": null, + "dest_addr": null, + "out_if_name": "10GE1/0/1", + "session_name": "bfd_l2link", + "src_addr": null, + "state": "present", + "use_default_ip": true, + "vrf_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INTERFACE = """ + + + %s + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_INTERFACE = """ + + + %s + + + %s + + + + +""" + +CE_NC_DELETE_ISIS_INTERFACE = """ + + + %s + + + %s + + + + +""" + +CE_NC_GET_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_ip_addr(ipaddr): + """check ip address, Supports IPv4 and IPv6""" + + if not ipaddr or '\x00' in ipaddr: + return False + + try: + res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_NUMERICHOST) + return bool(res) + except socket.gaierror: + err = sys.exc_info()[1] + if err.args[0] == socket.EAI_NONAME: + return False + raise + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +class ISIS_Instance(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.ifname = self.module.params['ifname'] + self.leveltype = self.module.params['leveltype'] + self.level1dispriority = self.module.params['level1dispriority'] + self.level2dispriority = self.module.params['level2dispriority'] + self.silentenable = self.module.params['silentenable'] + self.silentcost = self.module.params['silentcost'] + self.typep2penable = self.module.params['typep2penable'] + self.snpacheck = self.module.params['snpacheck'] + self.p2pnegotiationmode = self.module.params['p2pnegotiationmode'] + self.p2ppeeripignore = self.module.params['p2ppeeripignore'] + self.ppposicpcheckenable = self.module.params['ppposicpcheckenable'] + self.level1cost = self.module.params['level1cost'] + self.level2cost = self.module.params['level2cost'] + self.bfdstaticen = self.module.params['bfdstaticen'] + self.bfdblocken = self.module.params['bfdblocken'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + mutually_exclusive = [["level1dispriority", "level2dispriority"], + ["level1cost", "level2cost"]] + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def get_isis_dict(self): + """bfd config dict""" + + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INTERFACE % self.instance_id)) + if self.bfdstaticen or self.bfdblocken: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_BFDINTERFACE % self.instance_id)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # + glb = root.find("isiscomm/isSites/isSite/isCircuits/isCircuit") + if self.bfdstaticen or self.bfdblocken: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isCircMts/isCircMt") + if glb: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + if self.ifname: + xml_str = "%s" % self.ifname + self.updates_cmd.append("interface %s" % self.ifname) + if self.state == "present": + self.updates_cmd.append("isis enable %s" % self.instance_id) + + if self.leveltype: + if self.leveltype == "level_1": + xml_str += "level_1" + self.updates_cmd.append("isis circuit-level level-1") + elif self.leveltype == "level_2": + xml_str += "level_2" + self.updates_cmd.append("isis circuit-level level-2") + elif self.leveltype == "level_1_2": + xml_str += "level_1_2" + self.updates_cmd.append("isis circuit-level level-1-2") + if self.level1dispriority is not None: + xml_str += "%s" % self.level1dispriority + self.updates_cmd.append("isis dis-priority %s level-1" % self.level1dispriority) + if self.level2dispriority is not None: + xml_str += "%s" % self.level2dispriority + self.updates_cmd.append("isis dis-priority %s level-2" % self.level2dispriority) + if self.p2pnegotiationmode: + if self.p2pnegotiationmode == "2_way": + xml_str += "2_way" + self.updates_cmd.append("isis ppp-negotiation 2-way") + elif self.p2pnegotiationmode == "3_way": + xml_str += "3_way" + self.updates_cmd.append("isis ppp-negotiation 3-way") + elif self.p2pnegotiationmode == "3_wayonly": + xml_str += "3_wayonly" + self.updates_cmd.append("isis ppp-negotiation only") + if self.level1cost is not None: + xml_str += "%s" % self.level1cost + self.updates_cmd.append("isis cost %s level-1" % self.level1cost) + if self.level2cost is not None: + xml_str += "%s" % self.level2cost + self.updates_cmd.append("isis cost %s level-2" % self.level2cost) + + else: + # absent + self.updates_cmd.append("undo isis enable") + if self.leveltype and self.leveltype == instance.get("circuitLevelType"): + xml_str += "level_1_2" + self.updates_cmd.append("undo isis circuit-level") + if self.level1dispriority is not None and self.level1dispriority == instance.get("level1DisPriority"): + xml_str += "64" + self.updates_cmd.append("undo isis dis-priority %s level-1" % self.level1dispriority) + if self.level2dispriority is not None and self.level2dispriority == instance.get("level2dispriority"): + xml_str += "64" + self.updates_cmd.append("undo isis dis-priority %s level-2" % self.level2dispriority) + if self.p2pnegotiationmode and self.p2pnegotiationmode == instance.get("p2pNegotiationMode"): + xml_str += "" + self.updates_cmd.append("undo isis ppp-negotiation") + if self.level1cost is not None and self.level1cost == instance.get("level1Cost"): + xml_str += "" + self.updates_cmd.append("undo isis cost %s level-1" % self.level1cost) + if self.level2cost is not None and self.level2cost == instance.get("level2Cost"): + xml_str += "" + self.updates_cmd.append("undo isis cost %s level-2" % self.level2cost) + + if self.silentenable and instance.get("silentEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis silent") + elif not self.silentenable and instance.get("silentEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis silent") + + if self.silentcost and instance.get("silentCost", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis silent advertise-zero-cost") + elif not self.silentcost and instance.get("silentCost", "false") == "true": + xml_str += "false" + + if self.typep2penable and instance.get("typeP2pEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis circuit-type p2p") + elif not self.typep2penable and instance.get("typeP2pEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis circuit-type") + + if self.snpacheck and instance.get("snpaCheck", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis circuit-type p2p strict-snpa-check") + elif not self.snpacheck and instance.get("snpaCheck", "false") == "true": + xml_str += "false" + + if self.p2ppeeripignore and instance.get("p2pPeerIPIgnore", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis peer-ip-ignore") + elif not self.p2ppeeripignore and instance.get("p2pPeerIPIgnore", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis peer-ip-ignore") + + if self.ppposicpcheckenable and instance.get("pPPOsicpCheckEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis ppp-osicp-check") + elif not self.ppposicpcheckenable and instance.get("pPPOsicpCheckEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis ppp-osicp-check") + if self.bfdstaticen and instance.get("bfdStaticEn", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis bfd static") + elif not self.bfdstaticen and instance.get("bfdStaticEn", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis bfd static") + if self.bfdblocken and instance.get("bfdBlockEn", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis bfd block") + elif not self.bfdblocken and instance.get("bfdBlockEn", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis bfd block") + + if self.state == "present": + if self.bfdstaticen is not None or self.bfdblocken is not None: + return CE_NC_MERGE_ISIS_BFDINTERFACE % (self.instance_id, xml_str) + return CE_NC_MERGE_ISIS_INTERFACE % (self.instance_id, xml_str) + else: + if self.bfdstaticen is not None or self.bfdblocken is not None: + return CE_NC_DELETE_ISIS_BFDINTERFACE % (self.instance_id, xml_str) + return CE_NC_DELETE_ISIS_INTERFACE % (self.instance_id, xml_str) + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check instance id + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check level1dispriority + if self.level1dispriority is not None: + if self.level1dispriority < 0 or self.level1dispriority > 127: + self.module.fail_json(msg="Error: level1dispriority is not ranges from 0 to 127.") + + if self.level2dispriority is not None: + if self.level2dispriority < 0 or self.level2dispriority > 127: + self.module.fail_json(msg="Error: level2dispriority is not ranges from 0 to 127.") + + if self.level1cost is not None: + if self.level1cost < 0 or self.level1cost > 16777215: + self.module.fail_json(msg="Error: level1cost is not ranges from 0 to 16777215.") + + if self.level2cost is not None: + if self.level2cost < 0 or self.level2cost > 16777215: + self.module.fail_json(msg="Error: level2cost is not ranges from 0 to 16777215.") + + def get_proposed(self): + """get proposed info""" + self.proposed["instance_id"] = self.instance_id + self.proposed["ifname"] = self.ifname + self.proposed["leveltype"] = self.leveltype + self.proposed["level1dispriority"] = self.level1dispriority + self.proposed["level2dispriority"] = self.level2dispriority + self.proposed["silentenable"] = self.silentenable + self.proposed["silentcost"] = self.silentcost + self.proposed["typep2penable"] = self.typep2penable + self.proposed["snpacheck"] = self.snpacheck + self.proposed["p2pnegotiationmode"] = self.p2pnegotiationmode + self.proposed["p2ppeeripignore"] = self.p2ppeeripignore + self.proposed["ppposicpcheckenable"] = self.ppposicpcheckenable + self.proposed["level1cost"] = self.level1cost + self.proposed["level2cost"] = self.level2cost + self.proposed["bfdstaticen"] = self.bfdstaticen + self.proposed["bfdblocken"] = self.bfdblocken + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + else: + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + else: + self.end_state["instance"] = isis_dict.get("instance") + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + instance_id=dict(required=True, type='int'), + ifname=dict(required=True, type='str'), + leveltype=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + level1dispriority=dict(required=False, type='int'), + level2dispriority=dict(required=False, type='int'), + silentenable=dict(required=False, type='bool'), + silentcost=dict(required=False, type='bool'), + typep2penable=dict(required=False, type='bool'), + snpacheck=dict(required=False, type='bool'), + p2pnegotiationmode=dict(required=False, type='str', choices=['2_way', '3_way', '3_wayonly']), + p2ppeeripignore=dict(required=False, type='bool'), + ppposicpcheckenable=dict(required=False, type='bool'), + level1cost=dict(required=False, type='int'), + level2cost=dict(required=False, type='int'), + bfdstaticen=dict(required=False, type='bool'), + bfdblocken=dict(required=False, type='bool'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_Instance(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_view.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_view.py new file mode 100644 index 00000000..aa5da948 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_is_is_view.py @@ -0,0 +1,1952 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_is_is_view +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis view configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id + on HUAWEI CloudEngine devices. +options: + coststyle: + description: + - Specifies the cost style. + type: str + choices: ['narrow', 'wide', 'transition', 'ntransition', 'wtransition'] + cost_type: + description: + - Specifies the cost type. + type: str + choices: ['external', 'internal'] + defaultmode: + description: + - Specifies the default mode. + type: str + choices: ['always', 'matchDefault', 'matchAny'] + export_policytype: + description: + - Specifies the default mode. + type: str + choices: ['aclNumOrName', 'ipPrefix', 'routePolicy'] + export_protocol: + description: + - Specifies the export router protocol. + type: str + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all'] + impotr_leveltype: + description: + - Specifies the export router protocol. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + islevel: + description: + - Specifies the isis level. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + level_type: + description: + - Specifies the isis level type. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + penetration_direct: + description: + - Specifies the penetration direct. + type: str + choices: ['level2-level1', 'level1-level2'] + protocol: + description: + - Specifies the protocol. + type: str + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all'] + aclnum_or_name: + description: + - Specifies the acl number or name for isis. + type: str + allow_filter: + description: + - Specifies the alow filter or not. + type: bool + allow_up_down: + description: + - Specifies the alow up or down. + type: bool + autocostenable: + description: + - Specifies the alow auto cost enable. + type: bool + autocostenablecompatible: + description: + - Specifies the alow auto cost enable compatible. + type: bool + avoid_learning: + description: + - Specifies the alow avoid learning. + type: bool + bfd_min_tx: + description: + - Specifies the bfd min sent package. + type: int + bfd_min_rx: + description: + - Specifies the bfd min received package. + type: int + bfd_multiplier_num: + description: + - Specifies the bfd multiplier number. + type: int + cost: + description: + - Specifies the bfd cost. + type: int + description: + description: + - Specifies description of isis. + type: str + enablelevel1tolevel2: + description: + - Enable level1 to level2. + type: bool + export_aclnumorname: + description: + - Specifies export acl number or name. + type: str + export_ipprefix: + description: + - Specifies export ip prefix. + type: str + export_processid: + description: + - Specifies export process id. + type: int + export_routepolicyname: + description: + - Specifies export route policy name. + type: str + import_aclnumorname: + description: + - Specifies import acl number or name. + type: str + import_cost: + description: + - Specifies import cost. + type: int + import_ipprefix: + description: + - Specifies import ip prefix. + type: str + import_route_policy: + description: + - Specifies import route policy. + type: str + import_routepolicy_name: + description: + - Specifies import route policy name. + type: str + import_routepolicyname: + description: + - Specifies import route policy name. + type: str + import_tag: + description: + - Specifies import tag. + type: int + inheritcost: + description: + - Enable inherit cost. + type: bool + instance_id: + description: + - Specifies instance id. + type: int + ip_address: + description: + - Specifies ip address. + type: str + ip_prefix_name: + description: + - Specifies ip prefix name. + type: str + max_load: + description: + - Specifies route max load. + type: int + mode_routepolicyname: + description: + - Specifies the mode of route polic yname. + type: str + mode_tag: + description: + - Specifies the tag of mode. + type: int + netentity: + description: + - Specifies the netentity. + type: str + permitibgp: + description: + - Specifies the permitibgp. + type: bool + processid: + description: + - Specifies the process id. + type: int + relaxspfLimit: + description: + - Specifies enable the relax spf limit. + type: bool + route_policy_name: + description: + - Specifies the route policy name. + type: str + stdbandwidth: + description: + - Specifies the std band width. + type: int + stdlevel1cost: + description: + - Specifies the std level1 cost. + type: int + stdlevel2cost: + description: + - Specifies the std level2 cost. + type: int + tag: + description: + - Specifies the isis tag. + type: int + weight: + description: + - Specifies the isis weight. + type: int + preference_value: + description: + - Specifies the preference value. + type: int + state: + description: + - Determines whether the config should be present or not on the device. + default: present + type: str + choices: ['present', 'absent'] +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +''' + +EXAMPLES = ''' + - name: Set isis description + community.network.ce_is_is_view: + instance_id: 3 + description: abcdeggfs + state: present + + - name: Set isis islevel + community.network.ce_is_is_view: + instance_id: 3 + islevel: level_1 + state: present + - name: Set isis coststyle + community.network.ce_is_is_view: + instance_id: 3 + coststyle: narrow + state: present + + - name: Set isis stdlevel1cost + community.network.ce_is_is_view: + instance_id: 3 + stdlevel1cost: 63 + state: present + + - name: Set isis stdlevel2cost + community.network.ce_is_is_view: + instance_id: 3 + stdlevel2cost: 63 + state: present + + - name: Set isis stdbandwidth + community.network.ce_is_is_view: + instance_id: 3 + stdbandwidth: 1 + state: present +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "state": "present" + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INSTANCE = """ + + + %s + + + + + + + + + + + +""" + +CE_NC_GET_ISIS_ENTITY = """ + + + %s + + + + + + + +""" + +CE_NC_CREAT_ISIS_ENTITY = """ + + + %s + + + %s + + + + +""" + +CE_NC_DELATE_ISIS_ENTITY = """ + + + %s + + + %s + + + + +""" + +CE_NC_GET_ISIS_PREFERENCE = """ + + + %s + + + + + + + + + + + + +""" + +CE_NC_MREGE_ISIS_PREFERENCE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_PREFERENCE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + + + + + +""" + +CE_NC_MERGE_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + %s + + + + +""" + +CE_NC_DELETE_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + 32 + + + + +""" + +CE_NC_GET_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + %s + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + %s + 1 + + + + + + +""" + +CE_NC_GET_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + 0 + + + + false + + + + + + +""" + +CE_NC_GET_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + 0 + + + + false + false + + + + + + +""" + +CE_NC_GET_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + always + 0 + 0 + level_2 + false + + + + + + +""" + +CE_NC_GET_ISIS_IMPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_IMPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_EXPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_EXPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_IMPORTIPROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_IMPORTIPROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + + + + + + + +""" + +CE_NC_MERGE_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + %s + + + + +""" + +CE_NC_DELETE_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + 3 + 3 + 3 + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_ip_addr(ipaddr): + """check ip address, Supports IPv4 and IPv6""" + + if not ipaddr or '\x00' in ipaddr: + return False + + try: + res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_NUMERICHOST) + return bool(res) + except socket.gaierror: + err = sys.exc_info()[1] + if err.args[0] == socket.EAI_NONAME: + return False + raise + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class ISIS_View(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.description = self.module.params['description'] + self.islevel = self.module.params['islevel'] + self.coststyle = self.module.params['coststyle'] + self.relaxspfLimit = self.module.params['relaxspfLimit'] + self.stdlevel1cost = self.module.params['stdlevel1cost'] + self.stdlevel2cost = self.module.params['stdlevel2cost'] + self.stdbandwidth = self.module.params['stdbandwidth'] + self.autocostenable = self.module.params['autocostenable'] + self.autocostenablecompatible = self.module.params['autocostenablecompatible'] + self.netentity = self.module.params['netentity'] + self.preference_value = self.module.params['preference_value'] + self.route_policy_name = self.module.params['route_policy_name'] + self.max_load = self.module.params['max_load'] + self.ip_address = self.module.params['ip_address'] + self.weight = self.module.params['weight'] + self.aclnum_or_name = self.module.params['aclnum_or_name'] + self.ip_prefix_name = self.module.params['ip_prefix_name'] + self.import_routepolicy_name = self.module.params['import_routepolicy_name'] + self.tag = self.module.params['tag'] + self.allow_filter = self.module.params['allow_filter'] + self.allow_up_down = self.module.params['allow_up_down'] + self.penetration_direct = self.module.params['penetration_direct'] + self.enablelevel1tolevel2 = self.module.params['enablelevel1tolevel2'] + self.defaultmode = self.module.params['defaultmode'] + self.mode_routepolicyname = self.module.params['mode_routepolicyname'] + self.cost = self.module.params['cost'] + self.mode_tag = self.module.params['mode_tag'] + self.level_type = self.module.params['level_type'] + self.avoid_learning = self.module.params['avoid_learning'] + self.protocol = self.module.params['protocol'] + self.processid = self.module.params['processid'] + self.cost_type = self.module.params['cost_type'] + self.import_cost = self.module.params['import_cost'] + self.import_tag = self.module.params['import_tag'] + self.impotr_leveltype = self.module.params['impotr_leveltype'] + self.import_route_policy = self.module.params['import_route_policy'] + self.inheritcost = self.module.params['inheritcost'] + self.permitibgp = self.module.params['permitibgp'] + self.avoid_learning = self.module.params['avoid_learning'] + self.export_protocol = self.module.params['export_protocol'] + self.export_policytype = self.module.params['export_policytype'] + self.export_processid = self.module.params['export_processid'] + self.export_aclnumorname = self.module.params['export_aclnumorname'] + self.export_ipprefix = self.module.params['export_ipprefix'] + self.export_routepolicyname = self.module.params['export_routepolicyname'] + self.import_aclnumorname = self.module.params['import_aclnumorname'] + self.import_ipprefix = self.module.params['import_ipprefix'] + self.import_routepolicyname = self.module.params['import_routepolicyname'] + self.bfd_min_rx = self.module.params['bfd_min_rx'] + self.bfd_min_tx = self.module.params['bfd_min_tx'] + self.bfd_multiplier_num = self.module.params['bfd_multiplier_num'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + mutually_exclusive = [["stdlevel1cost", "stdlevel2cost"], + ["aclnum_or_name", "ip_prefix_name", "import_routepolicy_name"], + ["export_aclnumorname", "import_ipprefix", "import_routepolicyname"]] + required_together = [('ip_address', 'weight')] + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + required_together=required_together, + supports_check_mode=True) + + def get_isis_dict(self): + """bfd config dict""" + + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INSTANCE % self.instance_id)) + + if self.netentity: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_ENTITY % self.instance_id)) + + if self.route_policy_name or self.preference_value: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_PREFERENCE % self.instance_id)) + if self.max_load: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_MAXLOAD % self.instance_id)) + if self.ip_address: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_NEXTHOP % self.instance_id)) + if self.penetration_direct and self.penetration_direct == "level2-level1": + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_LEAKROUTELEVEL2 % self.instance_id)) + elif self.penetration_direct and self.penetration_direct == "level1-level2": + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_LEAKROUTELEVEL1 % self.instance_id)) + elif self.defaultmode: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_DEFAULTROUTE % self.instance_id)) + elif self.protocol: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_IMPORTROUTE % self.instance_id)) + elif self.export_protocol: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_EXPORTROUTE % self.instance_id)) + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_BFDLINK % self.instance_id)) + elif self.import_aclnumorname or self.import_ipprefix or self.import_ipprefix: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_IMPORTIPROUTE % self.instance_id)) + xml_str = get_nc_config(self.module, conf_str) + + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + if self.netentity: + glb = root.find("isiscomm/isSites/isSite/isNetEntitys/isNetEntity") + elif self.route_policy_name or self.preference_value: + glb = root.find("isiscomm/isSites/isSite//isSiteMTs/isSiteMT/isPreferences/isPreference") + elif self.max_load: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT") + elif self.ip_address: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isNextHopWeights/isNextHopWeight") + elif self.penetration_direct and self.penetration_direct == "level2-level1": + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isLeakRouteLevel2ToLevel1s/isLeakRouteLevel2ToLevel1") + elif self.penetration_direct and self.penetration_direct == "level1-level2": + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isLeakRouteLevel1ToLevel2s/isLeakRouteLevel1ToLevel2") + elif self.defaultmode: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isDefaultRoutes/isDefaultRoute") + elif self.protocol: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isImportRoutes/isImportRoute") + elif self.export_protocol: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isFilterExports/isFilterExport") + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT") + elif self.import_aclnumorname or self.import_ipprefix or self.import_ipprefix: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isFilterImports/isFilterImport") + else: + glb = root.find("isiscomm/isSites/isSite") + + if glb is not None: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + xml_str = "%s" % self.instance_id + self.updates_cmd.append("isis %s" % self.instance_id) + cmd_list = list() + + if self.state == "present": + if self.description and self.description != instance.get("description"): + xml_str += "%s" % self.description + self.updates_cmd.append("description %s" % self.description) + + if self.islevel and self.islevel != instance.get("isLevel"): + xml_str += "%s" % self.islevel + self.updates_cmd.append("is-level %s" % self.islevel) + + if self.coststyle: + if self.coststyle != instance.get("costStyle"): + xml_str += "%s" % self.coststyle + self.updates_cmd.append("cost-style %s" % self.coststyle) + if self.relaxspfLimit and instance.get("relaxSpfLimit", "false") == "false": + xml_str += "true" + self.updates_cmd.append("cost-style %s relax-spf-limit" % self.coststyle) + elif not self.relaxspfLimit and instance.get("relaxSpfLimit", "false") == "true": + xml_str += "false" + self.updates_cmd.append("cost-style %s" % self.coststyle) + + if self.stdlevel1cost and str(self.stdlevel1cost) != instance.get("stdLevel1Cost"): + xml_str += "%s" % self.stdlevel1cost + self.updates_cmd.append("circuit-cost %s level-1" % self.stdlevel1cost) + + if self.stdlevel2cost and str(self.stdlevel2cost) != instance.get("stdLevel2Cost"): + xml_str += "%s" % self.stdlevel2cost + self.updates_cmd.append("circuit-cost %s level-2" % self.stdlevel2cost) + + if self.stdbandwidth and str(self.stdbandwidth) != instance.get("stdbandwidth"): + xml_str += "%s" % self.stdbandwidth + self.updates_cmd.append("bandwidth-reference %s" % self.stdbandwidth) + + if self.netentity and self.netentity != instance.get("netEntity"): + xml_str = CE_NC_CREAT_ISIS_ENTITY % (self.instance_id, self.netentity) + self.updates_cmd.append("network-entity %s" % self.netentity) + + if self.preference_value or self.route_policy_name: + xml_str = "" + cmd_session = "preference" + if self.preference_value and str(self.preference_value) != instance.get("preferenceValue"): + xml_str = "%s" % self.preference_value + cmd_session += " %s" % self.preference_value + if self.route_policy_name and self.route_policy_name != instance.get("routePolicyName"): + xml_str += "%s" % self.route_policy_name + cmd_session += " route-policy %s" % self.route_policy_name + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + xml_str = CE_NC_MREGE_ISIS_PREFERENCE % (self.instance_id, xml_str) + + if self.max_load and str(self.max_load) != instance.get("maxLoadBalancing"): + xml_str = CE_NC_MERGE_ISIS_MAXLOAD % (self.instance_id, self.max_load) + self.updates_cmd.append("maximum load-balancing %s" % self.max_load) + + if self.ip_address: + xml_str = CE_NC_MERGE_ISIS_NEXTHOP % (self.instance_id, self.ip_address, self.weight) + self.updates_cmd.append("nexthop %s weight %s" % (self.ip_address, self.weight)) + + if self.penetration_direct: + xml_str = "" + if self.penetration_direct == "level2-level1": + cmd_session = "import-route isis level-2 into level-1" + elif self.penetration_direct == "level1-level2": + cmd_session = "import-route isis level-1 into level-2" + if self.aclnum_or_name: + xml_str = "%s" % self.aclnum_or_name + xml_str += "aclNumOrName" + if isinstance(self.aclnum_or_name, int): + cmd_session += " filter-policy %s" % self.aclnum_or_name + elif isinstance(self.aclnum_or_name, str): + cmd_session += " filter-policy acl-name %s" % self.aclnum_or_name + if self.ip_prefix_name: + xml_str = "%s" % self.ip_prefix_name + xml_str += "ipPrefix" + cmd_session += " filter-policy ip-prefix %s" % self.ip_prefix_name + if self.import_routepolicy_name: + xml_str = "%s" % self.import_routepolicy_name + xml_str += "routePolicy" + cmd_session += " filter-policy route-policy %s" % self.import_routepolicy_name + if self.tag: + xml_str += "%s" % self.tag + cmd_session += " tag %s" % self.tag + if self.allow_filter or self.allow_up_down: + cmd_session += " direct" + if self.allow_filter: + xml_str += "true" + cmd_session += " allow-filter-policy" + if self.allow_up_down: + xml_str += "true" + cmd_session += " allow-up-down-bit" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + if self.enablelevel1tolevel2: + xml_str += "true" + self.updates_cmd.append("undo import-route isis level-1 into level-2 disable") + + if self.defaultmode: + cmd_session = "default-route-advertise" + if self.defaultmode == "always": + xml_str = "always" + cmd_session += " always" + elif self.defaultmode == "matchDefault": + xml_str = "matchDefault" + cmd_session += " match default" + elif self.defaultmode == "matchAny": + xml_str = "matchAny" + xml_str += "routePolicy" + xml_str += "%s" % self.mode_routepolicyname + cmd_session += " route-policy %s" % self.mode_routepolicyname + if self.cost is not None: + xml_str += "%s" % self.cost + cmd_session += " cost %s" % self.cost + if self.mode_tag: + xml_str += "%s" % self.mode_tag + cmd_session += " tag %s" % self.mode_tag + if self.level_type: + if self.level_type == "level_1": + xml_str += "level_1" + cmd_session += " level-1" + elif self.level_type == "level_2": + xml_str += "level_2" + cmd_session += " level-2" + elif self.level_type == "level_1_2": + xml_str += "level_1_2" + cmd_session += " level-1-2" + if self.avoid_learning: + xml_str += "true" + cmd_session += " avoid-learning" + elif not self.avoid_learning: + xml_str += "false" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.protocol: + cmd_session = "import-route" + if self.protocol == "rip": + xml_str = "rip" + cmd_session += " rip" + elif self.protocol == "isis": + xml_str = "isis" + cmd_session += " isis" + elif self.protocol == "ospf": + xml_str = "ospf" + cmd_session += " ospf" + elif self.protocol == "static": + xml_str = "static" + cmd_session += " static" + elif self.protocol == "direct": + xml_str = "direct" + cmd_session += " direct" + elif self.protocol == "bgp": + xml_str = "bgp" + cmd_session += " bgp" + if self.permitibgp: + xml_str += "true" + cmd_session += " permit-ibgp" + if self.protocol == "rip" or self.protocol == "isis" or self.protocol == "ospf": + xml_str += "%s" % self.processid + cmd_session += " %s" % self.processid + if self.inheritcost: + xml_str += "%s" % self.inheritcost + cmd_session += " inherit-cost" + if self.cost_type: + if self.cost_type == "external": + xml_str += "external" + cmd_session += " cost-type external" + elif self.cost_type == "internal": + xml_str += "internal" + cmd_session += " cost-type internal" + if self.import_cost: + xml_str += "%s" % self.import_cost + cmd_session += " cost %s" % self.import_cost + if self.import_tag: + xml_str += "%s" % self.import_tag + cmd_session += " tag %s" % self.import_tag + if self.import_route_policy: + xml_str += "routePolicy" + xml_str += "%s" % self.import_route_policy + cmd_session += " route-policy %s" % self.import_route_policy + if self.impotr_leveltype: + if self.impotr_leveltype == "level_1": + cmd_session += " level-1" + elif self.impotr_leveltype == "level_2": + cmd_session += " level-2" + elif self.impotr_leveltype == "level_1_2": + cmd_session += " level-1-2" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + xml_str = "" + self.updates_cmd.append("bfd all-interfaces enable") + cmd_session = "bfd all-interfaces" + if self.bfd_min_rx: + xml_str += "%s" % self.bfd_min_rx + cmd_session += " min-rx-interval %s" % self.bfd_min_rx + if self.bfd_min_tx: + xml_str += "%s" % self.bfd_min_tx + cmd_session += " min-tx-interval %s" % self.bfd_min_tx + if self.bfd_multiplier_num: + xml_str += "%s" % self.bfd_multiplier_num + cmd_session += " detect-multiplier %s" % self.bfd_multiplier_num + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.export_protocol: + cmd_session = "filter-policy" + if self.export_aclnumorname: + xml_str = "aclNumOrName" + xml_str += "%s" % self.export_aclnumorname + if isinstance(self.export_aclnumorname, int): + cmd_session += " %s" % self.export_aclnumorname + elif isinstance(self.export_aclnumorname, str): + cmd_session += " acl-name %s" % self.export_aclnumorname + if self.export_ipprefix: + xml_str = "ipPrefix" + xml_str += "%s" % self.export_ipprefix + cmd_session += " ip-prefix %s" % self.export_ipprefix + if self.export_routepolicyname: + xml_str = "routePolicy" + xml_str += "%s" % self.export_routepolicyname + cmd_session += " route-policy %s" % self.export_routepolicyname + xml_str += "%s" % self.export_protocol + cmd_session += " export %s" % self.export_protocol + if self.export_processid is not None: + xml_str += "%s" % self.export_processid + cmd_session += " %s" % self.export_processid + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.import_ipprefix or self.import_aclnumorname or self.import_routepolicyname: + cmd_session = "filter-policy" + if self.import_aclnumorname: + xml_str = "aclNumOrName" + xml_str += "%s" % self.import_aclnumorname + if isinstance(self.import_aclnumorname, int): + cmd_session += " %s" % self.import_aclnumorname + elif isinstance(self.import_aclnumorname, str): + cmd_session += " acl-name %s" % self.import_aclnumorname + if self.import_ipprefix: + xml_str = "ipPrefix" + xml_str += "%s" % self.import_ipprefix + cmd_session += " ip-prefix %s" % self.import_ipprefix + if self.import_routepolicyname: + xml_str = "routePolicy" + xml_str += "%s" % self.import_routepolicyname + cmd_session += " route-policy %s" % self.import_routepolicyname + cmd_session += "import" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + else: + # absent + if self.description and self.description == instance.get("description"): + xml_str += "%s" % self.description + self.updates_cmd.append("undo description") + + if self.islevel and self.islevel == instance.get("isLevel"): + xml_str += "level_1_2" + self.updates_cmd.append("undo is-level") + + if self.coststyle and self.coststyle == instance.get("costStyle"): + xml_str += "%s" % ("narrow") + xml_str += "false" + self.updates_cmd.append("undo cost-style") + + if self.stdlevel1cost and str(self.stdlevel1cost) == instance.get("stdLevel1Cost"): + xml_str += "%s" % self.stdlevel1cost + self.updates_cmd.append("undo circuit-cost %s level-1" % self.stdlevel1cost) + + if self.stdlevel2cost and str(self.stdlevel2cost) == instance.get("stdLevel2Cost"): + xml_str += "%s" % self.stdlevel2cost + self.updates_cmd.append("undo circuit-cost %s level-2" % self.stdlevel2cost) + + if self.stdbandwidth and str(self.stdbandwidth) == instance.get("stdbandwidth"): + xml_str += "100" + self.updates_cmd.append("undo bandwidth-reference") + + if self.netentity and self.netentity == instance.get("netEntity"): + xml_str = CE_NC_DELATE_ISIS_ENTITY % (self.instance_id, self.netentity) + self.updates_cmd.append("undo network-entity %s" % self.netentity) + + if self.preference_value or self.route_policy_name: + xml_str = "" + if self.preference_value and str(self.preference_value) == instance.get("preferenceValue"): + xml_str = "%s" % self.preference_value + if self.route_policy_name and self.route_policy_name == instance.get("routePolicyName"): + xml_str += "%s" % self.route_policy_name + self.updates_cmd.append("undo preference") + elif not self.preference_value and self.route_policy_name and self.route_policy_name == instance.get("routePolicyName"): + xml_str = "%s" % self.route_policy_name + self.updates_cmd.append("undo preference") + xml_str = CE_NC_DELETE_ISIS_PREFERENCE % (self.instance_id, xml_str) + + if self.max_load and str(self.max_load) == instance.get("maxLoadBalancing"): + xml_str = CE_NC_DELETE_ISIS_MAXLOAD % self.instance_id + self.updates_cmd.append("undo maximum load-balancing") + + if self.ip_address: + xml_str = CE_NC_DELETE_ISIS_NEXTHOP % (self.instance_id, self.ip_address) + self.updates_cmd.append("undo nexthop %s" % self.ip_address) + + if self.penetration_direct: + if self.penetration_direct == "level2-level1": + self.updates_cmd.append("undo import-route isis level-2 into level-1") + elif self.penetration_direct == "level1-level2": + self.updates_cmd.append("undo import-route isis level-1 into level-2") + self.updates_cmd.append("import-route isis level-1 into level-2 disable") + + if self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num is not None: + xml_str = CE_NC_DELETE_ISIS_BFDLINK % self.instance_id + self.updates_cmd.append("undo bfd all-interfaces enable") + cmd_session = "undo bfd all-interfaces" + if self.bfd_min_rx: + cmd_session += " min-rx-interval %s" % self.bfd_min_rx + if self.bfd_min_tx: + cmd_session += " min-tx-interval %s" % self.bfd_min_tx + if self.bfd_multiplier_num: + cmd_session += " detect-multiplier %s" % self.bfd_multiplier_num + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.defaultmode: + xml_str = CE_NC_DELETE_ISIS_DEFAULTROUTE % self.instance_id + self.updates_cmd.append("undo default-route-advertise") + + if self.protocol: + if self.protocol == "rip" or self.protocol == "isis" or self.protocol == "ospf": + self.updates_cmd.append("undo import-route %s %s" % (self.protocol, self.processid)) + else: + self.updates_cmd.append("undo import-route %s" % self.protocol) + + if self.export_protocol: + cmd_session = "undo filter-policy" + if self.export_aclnumorname: + if isinstance(self.export_aclnumorname, int): + cmd_session += " %s" % self.export_aclnumorname + elif isinstance(self.export_aclnumorname, str): + cmd_session += " acl-name %s" % self.export_aclnumorname + if self.export_ipprefix: + cmd_session += " ip-prefix %s" % self.export_ipprefix + if self.export_routepolicyname: + cmd_session += " route-policy %s" % self.export_routepolicyname + cmd_session += " export %s" % self.export_protocol + if self.export_processid is not None: + cmd_session += " %s" % self.export_processid + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + if self.import_ipprefix or self.import_aclnumorname or self.import_routepolicyname: + cmd_session = "undo filter-policy" + if self.import_aclnumorname: + if isinstance(self.import_aclnumorname, int): + cmd_session += " %s" % self.import_aclnumorname + elif isinstance(self.import_aclnumorname, str): + cmd_session += " acl-name %s" % self.import_aclnumorname + if self.import_ipprefix: + cmd_session += " ip-prefix %s" % self.import_ipprefix + if self.import_routepolicyname: + cmd_session += " route-policy %s" % self.import_routepolicyname + cmd_session += " import" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.autocostenable and instance.get("stdAutoCostEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("auto-cost enable") + elif not self.autocostenable and instance.get("stdAutoCostEnable", "false") == "true": + xml_str += "false" + xml_str += "false" + self.updates_cmd.append("undo auto-cost enable") + + if self.autocostenable: + if self.autocostenablecompatible and instance.get("stdAutoCostEnableCompatible", "false") == "false": + xml_str += "true" + self.updates_cmd.append("auto-cost enable compatible") + elif not self.autocostenablecompatible and instance.get("stdAutoCostEnableCompatible", "false") == "true": + xml_str += "false" + self.updates_cmd.append("auto-cost enable") + + if self.state == "present": + if self.netentity or self.preference_value or self.route_policy_name or self.max_load or self.ip_address: + return xml_str + elif self.penetration_direct: + if self.penetration_direct == "level2-level1": + return CE_NC_MERGE_ISIS_LEAKROUTELEVEL2 % (self.instance_id, xml_str) + elif self.penetration_direct == "level1-level2": + return CE_NC_MERGE_ISIS_LEAKROUTELEVEL1 % (self.instance_id, xml_str) + elif self.defaultmode: + return CE_NC_MERGE_ISIS_DEFAULTROUTE % (self.instance_id, xml_str) + elif self.protocol: + return CE_NC_MERGE_ISIS_IMPORTROUTE % (self.instance_id, xml_str) + elif self.export_protocol: + return CE_NC_MERGE_ISIS_EXPORTROUTE % (self.instance_id, xml_str) + elif self.import_routepolicyname or self.import_aclnumorname or self.import_ipprefix: + return CE_NC_MERGE_ISIS_IMPORTIPROUTE % (self.instance_id, xml_str) + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + return CE_NC_MERGE_ISIS_BFDLINK % (self.instance_id, xml_str) + else: + return '' + xml_str + '' + else: + if self.netentity or self.preference_value or self.route_policy_name or self.max_load \ + or self.ip_address or self.defaultmode or self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num is not None: + return xml_str + else: + return '' + xml_str + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + if xml_str == "%s" % self.instance_id: + pass + else: + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + levelcost = 16777215 + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check description + if self.description: + if len(self.description) < 1 or len(self.description) > 80: + self.module.fail_json(msg="Error: description is invalid.") + + # + if self.stdbandwidth: + if self.stdbandwidth < 1 or self.stdbandwidth > 2147483648: + self.module.fail_json(msg="Error: stdbandwidth is not ranges from 1 to 2147483648.") + + if self.relaxspfLimit is not None and not self.coststyle: + self.module.fail_json(msg="Error: relaxspfLimit must set after coststyle.") + + if self.coststyle: + if self.coststyle != "wide" and self.coststyle != "wtransition": + levelcost = 63 + else: + levelcost = 16777215 + if self.stdlevel1cost: + if self.stdlevel1cost < 1 or self.stdlevel1cost > levelcost: + self.module.fail_json(msg="Error: stdlevel1cost is not ranges from 1 to %s." % levelcost) + + if self.stdlevel2cost: + if self.stdlevel2cost < 1 or self.stdlevel2cost > levelcost: + self.module.fail_json(msg="Error: stdlevel2cost is not ranges from 1 to %s." % levelcost) + + if self.coststyle: + if self.coststyle != "ntransition" and self.coststyle != "transition": + if self.relaxspfLimit: + self.module.fail_json(msg="Error: relaxspfLimit can not be set while the coststyle is not ntransition or transition") + + if self.autocostenablecompatible: + if not self.autocostenable: + self.module.fail_json(msg="Error: you shoule enable the autocostenable first.") + + if self.preference_value: + if self.preference_value < 1 or self.preference_value > 255: + self.module.fail_json(msg="Error: preference_value is not ranges from 1 to 255.") + + if self.route_policy_name: + if len(self.route_policy_name) < 1 or len(self.route_policy_name) > 200: + self.module.fail_json(msg="Error: route_policy_name is invalid.") + + if self.max_load: + if self.max_load < 1 or self.max_load > 32: + self.module.fail_json(msg="Error: max_load is not ranges from 1 to 32.") + + if self.weight: + if self.weight < 1 or self.weight > 254: + self.module.fail_json(msg="Error: weight is not ranges from 1 to 254.") + + if self.aclnum_or_name: + if isinstance(self.aclnum_or_name, int): + if self.aclnum_or_name < 2000 or self.aclnum_or_name > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.aclnum_or_name, str): + if len(self.aclnum_or_name) < 1 or len(self.aclnum_or_name) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + if self.ip_prefix_name: + if len(self.ip_prefix_name) < 1 or len(self.ip_prefix_name) > 169: + self.module.fail_json(msg="Error: ip_prefix_name is invalid.") + if self.import_routepolicy_name: + if len(self.import_routepolicy_name) < 1 or len(self.import_routepolicy_name) > 200: + self.module.fail_json(msg="Error: import_routepolicy_name is invalid.") + if self.tag: + if self.tag < 1 or self.tag > 4294967295: + self.module.fail_json(msg="Error: tag is not ranges from 1 to 4294967295.") + + if self.mode_routepolicyname: + if len(self.mode_routepolicyname) < 1 or len(self.mode_routepolicyname) > 200: + self.module.fail_json(msg="Error: mode_routepolicyname is invalid.") + if self.cost is not None: + if self.cost < 0 or self.cost > 4261412864: + self.module.fail_json(msg="Error: cost is not ranges from 0 to 4261412864.") + if self.mode_tag: + if self.mode_tag < 1 or self.mode_tag > 4294967295: + self.module.fail_json(msg="Error: mode_tag is not ranges from 1 to 4294967295.") + + if self.processid is not None: + if self.processid < 0 or self.processid > 4294967295: + self.module.fail_json(msg="Error: processid is not ranges from 0 to 4294967295.") + + if self.import_cost is not None: + if self.import_cost < 0 or self.import_cost > 4261412864: + self.module.fail_json(msg="Error: import_cost is not ranges from 0 to 4261412864.") + + if self.import_tag: + if self.import_tag < 1 or self.import_tag > 4294967295: + self.module.fail_json(msg="Error: import_tag is not ranges from 1 to 4294967295.") + + if self.export_aclnumorname: + if isinstance(self.export_aclnumorname, int): + if self.export_aclnumorname < 2000 or self.export_aclnumorname > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.export_aclnumorname, str): + if len(self.export_aclnumorname) < 1 or len(self.export_aclnumorname) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + + if self.export_processid: + if self.export_processid < 1 or self.export_processid > 4294967295: + self.module.fail_json(msg="Error: export_processid is not ranges from 1 to 4294967295.") + + if self.export_ipprefix: + if len(self.export_ipprefix) < 1 or len(self.export_ipprefix) > 169: + self.module.fail_json(msg="Error: export_ipprefix is invalid.") + + if self.export_routepolicyname: + if len(self.export_routepolicyname) < 1 or len(self.export_routepolicyname) > 200: + self.module.fail_json(msg="Error: export_routepolicyname is invalid.") + + if self.bfd_min_rx: + if self.bfd_min_rx < 50 or self.bfd_min_rx > 1000: + self.module.fail_json(msg="Error: bfd_min_rx is not ranges from 50 to 1000.") + + if self.bfd_min_tx: + if self.bfd_min_tx < 50 or self.bfd_min_tx > 1000: + self.module.fail_json(msg="Error: bfd_min_tx is not ranges from 50 to 1000.") + + if self.bfd_multiplier_num: + if self.bfd_multiplier_num < 3 or self.bfd_multiplier_num > 50: + self.module.fail_json(msg="Error: bfd_multiplier_num is not ranges from 3 to 50.") + + if self.import_routepolicyname: + if len(self.import_routepolicyname) < 1 or len(self.import_routepolicyname) > 200: + self.module.fail_json(msg="Error: import_routepolicyname is invalid.") + + if self.import_aclnumorname: + if isinstance(self.import_aclnumorname, int): + if self.import_aclnumorname < 2000 or self.import_aclnumorname > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.import_aclnumorname, str): + if len(self.import_aclnumorname) < 1 or len(self.import_aclnumorname) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + + def get_proposed(self): + """get proposed info""" + # base config + self.proposed["instance_id"] = self.instance_id + self.proposed["description"] = self.description + self.proposed["islevel"] = self.islevel + self.proposed["coststyle"] = self.coststyle + self.proposed["relaxspfLimit"] = self.relaxspfLimit + self.proposed["stdlevel1cost"] = self.stdlevel1cost + self.proposed["stdlevel2cost"] = self.stdlevel2cost + self.proposed["stdbandwidth"] = self.stdbandwidth + self.proposed["autocostenable"] = self.autocostenable + self.proposed["autocostenablecompatible"] = self.autocostenablecompatible + self.proposed["netentity"] = self.netentity + self.proposed["preference_value"] = self.preference_value + self.proposed["route_policy_name"] = self.route_policy_name + self.proposed["max_load"] = self.max_load + self.proposed["ip_address"] = self.ip_address + self.proposed["weight"] = self.weight + self.proposed["penetration_direct"] = self.penetration_direct + self.proposed["aclnum_or_name"] = self.aclnum_or_name + self.proposed["ip_prefix_name"] = self.ip_prefix_name + self.proposed["import_routepolicy_name"] = self.import_routepolicy_name + self.proposed["tag"] = self.tag + self.proposed["allow_filter"] = self.allow_filter + self.proposed["allow_up_down"] = self.allow_up_down + self.proposed["enablelevel1tolevel2"] = self.enablelevel1tolevel2 + self.proposed["protocol"] = self.protocol + self.proposed["processid"] = self.processid + self.proposed["cost_type"] = self.cost_type + self.proposed["import_cost"] = self.import_cost + self.proposed["import_tag"] = self.import_tag + self.proposed["import_route_policy"] = self.import_route_policy + self.proposed["impotr_leveltype"] = self.impotr_leveltype + self.proposed["inheritcost"] = self.inheritcost + self.proposed["permitibgp"] = self.permitibgp + self.proposed["export_protocol"] = self.export_protocol + self.proposed["export_policytype"] = self.export_policytype + self.proposed["export_processid"] = self.export_processid + self.proposed["export_aclnumorname"] = self.export_aclnumorname + self.proposed["export_ipprefix"] = self.export_ipprefix + self.proposed["export_routepolicyname"] = self.export_routepolicyname + self.proposed["import_aclnumorname"] = self.import_aclnumorname + self.proposed["import_ipprefix"] = self.import_ipprefix + self.proposed["import_routepolicyname"] = self.import_routepolicyname + self.proposed["bfd_min_rx"] = self.bfd_min_rx + self.proposed["bfd_min_tx"] = self.bfd_min_tx + self.proposed["bfd_multiplier_num"] = self.bfd_multiplier_num + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + else: + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + else: + self.end_state["instance"] = isis_dict.get("instance") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + xml_str += self.config_session() + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + instance_id=dict(required=True, type='int'), + description=dict(required=False, type='str'), + islevel=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + coststyle=dict(required=False, type='str', choices=['narrow', 'wide', 'transition', 'ntransition', 'wtransition']), + relaxspfLimit=dict(required=False, type='bool'), + stdlevel1cost=dict(required=False, type='int'), + stdlevel2cost=dict(required=False, type='int'), + stdbandwidth=dict(required=False, type='int'), + autocostenable=dict(required=False, type='bool'), + autocostenablecompatible=dict(required=False, type='bool'), + netentity=dict(required=False, type='str'), + preference_value=dict(required=False, type='int'), + route_policy_name=dict(required=False, type='str'), + max_load=dict(required=False, type='int'), + ip_address=dict(required=False, type='str'), + weight=dict(required=False, type='int'), + penetration_direct=dict(required=False, type='str', choices=['level2-level1', 'level1-level2']), + aclnum_or_name=dict(required=False, type='str'), + ip_prefix_name=dict(required=False, type='str'), + import_routepolicy_name=dict(required=False, type='str'), + tag=dict(required=False, type='int'), + allow_filter=dict(required=False, type='bool'), + allow_up_down=dict(required=False, type='bool'), + enablelevel1tolevel2=dict(required=False, type='bool'), + defaultmode=dict(required=False, type='str', choices=['always', 'matchDefault', 'matchAny']), + mode_routepolicyname=dict(required=False, type='str'), + cost=dict(required=False, type='int'), + mode_tag=dict(required=False, type='int'), + level_type=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + avoid_learning=dict(required=False, type='bool'), + protocol=dict(required=False, type='str', choices=['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all']), + processid=dict(required=False, type='int'), + cost_type=dict(required=False, type='str', choices=['external', 'internal']), + import_cost=dict(required=False, type='int'), + import_tag=dict(required=False, type='int'), + import_route_policy=dict(required=False, type='str'), + impotr_leveltype=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + inheritcost=dict(required=False, type='bool'), + permitibgp=dict(required=False, type='bool'), + export_protocol=dict(required=False, type='str', choices=['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all']), + export_policytype=dict(required=False, type='str', choices=['aclNumOrName', 'ipPrefix', 'routePolicy']), + export_processid=dict(required=False, type='int'), + export_aclnumorname=dict(required=False, type='str'), + export_ipprefix=dict(required=False, type='str'), + export_routepolicyname=dict(required=False, type='str'), + import_aclnumorname=dict(required=False, type='str'), + import_ipprefix=dict(required=False, type='str'), + import_routepolicyname=dict(required=False, type='str'), + bfd_min_rx=dict(required=False, type='int'), + bfd_min_tx=dict(required=False, type='int'), + bfd_multiplier_num=dict(required=False, type='int'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_View(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lacp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lacp.py new file mode 100644 index 00000000..fdfb2a6d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lacp.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ce_lacp +version_added: '0.2.0' +short_description: Manages Eth-Trunk interfaces on HUAWEI CloudEngine switches +description: + - Manages Eth-Trunk specific configuration parameters on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - C(state=absent) removes the Eth-Trunk config and interface if it already exists. If members to be removed are not explicitly + passed, all existing members (if any), are removed, and Eth-Trunk removed. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + trunk_id: + description: + - Eth-Trunk interface number. + The value is an integer. + The value range depends on the assign forward eth-trunk mode command. + When 256 is specified, the value ranges from 0 to 255. + When 512 is specified, the value ranges from 0 to 511. + When 1024 is specified, the value ranges from 0 to 1023. + type: int + mode: + description: + - Specifies the working mode of an Eth-Trunk interface. + default: null + choices: ['Manual','Dynamic','Static'] + type: str + preempt_enable: + description: + - Specifies lacp preempt enable of Eth-Trunk lacp. + The value is an boolean 'true' or 'false'. + type: bool + state_flapping: + description: + - Lacp dampening state-flapping. + type: bool + port_id_extension_enable: + description: + - Enable the function of extending the LACP negotiation port number. + type: bool + unexpected_mac_disable: + description: + - Lacp dampening unexpected-mac disable. + type: bool + system_id: + description: + - Link Aggregation Control Protocol System ID,interface Eth-Trunk View. + - Formate 'X-X-X',X is hex(a,aa,aaa, or aaaa) + type: str + timeout_type: + description: + - Lacp timeout type,that may be 'Fast' or 'Slow'. + choices: ['Slow', 'Fast'] + type: str + fast_timeout: + description: + - When lacp timeout type is 'Fast', user-defined time can be a number(3~90). + type: int + mixed_rate_link_enable: + description: + - Value of max active linknumber. + type: bool + preempt_delay: + description: + - Value of preemption delay time. + type: int + collector_delay: + description: + - Value of delay time in units of 10 microseconds. + type: int + max_active_linknumber: + description: + - Max active linknumber in link aggregation group. + type: int + select: + description: + - Select priority or speed to preempt. + choices: ['Speed', 'Prority'] + type: str + priority: + description: + - The priority of eth-trunk member interface. + type: int + global_priority: + description: + - Configure lacp priority on system-view. + type: int + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] + type: str +''' +EXAMPLES = r''' + - name: Ensure Eth-Trunk100 is created, and set to mode lacp-static + community.network.ce_lacp: + trunk_id: 100 + mode: 'lacp-static' + state: present + - name: Ensure Eth-Trunk100 is created, add two members, and set global priority to 1231 + community.network.ce_lacp: + trunk_id: 100 + global_priority: 1231 + state: present + - name: Ensure Eth-Trunk100 is created, and set mode to Dynamic and configure other options + community.network.ce_lacp: + trunk_id: 100 + mode: Dynamic + preempt_enable: True, + state_flapping: True, + port_id_extension_enable: True, + unexpected_mac_disable: True, + timeout_type: Fast, + fast_timeout: 123, + mixed_rate_link_enable: True, + preempt_delay: 23, + collector_delay: 33, + state: present +''' + +RETURN = r''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"trunk_id": "100", "members": ['10GE1/0/24','10GE1/0/25'], "mode": "lacp-static"} +existing: + description: k/v pairs of existing Eth-Trunk + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "manual"} +end_state: + description: k/v pairs of Eth-Trunk info after module execution + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/24", "memberIfState": "Down"}, + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "lacp-static"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Eth-Trunk 100", + "mode lacp-static", + "interface 10GE1/0/25", + "eth-trunk 100"] +''' + +import xml.etree.ElementTree as ET +import re +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +LACP = {'trunk_id': 'ifName', + 'mode': 'workMode', + 'preempt_enable': 'isSupportPrmpt', + 'state_flapping': 'dampStaFlapEn', + 'port_id_extension_enable': 'trunkPortIdExt', + 'unexpected_mac_disable': 'dampUnexpMacEn', + 'system_id': 'trunkSysMac', + 'timeout_type': 'rcvTimeoutType', + 'fast_timeout': 'fastTimeoutUserDefinedValue', + 'mixed_rate_link_enable': 'mixRateEnable', + 'preempt_delay': 'promptDelay', + 'collector_delay': 'collectMaxDelay', + 'max_active_linknumber': 'maxActiveNum', + 'select': 'selectPortStd', + 'weight': 'weight', + 'priority': 'portPriority', + 'global_priority': 'priority' + } + + +def has_element(parent, xpath): + """get or create a element by xpath""" + ele = parent.find('./' + xpath) + if ele is not None: + return ele + ele = parent + lpath = xpath.split('/') + for p in lpath: + e = parent.find('.//' + p) + if e is None: + e = ET.SubElement(ele, p) + ele = e + return ele + + +def bulid_xml(kwargs, operation='get'): + """create a xml tree by dictionary with operation,get,merge and delete""" + attrib = {'xmlns': "http://www.huawei.com/netconf/vrp", + 'content-version': "1.0", 'format-version': "1.0"} + + root = ET.Element('ifmtrunk') + for key in kwargs.keys(): + if key in ('global_priority',): + xpath = 'lacpSysInfo' + elif key in ('priority',): + xpath = 'TrunkIfs/TrunkIf/TrunkMemberIfs/TrunkMemberIf/lacpPortInfo/lacpPort' + elif key in ['preempt_enable', 'timeout_type', 'fast_timeout', 'select', 'preempt_delay', + 'max_active_linknumber', 'collector_delay', 'mixed_rate_link_enable', + 'state_flapping', 'unexpected_mac_disable', 'system_id', + 'port_id_extension_enable']: + xpath = 'TrunkIfs/TrunkIf/lacpTrunk' + elif key in ('trunk_id', 'mode'): + xpath = 'TrunkIfs/TrunkIf' + if xpath != '': + parent = has_element(root, xpath) + element = ET.SubElement(parent, LACP[key]) + if operation == 'merge': + parent.attrib = dict(operation=operation) + element.text = str(kwargs[key]) + if key == 'mode': + element.text = str(kwargs[key]) + if key == 'trunk_id': + element.text = 'Eth-Trunk' + str(kwargs[key]) + root.attrib = attrib + config = ET.tostring(root) + if operation == 'merge' or operation == 'delete': + return '%s' % to_native(config) + return '%s' % to_native(config) + + +def check_param(kwargs): + """check args list,the boolean or list values cloud not be checked,because they are limit by args list in main""" + + for key in kwargs: + if kwargs[key] is None: + continue + if key == 'trunk_id': + value = int(kwargs[key]) + # maximal value is 1024,although the value is limit by command 'assign forward eth-trunk mode ' + if value < 0 or value > 1024: + return 'Error: Wrong Value of Eth-Trunk interface number' + elif key == 'system_id': + # X-X-X ,X is hex(4 bit) + if not re.match(r'[0-9a-f]{1,4}\-[0-9a-f]{1,4}\-[0-9a-f]{1,4}', kwargs[key], re.IGNORECASE): + return 'Error: The system-id is invalid.' + values = kwargs[key].split('-') + flag = 0 + # all 'X' is 0,that is invalid value + for v in values: + if len(v.strip('0')) < 1: + flag += 1 + if flag == 3: + return 'Error: The system-id is invalid.' + elif key == 'timeout_type': + # select a value from choices, choices=['Slow','Fast'],it's checked by AnsibleModule + pass + elif key == 'fast_timeout': + value = int(kwargs[key]) + if value < 3 or value > 90: + return 'Error: Wrong Value of timeout,fast user-defined value<3-90>' + rtype = str(kwargs.get('timeout_type')) + if rtype == 'Slow': + return 'Error: Short timeout period for receiving packets is need,when user define the time.' + elif key == 'preempt_delay': + value = int(kwargs[key]) + if value < 0 or value > 180: + return 'Error: Value of preemption delay time is from 0 to 180' + elif key == 'collector_delay': + value = int(kwargs[key]) + if value < 0 or value > 65535: + return 'Error: Value of collector delay time is from 0 to 65535' + elif key == 'max_active_linknumber': + value = int(kwargs[key]) + if value < 0 or value > 64: + return 'Error: Value of collector delay time is from 0 to 64' + elif key == 'priority' or key == 'global_priority': + value = int(kwargs[key]) + if value < 0 or value > 65535: + return 'Error: Value of priority is from 0 to 65535' + return 'ok' + + +def xml_to_dict(args): + """transfer xml string into dict """ + rdict = dict() + args = re.sub(r'xmlns=\".+?\"', '', args) + root = ET.fromstring(args) + ifmtrunk = root.find('.//ifmtrunk') + if ifmtrunk is not None: + try: + ifmtrunk_iter = ET.Element.iter(ifmtrunk) + except AttributeError: + ifmtrunk_iter = ifmtrunk.getiterator() + + for ele in ifmtrunk_iter: + if ele.text is not None and len(ele.text.strip()) > 0: + rdict[ele.tag] = ele.text + return rdict + + +def compare_config(module, kwarg_exist, kwarg_end): + """compare config between exist and end""" + dic_command = {'isSupportPrmpt': 'lacp preempt enable', + 'rcvTimeoutType': 'lacp timeout', # lacp timeout fast user-defined 23 + 'fastTimeoutUserDefinedValue': 'lacp timeout user-defined', + 'selectPortStd': 'lacp select', + 'promptDelay': 'lacp preempt delay', + 'maxActiveNum': 'lacp max active-linknumber', + 'collectMaxDelay': 'lacp collector delay', + 'mixRateEnable': 'lacp mixed-rate link enable', + 'dampStaFlapEn': 'lacp dampening state-flapping', + 'dampUnexpMacEn': 'lacp dampening unexpected-mac disable', + 'trunkSysMac': 'lacp system-id', + 'trunkPortIdExt': 'lacp port-id-extension enable', + 'portPriority': 'lacp priority', # interface 10GE1/0/1 + 'lacpMlagPriority': 'lacp m-lag priority', + 'lacpMlagSysId': 'lacp m-lag system-id', + 'priority': 'lacp priority' + } + rlist = list() + exist = set(kwarg_exist.keys()) + end = set(kwarg_end.keys()) + undo = exist - end + add = end - exist + update = end & exist + + for key in undo: + if key in dic_command: + rlist.append('undo ' + dic_command[key]) + for key in add: + if key in dic_command: + rlist.append(dic_command[key] + ' ' + kwarg_end[key]) + for key in update: + if kwarg_exist[key] != kwarg_end[key] and key in dic_command: + if kwarg_exist[key] == 'true' and kwarg_end[key] == 'false': + rlist.append('undo ' + dic_command[key]) + elif kwarg_exist[key] == 'false' and kwarg_end[key] == 'true': + rlist.append(dic_command[key]) + else: + rlist.append(dic_command[key] + ' ' + kwarg_end[key].lower()) + return rlist + + +class Lacp(object): + """ + Manages Eth-Trunk interfaces LACP. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.trunk_id = self.module.params['trunk_id'] + self.mode = self.module.params['mode'] + self.param = dict() + + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """ init AnsibleModule """ + + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=[['trunk_id', 'global_priority']], + required_one_of=[['trunk_id', 'global_priority']], + supports_check_mode=True) + + def check_params(self): + """check module params """ + for key in self.module.params.keys(): + if key in LACP.keys() and self.module.params[key] is not None: + self.param[key] = self.module.params[key] + if isinstance(self.module.params[key], bool): + self.param[key] = str(self.module.params[key]).lower() + msg = check_param(self.param) + if msg != 'ok': + self.module.fail_json(msg=msg) + + def get_existing(self): + """get existing""" + xml_str = bulid_xml(self.param) + xml = get_nc_config(self.module, xml_str) + return xml_to_dict(xml) + + def get_proposed(self): + """get proposed""" + proposed = dict(state=self.state) + proposed.update(self.param) + return proposed + + def get_end_state(self): + """ get end_state""" + xml_str = bulid_xml(self.param) + xml = get_nc_config(self.module, xml_str) + return xml_to_dict(xml) + + def work(self): + """worker""" + + self.check_params() + existing = self.get_existing() + proposed = self.get_proposed() + + # deal present or absent + if self.state == "present": + operation = 'merge' + else: + operation = 'delete' + + xml_str = bulid_xml(self.param, operation=operation) + set_nc_config(self.module, xml_str) + end_state = self.get_end_state() + + self.results['proposed'] = proposed + self.results['existing'] = existing + self.results['end_state'] = end_state + updates_cmd = compare_config(self.module, existing, end_state) + self.results['updates'] = updates_cmd + if updates_cmd: + self.results['changed'] = True + else: + self.results['changed'] = False + + self.module.exit_json(**self.results) + + +def main(): + + argument_spec = dict( + mode=dict(required=False, + choices=['Manual', 'Dynamic', 'Static'], + type='str'), + trunk_id=dict(required=False, type='int'), + preempt_enable=dict(required=False, type='bool'), + state_flapping=dict(required=False, type='bool'), + port_id_extension_enable=dict(required=False, type='bool'), + unexpected_mac_disable=dict(required=False, type='bool'), + system_id=dict(required=False, type='str'), + timeout_type=dict(required=False, type='str', choices=['Slow', 'Fast']), + fast_timeout=dict(required=False, type='int'), + mixed_rate_link_enable=dict(required=False, type='bool'), + preempt_delay=dict(required=False, type='int'), + collector_delay=dict(required=False, type='int'), + max_active_linknumber=dict(required=False, type='int'), + select=dict(required=False, type='str', choices=['Speed', 'Prority']), + priority=dict(required=False, type='int'), + global_priority=dict(required=False, type='int'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + module = Lacp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_link_status.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_link_status.py new file mode 100644 index 00000000..0e3c467b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_link_status.py @@ -0,0 +1,564 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_link_status +short_description: Get interface link status on HUAWEI CloudEngine switches. +description: + - Get interface link status on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - Current physical state shows an interface's physical status. + - Current link state shows an interface's link layer protocol status. + - Current IPv4 state shows an interface's IPv4 protocol status. + - Current IPv6 state shows an interface's IPv6 protocol status. + - Inbound octets(bytes) shows the number of bytes that an interface received. + - Inbound unicast(pkts) shows the number of unicast packets that an interface received. + - Inbound multicast(pkts) shows the number of multicast packets that an interface received. + - Inbound broadcast(pkts) shows the number of broadcast packets that an interface received. + - Inbound error(pkts) shows the number of error packets that an interface received. + - Inbound drop(pkts) shows the total number of packets that were sent to the interface but dropped by an interface. + - Inbound rate(byte/sec) shows the rate at which an interface receives bytes within an interval. + - Inbound rate(pkts/sec) shows the rate at which an interface receives packets within an interval. + - Outbound octets(bytes) shows the number of the bytes that an interface sent. + - Outbound unicast(pkts) shows the number of unicast packets that an interface sent. + - Outbound multicast(pkts) shows the number of multicast packets that an interface sent. + - Outbound broadcast(pkts) shows the number of broadcast packets that an interface sent. + - Outbound error(pkts) shows the total number of packets that an interface sent but dropped by the remote interface. + - Outbound drop(pkts) shows the number of dropped packets that an interface sent. + - Outbound rate(byte/sec) shows the rate at which an interface sends bytes within an interval. + - Outbound rate(pkts/sec) shows the rate at which an interface sends packets within an interval. + - Speed shows the rate for an Ethernet interface. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - For the interface parameter, you can enter C(all) to display information about all interfaces, + an interface type such as C(40GE) to display information about interfaces of the specified type, + or full name of an interface such as C(40GE1/0/22) or C(vlanif10) + to display information about the specific interface. + required: true +''' + +EXAMPLES = ''' + +- name: Link status test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Get specified interface link status information + community.network.ce_link_status: + interface: 40GE1/0/1 + provider: "{{ cli }}" + + - name: Get specified interface type link status information + community.network.ce_link_status: + interface: 40GE + provider: "{{ cli }}" + + - name: Get all interfaces link status information + community.network.ce_link_status: + interface: all + provider: "{{ cli }}" +''' + +RETURN = ''' +result: + description: Interface link status information + returned: always + type: dict + sample: { + "40ge2/0/8": { + "Current IPv4 state": "down", + "Current IPv6 state": "down", + "Current link state": "up", + "Current physical state": "up", + "Inbound broadcast(pkts)": "0", + "Inbound drop(pkts)": "0", + "Inbound error(pkts)": "0", + "Inbound multicast(pkts)": "20151", + "Inbound octets(bytes)": "7314813", + "Inbound rate(byte/sec)": "11", + "Inbound rate(pkts/sec)": "0", + "Inbound unicast(pkts)": "0", + "Outbound broadcast(pkts)": "1", + "Outbound drop(pkts)": "0", + "Outbound error(pkts)": "0", + "Outbound multicast(pkts)": "20152", + "Outbound octets(bytes)": "7235021", + "Outbound rate(byte/sec)": "11", + "Outbound rate(pkts/sec)": "0", + "Outbound unicast(pkts)": "0", + "Speed": "40GE" + } + } +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, get_nc_next + +CE_NC_GET_PORT_SPEED = """ + + + + + %s + + + + + + + +""" + +CE_NC_GET_INT_STATISTICS = """ + + + + + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +INTERFACE_ALL = 1 +INTERFACE_TYPE = 2 +INTERFACE_FULL_NAME = 3 + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-Port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_ethernet_port(interface): + """Judge whether it is ethernet port""" + + ethernet_port = ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'meth'] + if_type = get_interface_type(interface) + if if_type in ethernet_port: + return True + return False + + +class LinkStatus(object): + """Get interface link status information""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface name + self.interface = self.module.params['interface'] + self.interface = self.interface.replace(' ', '').lower() + self.param_type = None + self.if_type = None + + # state + self.results = dict() + self.result = dict() + + def check_params(self): + """Check all input params""" + + if not self.interface: + self.module.fail_json(msg='Error: Interface name cannot be empty.') + + if self.interface and self.interface != 'all': + if not self.if_type: + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def show_result(self): + """Show result""" + + self.results['result'] = self.result + + self.module.exit_json(**self.results) + + def get_intf_dynamic_info(self, dyn_info, intf_name): + """Get interface dynamic information""" + + if not intf_name: + return + + if dyn_info: + for eles in dyn_info: + if eles.tag in ["ifPhyStatus", "ifV4State", "ifV6State", "ifLinkStatus"]: + if eles.tag == "ifPhyStatus": + self.result[intf_name][ + 'Current physical state'] = eles.text + elif eles.tag == "ifLinkStatus": + self.result[intf_name][ + 'Current link state'] = eles.text + elif eles.tag == "ifV4State": + self.result[intf_name][ + 'Current IPv4 state'] = eles.text + elif eles.tag == "ifV6State": + self.result[intf_name][ + 'Current IPv6 state'] = eles.text + + def get_intf_statistics_info(self, stat_info, intf_name): + """Get interface statistics information""" + + if not intf_name: + return + + if_type = get_interface_type(intf_name) + if if_type == 'fcoe-port' or if_type == 'nve' or if_type == 'tunnel' or \ + if_type == 'vbdif' or if_type == 'vlanif': + return + + if stat_info: + for eles in stat_info: + if eles.tag in ["receiveByte", "sendByte", "rcvUniPacket", "rcvMutiPacket", "rcvBroadPacket", + "sendUniPacket", "sendMutiPacket", "sendBroadPacket", "rcvErrorPacket", + "rcvDropPacket", "sendErrorPacket", "sendDropPacket"]: + if eles.tag == "receiveByte": + self.result[intf_name][ + 'Inbound octets(bytes)'] = eles.text + elif eles.tag == "rcvUniPacket": + self.result[intf_name][ + 'Inbound unicast(pkts)'] = eles.text + elif eles.tag == "rcvMutiPacket": + self.result[intf_name][ + 'Inbound multicast(pkts)'] = eles.text + elif eles.tag == "rcvBroadPacket": + self.result[intf_name][ + 'Inbound broadcast(pkts)'] = eles.text + elif eles.tag == "rcvErrorPacket": + self.result[intf_name][ + 'Inbound error(pkts)'] = eles.text + elif eles.tag == "rcvDropPacket": + self.result[intf_name][ + 'Inbound drop(pkts)'] = eles.text + elif eles.tag == "sendByte": + self.result[intf_name][ + 'Outbound octets(bytes)'] = eles.text + elif eles.tag == "sendUniPacket": + self.result[intf_name][ + 'Outbound unicast(pkts)'] = eles.text + elif eles.tag == "sendMutiPacket": + self.result[intf_name][ + 'Outbound multicast(pkts)'] = eles.text + elif eles.tag == "sendBroadPacket": + self.result[intf_name][ + 'Outbound broadcast(pkts)'] = eles.text + elif eles.tag == "sendErrorPacket": + self.result[intf_name][ + 'Outbound error(pkts)'] = eles.text + elif eles.tag == "sendDropPacket": + self.result[intf_name][ + 'Outbound drop(pkts)'] = eles.text + + def get_intf_cleared_stat(self, clr_stat, intf_name): + """Get interface cleared state information""" + + if not intf_name: + return + + if_type = get_interface_type(intf_name) + if if_type == 'fcoe-port' or if_type == 'nve' or if_type == 'tunnel' or \ + if_type == 'vbdif' or if_type == 'vlanif': + return + + if clr_stat: + for eles in clr_stat: + if eles.tag in ["inByteRate", "inPacketRate", "outByteRate", "outPacketRate"]: + if eles.tag == "inByteRate": + self.result[intf_name][ + 'Inbound rate(byte/sec)'] = eles.text + elif eles.tag == "inPacketRate": + self.result[intf_name][ + 'Inbound rate(pkts/sec)'] = eles.text + elif eles.tag == "outByteRate": + self.result[intf_name][ + 'Outbound rate(byte/sec)'] = eles.text + elif eles.tag == "outPacketRate": + self.result[intf_name][ + 'Outbound rate(pkts/sec)'] = eles.text + + def get_all_interface_info(self, intf_type=None): + """Get interface information by all or by interface type""" + + xml_str = CE_NC_GET_INT_STATISTICS % '' + con_obj = get_nc_next(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + intfs_info = root.findall("ifm/interfaces/interface") + if not intfs_info: + return + + intf_name = '' + flag = False + for eles in intfs_info: + if eles.tag == "interface": + for ele in eles: + if ele.tag in ["ifName", "ifDynamicInfo", "ifStatistics", "ifClearedStat"]: + if ele.tag == "ifName": + intf_name = ele.text.lower() + if intf_type: + if get_interface_type(intf_name) != intf_type.lower(): + break + else: + flag = True + self.init_interface_data(intf_name) + if is_ethernet_port(intf_name): + self.get_port_info(intf_name) + if ele.tag == "ifDynamicInfo": + self.get_intf_dynamic_info(ele, intf_name) + elif ele.tag == "ifStatistics": + self.get_intf_statistics_info(ele, intf_name) + elif ele.tag == "ifClearedStat": + self.get_intf_cleared_stat(ele, intf_name) + if intf_type and not flag: + self.module.fail_json( + msg='Error: %s interface type does not exist.' % intf_type.upper()) + + def get_interface_info(self): + """Get interface information""" + + xml_str = CE_NC_GET_INT_STATISTICS % self.interface.upper() + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + self.module.fail_json( + msg='Error: %s interface does not exist.' % self.interface.upper()) + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + intf_info = root.find("ifm/interfaces/interface") + if intf_info: + for eles in intf_info: + if eles.tag in ["ifDynamicInfo", "ifStatistics", "ifClearedStat"]: + if eles.tag == "ifDynamicInfo": + self.get_intf_dynamic_info(eles, self.interface) + elif eles.tag == "ifStatistics": + self.get_intf_statistics_info(eles, self.interface) + elif eles.tag == "ifClearedStat": + self.get_intf_cleared_stat(eles, self.interface) + + def init_interface_data(self, intf_name): + """Init interface data""" + + # init link status data + self.result[intf_name] = dict() + self.result[intf_name]['Current physical state'] = 'down' + self.result[intf_name]['Current link state'] = 'down' + self.result[intf_name]['Current IPv4 state'] = 'down' + self.result[intf_name]['Current IPv6 state'] = 'down' + self.result[intf_name]['Inbound octets(bytes)'] = '--' + self.result[intf_name]['Inbound unicast(pkts)'] = '--' + self.result[intf_name]['Inbound multicast(pkts)'] = '--' + self.result[intf_name]['Inbound broadcast(pkts)'] = '--' + self.result[intf_name]['Inbound error(pkts)'] = '--' + self.result[intf_name]['Inbound drop(pkts)'] = '--' + self.result[intf_name]['Inbound rate(byte/sec)'] = '--' + self.result[intf_name]['Inbound rate(pkts/sec)'] = '--' + self.result[intf_name]['Outbound octets(bytes)'] = '--' + self.result[intf_name]['Outbound unicast(pkts)'] = '--' + self.result[intf_name]['Outbound multicast(pkts)'] = '--' + self.result[intf_name]['Outbound broadcast(pkts)'] = '--' + self.result[intf_name]['Outbound error(pkts)'] = '--' + self.result[intf_name]['Outbound drop(pkts)'] = '--' + self.result[intf_name]['Outbound rate(byte/sec)'] = '--' + self.result[intf_name]['Outbound rate(pkts/sec)'] = '--' + self.result[intf_name]['Speed'] = '--' + + def get_port_info(self, interface): + """Get port information""" + + if_type = get_interface_type(interface) + if if_type == 'meth': + xml_str = CE_NC_GET_PORT_SPEED % interface.lower().replace('meth', 'MEth') + else: + xml_str = CE_NC_GET_PORT_SPEED % interface.upper() + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + port_info = root.find("devm/ports/port") + if port_info: + for eles in port_info: + if eles.tag == "ethernetPort": + for ele in eles: + if ele.tag == 'speed': + self.result[interface]['Speed'] = ele.text + + def get_link_status(self): + """Get link status information""" + + if self.param_type == INTERFACE_FULL_NAME: + self.init_interface_data(self.interface) + self.get_interface_info() + if is_ethernet_port(self.interface): + self.get_port_info(self.interface) + elif self.param_type == INTERFACE_TYPE: + self.get_all_interface_info(self.interface) + else: + self.get_all_interface_info() + + def get_intf_param_type(self): + """Get the type of input interface parameter""" + + if self.interface == 'all': + self.param_type = INTERFACE_ALL + return + + if self.if_type == self.interface: + self.param_type = INTERFACE_TYPE + return + + self.param_type = INTERFACE_FULL_NAME + + def work(self): + """Worker""" + + self.if_type = get_interface_type(self.interface) + self.check_params() + self.get_intf_param_type() + self.get_link_status() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + ) + argument_spec.update(ce_argument_spec) + linkstatus_obj = LinkStatus(argument_spec) + linkstatus_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp.py new file mode 100644 index 00000000..13f6f7b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp.py @@ -0,0 +1,788 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- + +module: ce_lldp +version_added: '0.2.0' +short_description: Manages LLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages LLDP configuration on HUAWEI CloudEngine switches. +author: + - xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + lldpenable: + description: + - Set global LLDP enable state. + required: false + choices: ['enabled', 'disabled'] + type: str + mdnstatus: + description: + - Set global MDN enable state. + required: false + choices: ['rxOnly', 'disabled'] + type: str + interval: + description: + - Frequency at which LLDP advertisements are sent (in seconds). + required: false + type: int + hold_multiplier: + description: + - Time multiplier for device information in neighbor devices. + required: false + type: int + restart_delay: + description: + - Specifies the delay time of the interface LLDP module from disabled state to re enable. + required: false + type: int + transmit_delay: + description: + - Delay time for sending LLDP messages. + required: false + type: int + notification_interval: + description: + - Suppression time for sending LLDP alarm. + required: false + type: int + fast_count: + description: + - The number of LLDP messages sent to the neighbor nodes by the specified device. + required: false + type: int + mdn_notification_interval: + description: + - Delay time for sending MDN neighbor information change alarm. + required: false + type: int + management_address: + description: + - The management IP address of LLDP. + required: false + default: null + type: str + bind_name: + description: + - Binding interface name. + required: false + default: null + type: str + state: + description: + - Manage the state of the resource. + required: false + default: present + type: str + choices: ['present','absent'] +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + community.network.ce_lldp: + lldpenable: enabled + + - name: "Configure global MDN enable state" + community.network.ce_lldp: + mdnstatus: rxOnly + + - name: "Configure LLDP transmit interval and ensure global LLDP state is already enabled" + community.network.ce_lldp: + enable: enable + interval: 32 + + - name: "Configure LLDP transmit multiplier hold and ensure global LLDP state is already enabled" + community.network.ce_lldp: + enable: enable + hold_multiplier: 5 + + - name: "Configure the delay time of the interface LLDP module from disabled state to re enable" + community.network.ce_lldp: + enable: enable + restart_delay: 3 + + - name: "Reset the delay time for sending LLDP messages" + community.network.ce_lldp: + enable: enable + transmit_delay: 4 + + - name: "Configure device to send neighbor device information change alarm delay time" + community.network.ce_lldp: + lldpenable: enabled + notification_interval: 6 + + - name: "Configure the number of LLDP messages sent to the neighbor nodes by the specified device" + community.network.ce_lldp: + enable: enable + fast_count: 5 + + - name: "Configure the delay time for sending MDN neighbor information change alarm" + community.network.ce_lldp: + enable: enable + mdn_notification_interval: 6 + - name: "Configuring the management IP address of LLDP" + community.network.ce_lldp: + enable: enable + management_address: 10.1.0.1 + + - name: "Configuring LLDP to manage the binding relationship between IP addresses and interfaces" + community.network.ce_lldp: + enable: enable + bind_name: LoopBack2 +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "mdnstatus": "rxOnly", + "interval": "32", + "hold_multiplier": "5", + "restart_delay": "3", + "transmit_delay": "4", + "notification_interval": "6", + "fast_count": "5", + "mdn_notification_interval": "6", + "management_address": "10.1.0.1", + "bind_name": "LoopBack2", + "state": "present" + } +existing: + description: k/v pairs of existing global LLDP configuration. + returned: always + type: dict + sample: { + "lldpenable": "disabled", + "mdnstatus": "disabled" + } +end_state: + description: k/v pairs of global LLDP configuration after module execution. + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "mdnstatus": "rxOnly", + "interval": "32", + "hold_multiplier": "5", + "restart_delay": "3", + "transmit_delay": "4", + "notification_interval": "6", + "fast_count": "5", + "mdn_notification_interval": "6", + "management_address": "10.1.0.1", + "bind_name": "LoopBack2" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "lldp enable", + "lldp mdn enable", + "lldp transmit interval 32", + "lldp transmit multiplier 5", + "lldp restart 3", + "lldp transmit delay 4", + "lldp trap-interval 6", + "lldp fast-count 5", + "lldp mdn trap-interval 6", + "lldp management-address 10.1.0.1", + "lldp management-address bind interface LoopBack 2" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_GLOBAL_LLDP_CONFIG = """ + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER = """ + + + + +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HOLD_MULTIPLIER = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_RESTART_DELAY = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TRANSMIT_DELAY = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_NOTIFICATION_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_FAST_COUNT = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MDN_NOTIFICATION_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MANAGEMENT_ADDRESS = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_BIND_NAME = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL = """ + + + + +""" + + +class Lldp(object): + """Manage global lldp enable configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + self.lldpenable = self.module.params['lldpenable'] or None + self.interval = self.module.params['interval'] or None + self.mdnstatus = self.module.params['mdnstatus'] or None + self.hold_multiplier = self.module.params['hold_multiplier'] or None + self.restart_delay = self.module.params['restart_delay'] or None + self.transmit_delay = self.module.params['transmit_delay'] or None + self.notification_interval = self.module.params['notification_interval'] or None + self.fast_count = self.module.params['fast_count'] or None + self.mdn_notification_interval = self.module.params['mdn_notification_interval'] or None + self.management_address = self.module.params['management_address'] + self.bind_name = self.module.params['bind_name'] + self.state = self.module.params['state'] + self.lldp_conf = dict() + self.conf_exsit = False + self.conf_exsit_lldp = False + self.enable_flag = 0 + self.check_params() + self.existing_state_value = dict() + self.existing_end_state_value = dict() + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def is_valid_v4addr(self): + """check if ipv4 addr is valid""" + if self.management_address.find('.') != -1: + addr_list = self.management_address.split('.') + if self.management_address == "0.0.0.0": + self.module.fail_json(msg='Error: The management address is 0.0.0.0 .') + if len(addr_list) != 4: + self.module.fail_json(msg='Error: Invalid IPV4 address.') + for each_num in addr_list: + each_num_tmp = str(each_num) + if not each_num_tmp.isdigit(): + self.module.fail_json(msg='Error: The ip address is not digit.') + if (int(each_num) > 255) or (int(each_num) < 0): + self.module.fail_json( + msg='Error: The value of ip address is out of [0 - 255].') + else: + self.module.fail_json(msg='Error: Invalid IP address.') + + def check_params(self): + """Check all input params""" + + if self.interval: + if int(self.interval) < 5 or int(self.interval) > 32768: + self.module.fail_json( + msg='Error: The value of interval is out of [5 - 32768].') + + if self.hold_multiplier: + if int(self.hold_multiplier) < 2 or int(self.hold_multiplier) > 10: + self.module.fail_json( + msg='Error: The value of hold_multiplier is out of [2 - 10].') + + if self.restart_delay: + if int(self.restart_delay) < 1 or int(self.restart_delay) > 10: + self.module.fail_json( + msg='Error: The value of restart_delay is out of [1 - 10].') + + if self.transmit_delay: + if int(self.transmit_delay) < 1 or int(self.transmit_delay) > 8192: + self.module.fail_json( + msg='Error: The value of transmit_delay is out of [1 - 8192].') + + if self.notification_interval: + if int(self.notification_interval) < 5 or int(self.notification_interval) > 3600: + self.module.fail_json( + msg='Error: The value of notification_interval is out of [5 - 3600].') + + if self.fast_count: + if int(self.fast_count) < 1 or int(self.fast_count) > 8: + self.module.fail_json( + msg='Error: The value of fast_count is out of [1 - 8].') + + if self.mdn_notification_interval: + if int(self.mdn_notification_interval) < 5 or int(self.mdn_notification_interval) > 3600: + self.module.fail_json( + msg='Error: The value of mdn_notification_interval is out of [5 - 3600].') + + if self.management_address: + self.is_valid_v4addr() + + if self.bind_name: + if (len(self.bind_name) < 1) or (len(self.bind_name) > 63): + self.module.fail_json( + msg='Error: Bind_name length is between 1 and 63.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_lldp(self): + """Configure lldp enabled and mdn enabled parameters""" + + if self.state == 'present': + if (self.enable_flag == 1 and self.lldpenable == 'enabled') and not self.conf_exsit: + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if self.lldpenable == 'enabled' and not self.conf_exsit: + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if (self.enable_flag == 1) and not self.conf_exsit: + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if (self.lldpenable == 'enabled' or self.enable_flag == 1) and not self.conf_exsit_lldp: + if self.hold_multiplier: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HOLD_MULTIPLIER % self.hold_multiplier) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_INTERVAL % self.interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.restart_delay: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_RESTART_DELAY % self.restart_delay) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.transmit_delay: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TRANSMIT_DELAY % self.transmit_delay) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.notification_interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_NOTIFICATION_INTERVAL % self.notification_interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.fast_count: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_FAST_COUNT % self.fast_count) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.mdn_notification_interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MDN_NOTIFICATION_INTERVAL % self.mdn_notification_interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.management_address: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MANAGEMENT_ADDRESS % self.management_address) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.bind_name: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_BIND_NAME % self.bind_name) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.lldpenable == 'disabled' and not self.conf_exsit: + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_DISABLE_CONFIG") + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_lldp_exist_config(self): + """Get lldp existed configure""" + + lldp_config = list() + lldp_dict = dict() + + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable", "mdnStatus"]: + lldp_dict[ele_enable.tag] = ele_enable.text + + if self.state == "present": + cur_lldp_cfg = dict(lldpenable=lldp_dict['lldpEnable'], mdnstatus=lldp_dict['mdnStatus']) + exp_lldp_cfg = dict(lldpenable=self.lldpenable, mdnstatus=self.mdnstatus) + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + if cur_lldp_cfg == exp_lldp_cfg: + self.conf_exsit = True + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'], mdnstatus=lldp_dict['mdnStatus'])) + + conf_str = CE_NC_GET_GLOBAL_LLDP_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + pass + + else: + xml_str = conf_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("lldp/lldpSys/lldpSysParameter") + for nexthop in ntpsite: + for ele in nexthop: + if ele.tag in ["messageTxInterval", "messageTxHoldMultiplier", "reinitDelay", "txDelay", + "notificationInterval", "fastMessageCount", "mdnNotificationInterval", + "configManAddr", "bindifName"]: + lldp_dict[ele.tag] = ele.text + + if self.state == "present": + cur_ntp_cfg = dict(interval=lldp_dict['messageTxInterval'], + hold_multiplier=lldp_dict['messageTxHoldMultiplier'], + restart_delay=lldp_dict['reinitDelay'], + transmit_delay=lldp_dict['txDelay'], + notification_interval=lldp_dict['notificationInterval'], + fast_count=lldp_dict['fastMessageCount'], + mdn_notification_interval=lldp_dict['mdnNotificationInterval'], + management_address=lldp_dict['configManAddr'], + bind_name=lldp_dict['bindifName']) + + exp_ntp_cfg = dict(interval=self.interval, hold_multiplier=self.hold_multiplier, + restart_delay=self.restart_delay, transmit_delay=self.transmit_delay, + notification_interval=self.notification_interval, + fast_count=self.fast_count, mdn_notification_interval=self.mdn_notification_interval, + management_address=self.management_address, bind_name=self.bind_name) + + if cur_ntp_cfg == exp_ntp_cfg: + self.conf_exsit_lldp = True + + lldp_config.append(dict(interval=lldp_dict['messageTxInterval'], + hold_multiplier=lldp_dict['messageTxHoldMultiplier'], + restart_delay=lldp_dict['reinitDelay'], transmit_delay=lldp_dict['txDelay'], + notification_interval=lldp_dict['notificationInterval'], + fast_count=lldp_dict['fastMessageCount'], + mdn_notification_interval=lldp_dict['mdnNotificationInterval'], + management_address=lldp_dict['configManAddr'], + bind_name=lldp_dict['bindifName'])) + + tmp_dict = dict() + str_1 = str(lldp_config) + temp_1 = str_1.replace('[', '').replace(']', '').replace('{', '').replace('}', '').replace('\'', '') + if temp_1: + tmp_2 = temp_1.split(',') + for i in tmp_2: + tmp_value = re.match(r'(.*):(.*)', i) + key_tmp = tmp_value.group(1) + key_value = tmp_value.group(2) + tmp_dict[key_tmp] = key_value + return tmp_dict + + def get_existing(self): + """Get existing info""" + + self.existing = self.get_lldp_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.enable_flag == 1: + if self.lldpenable == 'enabled': + self.proposed = dict(lldpenable=self.lldpenable) + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + elif self.lldpenable == 'disabled': + self.proposed = dict(lldpenable=self.lldpenable) + self.changed = True + else: + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + else: + if self.lldpenable == 'enabled': + self.proposed = dict(lldpenable=self.lldpenable) + self.changed = True + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + if self.enable_flag == 1 or self.lldpenable == 'enabled': + if self.interval: + self.proposed = dict(interval=self.interval) + if self.hold_multiplier: + self.proposed = dict(hold_multiplier=self.hold_multiplier) + if self.restart_delay: + self.proposed = dict(restart_delay=self.restart_delay) + if self.transmit_delay: + self.proposed = dict(transmit_delay=self.transmit_delay) + if self.notification_interval: + self.proposed = dict(notification_interval=self.notification_interval) + if self.fast_count: + self.proposed = dict(fast_count=self.fast_count) + if self.mdn_notification_interval: + self.proposed = dict(mdn_notification_interval=self.mdn_notification_interval) + if self.management_address: + self.proposed = dict(management_address=self.management_address) + if self.bind_name: + self.proposed = dict(bind_name=self.bind_name) + + def get_end_state(self): + """Get end state info""" + + self.end_state = self.get_lldp_exist_config() + existing_key_list = self.existing.keys() + end_state_key_list = self.end_state.keys() + for i in end_state_key_list: + for j in existing_key_list: + if i == j and self.existing[i] != self.end_state[j]: + self.changed = True + + def get_update_cmd(self): + """Get updated commands""" + + if self.conf_exsit and self.conf_exsit_lldp: + return + + if self.state == "present": + if self.lldpenable == "enabled": + self.updates_cmd.append("lldp enable") + + if self.mdnstatus: + self.updates_cmd.append("lldp mdn enable") + if self.mdnstatus == "rxOnly": + self.updates_cmd.append("lldp mdn enable") + else: + self.updates_cmd.append("undo lldp mdn enable") + if self.interval: + self.updates_cmd.append("lldp transmit interval %s" % self.interval) + if self.hold_multiplier: + self.updates_cmd.append("lldp transmit multiplier %s" % self.hold_multiplier) + if self.restart_delay: + self.updates_cmd.append("lldp restart %s" % self.restart_delay) + if self.transmit_delay: + self.updates_cmd.append("lldp transmit delay %s" % self.transmit_delay) + if self.notification_interval: + self.updates_cmd.append("lldp trap-interval %s" % self.notification_interval) + if self.fast_count: + self.updates_cmd.append("lldp fast-count %s" % self.fast_count) + if self.mdn_notification_interval: + self.updates_cmd.append("lldp mdn trap-interval %s" % self.mdn_notification_interval) + if self.management_address: + self.updates_cmd.append("lldp management-address %s" % self.management_address) + if self.bind_name: + self.updates_cmd.append("lldp management-address bind interface %s" % self.bind_name) + elif self.lldpenable == "disabled": + self.updates_cmd.append("undo lldp enable") + else: + if self.enable_flag == 1: + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + self.updates_cmd.append("lldp mdn enable") + else: + self.updates_cmd.append("undo lldp mdn enable") + if self.interval: + self.updates_cmd.append("lldp transmit interval %s" % self.interval) + if self.hold_multiplier: + self.updates_cmd.append("lldp transmit multiplier %s" % self.hold_multiplier) + if self.restart_delay: + self.updates_cmd.append("lldp restart %s" % self.restart_delay) + if self.transmit_delay: + self.updates_cmd.append("lldp transmit delay %s" % self.transmit_delay) + if self.notification_interval: + self.updates_cmd.append("lldp trap-interval %s" % self.notification_interval) + if self.fast_count: + self.updates_cmd.append("lldp fast-count %s" % self.fast_count) + if self.mdn_notification_interval: + self.updates_cmd.append("lldp mdn trap-interval %s" % self.mdn_notification_interval) + if self.management_address: + self.updates_cmd.append("lldp management-address %s" % self.management_address) + if self.bind_name: + self.updates_cmd.append("lldp management-address bind interface %s" % self.bind_name) + + def work(self): + """Execute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_lldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + lldpenable=dict(required=False, choices=['enabled', 'disabled']), + mdnstatus=dict(required=False, choices=['rxOnly', 'disabled']), + interval=dict(required=False, type='int'), + hold_multiplier=dict(required=False, type='int'), + restart_delay=dict(required=False, type='int'), + transmit_delay=dict(required=False, type='int'), + notification_interval=dict(required=False, type='int'), + fast_count=dict(required=False, type='int'), + mdn_notification_interval=dict(required=False, type='int'), + management_address=dict(required=False, type='str'), + bind_name=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + lldp_obj = Lldp(argument_spec) + lldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp_interface.py new file mode 100644 index 00000000..648d9118 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_lldp_interface.py @@ -0,0 +1,1381 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ce_lldp_interface +version_added: '0.2.0' +short_description: Manages INTERFACE LLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages INTERFACE LLDP configuration on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + lldpenable: + description: + - Set global LLDP enable state. + type: str + choices: ['enabled', 'disabled'] + function_lldp_interface_flag: + description: + - Used to distinguish between command line functions. + type: str + choices: ['disableINTERFACE','tlvdisableINTERFACE','tlvenableINTERFACE','intervalINTERFACE'] + type_tlv_disable: + description: + - Used to distinguish between command line functions. + type: str + choices: ['basic_tlv', 'dot3_tlv'] + type_tlv_enable: + description: + - Used to distinguish between command line functions. + type: str + choices: ['dot1_tlv','dcbx'] + lldpadminstatus: + description: + - Set interface lldp enable state. + type: str + choices: ['txOnly', 'rxOnly', 'txAndRx', 'disabled'] + ifname: + description: + - Interface name. + type: str + txinterval: + description: + - LLDP send message interval. + type: int + txprotocolvlanid: + description: + - Set tx protocol vlan id. + type: int + txvlannameid: + description: + - Set tx vlan name id. + type: int + vlannametxenable: + description: + - Set vlan name tx enable or not. + type: bool + manaddrtxenable: + description: + - Make it able to send management address TLV. + type: bool + portdesctxenable: + description: + - Enabling the ability to send a description of TLV. + type: bool + syscaptxenable: + description: + - Enable the ability to send system capabilities TLV. + type: bool + sysdesctxenable: + description: + - Enable the ability to send system description TLV. + type: bool + sysnametxenable: + description: + - Enable the ability to send system name TLV. + type: bool + portvlantxenable: + description: + - Enable port vlan tx. + type: bool + protovlantxenable: + description: + - Enable protocol vlan tx. + type: bool + protoidtxenable: + description: + - Enable the ability to send protocol identity TLV. + type: bool + macphytxenable: + description: + - Enable MAC/PHY configuration and state TLV to be sent. + type: bool + linkaggretxenable: + description: + - Enable the ability to send link aggregation TLV. + type: bool + maxframetxenable: + description: + - Enable the ability to send maximum frame length TLV. + type: bool + eee: + description: + - Enable the ability to send EEE TLV. + type: bool + dcbx: + description: + - Enable the ability to send DCBX TLV. + type: bool + state: + description: + - Manage the state of the resource. + type: str + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + ce_lldp_interface_interface: + lldpenable: enabled + + - name: "Configure interface lldp enable state" + community.network.ce_lldp_interface: + function_lldp_interface_flag: disableINTERFACE + ifname: 10GE1/0/1 + lldpadminstatus: rxOnly + - name: "Configure LLDP transmit interval and ensure global LLDP state is already enabled" + community.network.ce_lldp_interface: + function_lldp_interface_flag: intervalINTERFACE + ifname: 10GE1/0/1 + txinterval: 4 + + - name: "Configure basic-tlv: management-address TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + manaddrtxenable: true + + - name: "Configure basic-tlv: prot description TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + portdesctxenable: true + + - name: "Configure basic-tlv: system capabilities TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + syscaptxenable: true + + - name: "Configure basic-tlv: system description TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + sysdesctxenable: true + + - name: "Configure basic-tlv: system name TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + sysnametxenable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, link aggregation TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + linkAggreTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, MAC/PHY configuration/status TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + macPhyTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, maximum frame size TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + maxFrameTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, EEE TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + eee: true + + - name: "Configure the interface to publish an optional DCBX TLV type " + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvenableINTERFACE + ifname: 10GE1/0/1 + type_tlv_enable: dcbx +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "lldpadminstatus": "rxOnly", + "function_lldp_interface_flag": "tlvenableINTERFACE", + "type_tlv_enable": "dot1_tlv", + "ifname": "10GE1/0/1", + "state": "present" + } +existing: + description: k/v pairs of existing global LLDP configration + returned: always + type: dict + sample: { + "lldpenable": "disabled", + "ifname": "10GE1/0/1", + "lldpadminstatus": "txAndRx" + } +end_state: + description: k/v pairs of global DLDP configration after module execution + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "lldpadminstatus": "rxOnly", + "function_lldp_interface_flag": "tlvenableINTERFACE", + "type_tlv_enable": "dot1_tlv", + "ifname": "10GE1/0/1" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "lldp enable", + "interface 10ge 1/0/1", + "undo lldp disable", + "lldp tlv-enable dot1-tlv vlan-name 4", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_INTERFACE_LLDP_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_LLDP_CONFIG = """ + + + + + %s + %s + + + + +""" + +CE_NC_GET_INTERFACE_INTERVAl_CONFIG = """ + + + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_INTERVAl_CONFIG = """ + + + + + %s + + %s + + + + + +""" + +CE_NC_GET_INTERFACE_TLV_ENABLE_CONFIG = """ + + + + + + + + + + + + + +""" + +CE_NC_GET_INTERFACE_TLV_DISABLE_CONFIG = """ + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER = """ + + + + + %s + +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_PROTOIDTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_DCBX = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MANADDRTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_PORTDESCTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSCAPTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSDESCTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSNAMETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_LINKAGGRETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MACPHYTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MAXFRAMETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_EEE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL = """ + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('PORT-GROUP'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + return iftype.lower() + + +class Lldp_interface(object): + """Manage global lldp enable configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + self.lldpenable = self.module.params['lldpenable'] or None + self.function_lldp_interface_flag = self.module.params['function_lldp_interface_flag'] + self.type_tlv_disable = self.module.params['type_tlv_disable'] + self.type_tlv_enable = self.module.params['type_tlv_enable'] + self.ifname = self.module.params['ifname'] + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.ifname = self.module.params['ifname'] + self.lldpadminstatus = self.module.params['lldpadminstatus'] + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + self.ifname = self.module.params['ifname'] + self.manaddrtxenable = self.module.params['manaddrtxenable'] + self.portdesctxenable = self.module.params['portdesctxenable'] + self.syscaptxenable = self.module.params['syscaptxenable'] + self.sysdesctxenable = self.module.params['sysdesctxenable'] + self.sysnametxenable = self.module.params['sysnametxenable'] + if self.type_tlv_disable == 'dot3_tlv': + self.ifname = self.module.params['ifname'] + self.macphytxenable = self.module.params['macphytxenable'] + self.linkaggretxenable = self.module.params['linkaggretxenable'] + self.maxframetxenable = self.module.params['maxframetxenable'] + self.eee = self.module.params['eee'] + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + self.ifname = self.module.params['ifname'] + self.protoidtxenable = self.module.params['protoidtxenable'] + if self.type_tlv_enable == 'dcbx': + self.ifname = self.module.params['ifname'] + self.dcbx = self.module.params['dcbx'] + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + self.ifname = self.module.params['ifname'] + self.txinterval = self.module.params['txinterval'] + self.state = self.module.params['state'] + + self.lldp_conf = dict() + self.conf_disable_exsit = False + self.conf_interface_lldp_disable_exsit = False + self.conf_interval_exsit = False + self.conf_tlv_disable_exsit = False + self.conf_tlv_enable_exsit = False + self.enable_flag = 0 + self.check_params() + self.existing_state_value = dict() + self.existing_end_state_value = dict() + self.interface_lldp_info = list() + + # state + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_params(self): + """Check all input params""" + + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json(msg='Error: ifname name of %s is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json(msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.txinterval: + if int(self.txinterval) < 1 or int(self.txinterval) > 32768: + self.module.fail_json( + msg='Error: The value of txinterval is out of [1 - 32768].') + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'dot1_tlv': + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + def check_response(self, xml_str, xml_name): + """Check if response message is already OK""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_lldp_enable_pre_config(self): + """Get lldp enable configure""" + + lldp_dict = dict() + lldp_config = list() + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable"]: + lldp_dict[ele_enable.tag] = ele_enable.text + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'])) + return lldp_config + + def get_interface_lldp_disable_pre_config(self): + """Get interface undo lldp disable configure""" + lldp_dict = dict() + interface_lldp_disable_dict = dict() + if self.enable_flag == 1: + conf_enable_str = CE_NC_GET_INTERFACE_LLDP_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + if "" in conf_enable_obj: + return + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_enable_str) + lldp_disable_enable = root.findall("lldp/lldpInterfaces/lldpInterface") + for nexthop_enable in lldp_disable_enable: + name = nexthop_enable.find("ifName") + status = nexthop_enable.find("lldpAdminStatus") + if name is not None and status is not None: + interface_lldp_disable_dict[name.text] = status.text + return interface_lldp_disable_dict + + def get_interface_lldp_disable_config(self): + lldp_config = list() + interface_lldp_disable_dict_tmp = dict() + if self.state == "present": + if self.ifname: + interface_lldp_disable_dict_tmp = self.get_interface_lldp_disable_pre_config() + key_list = interface_lldp_disable_dict_tmp.keys() + if len(key_list) != 0: + for key in key_list: + if key == self.ifname: + if interface_lldp_disable_dict_tmp[key] != self.lldpadminstatus: + self.conf_interface_lldp_disable_exsit = True + else: + self.conf_interface_lldp_disable_exsit = False + elif self.ifname not in key_list: + self.conf_interface_lldp_disable_exsit = True + elif (len(key_list) == 0) and self.ifname and self.lldpadminstatus: + self.conf_interface_lldp_disable_exsit = True + lldp_config.append(interface_lldp_disable_dict_tmp) + return lldp_config + + def get_interface_tlv_disable_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_TLV_DISABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + lldp_tlvdisable_ifname = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in lldp_tlvdisable_ifname: + ifname_tmp = ele.find("ifName") + manaddrtxenable_tmp = ele.find("tlvTxEnable/manAddrTxEnable") + portdesctxenable_tmp = ele.find("tlvTxEnable/portDescTxEnable") + syscaptxenable_tmp = ele.find("tlvTxEnable/sysCapTxEnable") + sysdesctxenable_tmp = ele.find("tlvTxEnable/sysDescTxEnable") + sysnametxenable_tmp = ele.find("tlvTxEnable/sysNameTxEnable") + linkaggretxenable_tmp = ele.find("tlvTxEnable/linkAggreTxEnable") + macphytxenable_tmp = ele.find("tlvTxEnable/macPhyTxEnable") + maxframetxenable_tmp = ele.find("tlvTxEnable/maxFrameTxEnable") + eee_tmp = ele.find("tlvTxEnable/eee") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if ifname_tmp is not None and manaddrtxenable_tmp is not None: + if manaddrtxenable_tmp.text is not None: + cur_interface_mdn_cfg["manaddrtxenable"] = manaddrtxenable_tmp.text + if ifname_tmp is not None and portdesctxenable_tmp is not None: + if portdesctxenable_tmp.text is not None: + cur_interface_mdn_cfg['portdesctxenable'] = portdesctxenable_tmp.text + if ifname_tmp is not None and syscaptxenable_tmp is not None: + if syscaptxenable_tmp.text is not None: + cur_interface_mdn_cfg['syscaptxenable'] = syscaptxenable_tmp.text + if ifname_tmp is not None and sysdesctxenable_tmp is not None: + if sysdesctxenable_tmp.text is not None: + cur_interface_mdn_cfg['sysdesctxenable'] = sysdesctxenable_tmp.text + if ifname_tmp is not None and sysnametxenable_tmp is not None: + if sysnametxenable_tmp.text is not None: + cur_interface_mdn_cfg['sysnametxenable'] = sysnametxenable_tmp.text + if ifname_tmp is not None and linkaggretxenable_tmp is not None: + if linkaggretxenable_tmp.text is not None: + cur_interface_mdn_cfg['linkaggretxenable'] = linkaggretxenable_tmp.text + if ifname_tmp is not None and macphytxenable_tmp is not None: + if macphytxenable_tmp.text is not None: + cur_interface_mdn_cfg['macphytxenable'] = macphytxenable_tmp.text + if ifname_tmp is not None and maxframetxenable_tmp is not None: + if maxframetxenable_tmp.text is not None: + cur_interface_mdn_cfg['maxframetxenable'] = maxframetxenable_tmp.text + if ifname_tmp is not None and eee_tmp is not None: + if eee_tmp.text is not None: + cur_interface_mdn_cfg['eee'] = eee_tmp.text + if self.state == "present": + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.manaddrtxenable: + exp_interface_mdn_cfg['manaddrtxenable'] = self.manaddrtxenable + if self.portdesctxenable: + exp_interface_mdn_cfg['portdesctxenable'] = self.portdesctxenable + if self.syscaptxenable: + exp_interface_mdn_cfg['syscaptxenable'] = self.syscaptxenable + if self.sysdesctxenable: + exp_interface_mdn_cfg['sysdesctxenable'] = self.sysdesctxenable + if self.sysnametxenable: + exp_interface_mdn_cfg['sysnametxenable'] = self.sysnametxenable + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if key == "ifname" and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'])) + if "manaddrtxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(manaddrtxenable=cur_interface_mdn_cfg['manaddrtxenable'])) + if "portdesctxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(portdesctxenable=cur_interface_mdn_cfg['portdesctxenable'])) + if "syscaptxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(syscaptxenable=cur_interface_mdn_cfg['syscaptxenable'])) + if "sysdesctxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(sysdesctxenable=cur_interface_mdn_cfg['sysdesctxenable'])) + if "sysnametxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(sysnametxenable=cur_interface_mdn_cfg['sysnametxenable'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_disable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_disable_exsit = True + return lldp_config + + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.linkaggretxenable: + exp_interface_mdn_cfg['linkaggretxenable'] = self.linkaggretxenable + if self.macphytxenable: + exp_interface_mdn_cfg['macphytxenable'] = self.macphytxenable + if self.maxframetxenable: + exp_interface_mdn_cfg['maxframetxenable'] = self.maxframetxenable + if self.eee: + exp_interface_mdn_cfg['eee'] = self.eee + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if key == "ifname" and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'])) + if "linkaggretxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(linkaggretxenable=cur_interface_mdn_cfg['linkaggretxenable'])) + if "macphytxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(macphytxenable=cur_interface_mdn_cfg['macphytxenable'])) + if "maxframetxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(maxframetxenable=cur_interface_mdn_cfg['maxframetxenable'])) + if "eee" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(eee=cur_interface_mdn_cfg['eee'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_disable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_disable_exsit = True + return lldp_config + return lldp_config + + def get_interface_tlv_enable_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_TLV_ENABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + lldpenablesite = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in lldpenablesite: + ifname_tmp = ele.find("ifName") + protoidtxenable_tmp = ele.find("tlvTxEnable/protoIdTxEnable") + dcbx_tmp = ele.find("tlvTxEnable/dcbx") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if ifname_tmp is not None and protoidtxenable_tmp is not None: + if protoidtxenable_tmp.text is not None: + cur_interface_mdn_cfg["protoidtxenable"] = protoidtxenable_tmp.text + if ifname_tmp is not None and dcbx_tmp is not None: + if dcbx_tmp.text is not None: + cur_interface_mdn_cfg['dcbx'] = dcbx_tmp.text + if self.state == "present": + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.protoidtxenable: + exp_interface_mdn_cfg['protoidtxenable'] = self.protoidtxenable + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "protoidtxenable" == str(key) and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(protoidtxenable=cur_interface_mdn_cfg['protoidtxenable'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_enable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_enable_exsit = True + return lldp_config + if self.type_tlv_enable == 'dcbx': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.dcbx: + exp_interface_mdn_cfg['dcbx'] = self.dcbx + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "dcbx" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(dcbx=cur_interface_mdn_cfg['dcbx'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_enable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_enable_exsit = True + return lldp_config + return lldp_config + + def get_interface_interval_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + interface_lldp_disable_dict_tmp2 = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1: + if interface_lldp_disable_dict_tmp2[self.ifname] != 'disabled': + conf_str = CE_NC_GET_INTERFACE_INTERVAl_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + txintervalsite = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in txintervalsite: + ifname_tmp = ele.find("ifName") + txinterval_tmp = ele.find("msgInterval/txInterval") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if txinterval_tmp is not None: + if txinterval_tmp.text is not None: + cur_interface_mdn_cfg["txinterval"] = txinterval_tmp.text + if self.state == "present": + if self.ifname: + exp_interface_mdn_cfg["ifname"] = self.ifname + if self.txinterval: + exp_interface_mdn_cfg["txinterval"] = self.txinterval + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "txinterval" == str(key) and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'], txinterval=exp_interface_mdn_cfg['txinterval'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_interval_exsit = True + lldp_config.append(cur_interface_mdn_cfg) + return lldp_config + else: + self.conf_interval_exsit = True + return lldp_config + return lldp_config + + def config_global_lldp_enable(self): + if self.state == 'present': + if self.enable_flag == 0 and self.lldpenable == 'enabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.lldpenable == 'disabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + + def config_interface_lldp_disable_config(self): + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.enable_flag == 1 and self.conf_interface_lldp_disable_exsit: + if self.ifname: + xml_str = CE_NC_MERGE_INTERFACE_LLDP_CONFIG % (self.ifname, self.lldpadminstatus) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_LLDP_DISABLE_CONFIG") + self.changed = True + + def config_interface_tlv_disable_config(self): + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.enable_flag == 1 and self.conf_tlv_disable_exsit: + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + if self.portdesctxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_PORTDESCTXENABLE % self.portdesctxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_PORTDESCTXENABLE") + self.changed = True + if self.manaddrtxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MANADDRTXENABLE % self.manaddrtxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MANADDRTXENABLE") + self.changed = True + if self.syscaptxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSCAPTXENABLE % self.syscaptxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSCAPTXENABLE") + self.changed = True + if self.sysdesctxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSDESCTXENABLE % self.sysdesctxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSDESCTXENABLE") + self.changed = True + if self.sysnametxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSNAMETXENABLE % self.sysnametxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSNAMETXENABLE") + self.changed = True + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + if self.linkaggretxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_LINKAGGRETXENABLE % self.linkaggretxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_LINKAGGRETXENABLE") + self.changed = True + if self.macphytxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MACPHYTXENABLE % self.macphytxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MACPHYTXENABLE") + self.changed = True + if self.maxframetxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MAXFRAMETXENABLE % self.maxframetxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MAXFRAMETXENABLE") + self.changed = True + if self.eee: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_EEE % self.eee) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_EEE") + self.changed = True + + def config_interface_tlv_enable_config(self): + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.enable_flag == 1 and self.conf_tlv_enable_exsit: + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + if self.protoidtxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_PROTOIDTXENABLE % self.protoidtxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_ENABLE_DOT1_PORT_VLAN") + self.changed = True + if self.type_tlv_enable == 'dcbx': + if self.ifname: + if self.dcbx: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_DCBX % self.dcbx) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_ENABLE_DCBX_VLAN") + self.changed = True + + def config_interface_interval_config(self): + if self.function_lldp_interface_flag == 'intervalINTERFACE': + tmp = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1 and self.conf_interval_exsit and tmp[self.ifname] != 'disabled': + if self.ifname: + if self.txinterval: + xml_str = CE_NC_MERGE_INTERFACE_INTERVAl_CONFIG % (self.ifname, self.txinterval) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_INTERVAL_CONFIG") + self.changed = True + + def get_existing(self): + """get existing information""" + self.get_lldp_enable_pre_config() + if self.lldpenable: + self.existing['globalLLDPENABLE'] = self.get_lldp_enable_pre_config() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.existing['disableINTERFACE'] = self.get_interface_lldp_disable_config() + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.existing['tlvdisableINTERFACE'] = self.get_interface_tlv_disable_config() + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.existing['tlvenableINTERFACE'] = self.get_interface_tlv_enable_config() + if self.function_lldp_interface_flag == 'intervalINTERFACE': + self.existing['intervalINTERFACE'] = self.get_interface_interval_config() + + def get_proposed(self): + """get proposed""" + if self.lldpenable: + self.proposed = dict(lldpenable=self.lldpenable) + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.enable_flag == 1: + self.proposed = dict(ifname=self.ifname, lldpadminstatus=self.lldpadminstatus) + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.enable_flag == 1: + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + if self.manaddrtxenable: + self.proposed = dict(ifname=self.ifname, manaddrtxenable=self.manaddrtxenable) + if self.portdesctxenable: + self.proposed = dict(ifname=self.ifname, portdesctxenable=self.portdesctxenable) + if self.syscaptxenable: + self.proposed = dict(ifname=self.ifname, syscaptxenable=self.syscaptxenable) + if self.sysdesctxenable: + self.proposed = dict(ifname=self.ifname, sysdesctxenable=self.sysdesctxenable) + if self.sysnametxenable: + self.proposed = dict(ifname=self.ifname, sysnametxenable=self.sysnametxenable) + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + if self.linkaggretxenable: + self.proposed = dict(ifname=self.ifname, linkaggretxenable=self.linkaggretxenable) + if self.macphytxenable: + self.proposed = dict(ifname=self.ifname, macphytxenable=self.macphytxenable) + if self.maxframetxenable: + self.proposed = dict(ifname=self.ifname, maxframetxenable=self.maxframetxenable) + if self.eee: + self.proposed = dict(ifname=self.ifname, eee=self.eee) + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.enable_flag == 1: + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + if self.protoidtxenable: + self.proposed = dict(ifname=self.ifname, protoidtxenable=self.protoidtxenable) + if self.type_tlv_enable == 'dcbx': + if self.ifname: + if self.dcbx: + self.proposed = dict(ifname=self.ifname, dcbx=self.dcbx) + if self.function_lldp_interface_flag == 'intervalINTERFACE': + tmp1 = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1 and tmp1[self.ifname] != 'disabled': + self.proposed = dict(ifname=self.ifname, txinterval=self.txinterval) + + def config_lldp_interface(self): + """config lldp interface""" + if self.lldpenable: + self.config_global_lldp_enable() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.config_interface_lldp_disable_config() + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.config_interface_tlv_disable_config() + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.config_interface_tlv_enable_config() + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + self.config_interface_interval_config() + + def get_end_state(self): + """get end_state information""" + self.get_lldp_enable_pre_config() + if self.lldpenable: + self.end_state['globalLLDPENABLE'] = self.get_lldp_enable_pre_config() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.end_state['disableINTERFACE'] = self.get_interface_lldp_disable_config() + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.end_state['tlvdisableINTERFACE'] = self.get_interface_tlv_disable_config() + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.end_state['tlvenableINTERFACE'] = self.get_interface_tlv_enable_config() + if self.function_lldp_interface_flag == 'intervalINTERFACE': + self.end_state['intervalINTERFACE'] = self.get_interface_interval_config() + + def get_update_cmd(self): + """Get updated commands""" + + cmds = [] + if self.state == "present": + if self.lldpenable == "enabled": + cmds.append("lldp enable") + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.lldpadminstatus == 'disabled': + cmds.append("lldp disable") + else: + cmds.append("undo lldp disable") + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.manaddrtxenable: + if self.manaddrtxenable == "false": + cmds.append("lldp tlv-disable basic-tlv management-address") + if self.manaddrtxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv management-address") + if self.portdesctxenable: + if self.portdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv port-description") + if self.portdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv port-description") + if self.syscaptxenable: + if self.syscaptxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-capability") + if self.syscaptxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-capability") + if self.sysdesctxenable: + if self.sysdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-description") + if self.sysdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-description") + if self.sysnametxenable: + if self.sysnametxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-name") + if self.sysnametxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-name") + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.linkaggretxenable: + if self.linkaggretxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv link-aggregation") + if self.linkaggretxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv link-aggregation") + if self.macphytxenable: + if self.macphytxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv mac-physic") + if self.macphytxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv mac-physic") + if self.maxframetxenable: + if self.maxframetxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv max-frame-size") + if self.maxframetxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv max-frame-size") + if self.eee: + if self.eee == "false": + cmds.append("lldp tlv-disable dot3-tlv eee") + if self.eee == "true": + cmds.append("undo lldp tlv-disable dot3-tlv eee") + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.protoidtxenable: + if self.protoidtxenable == "false": + cmds.append("undo lldp tlv-enable dot1-tlv protocol-identity") + if self.protoidtxenable == "true": + cmds.append("lldp tlv-enable dot1-tlv protocol-identity") + if self.type_tlv_enable == 'dcbx': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.dcbx: + if self.dcbx == "false": + cmds.append("undo lldp tlv-enable dcbx") + if self.dcbx == "true": + cmds.append("lldp tlv-enable dcbx") + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.txinterval: + cmds.append("lldp transmit fast-mode interval %s" % self.txinterval) + elif self.lldpenable == "disabled": + cmds.append("undo lldp enable") + else: + if self.enable_flag == 1: + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.lldpadminstatus == 'disabled': + cmds.append("lldp disable") + else: + cmds.append("undo lldp disable") + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.manaddrtxenable: + if self.manaddrtxenable == "false": + cmds.append("lldp tlv-disable basic-tlv management-address") + if self.manaddrtxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv management-address") + if self.portdesctxenable: + if self.portdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv port-description") + if self.portdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv port-description") + if self.syscaptxenable: + if self.syscaptxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-capability") + if self.syscaptxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-capability") + if self.sysdesctxenable: + if self.sysdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-description") + if self.sysdesctxenable == "true": + cli_str = "%s %s\n" % (cli_str, "undo lldp tlv-disable basic-tlv system-description") + if self.sysnametxenable: + if self.sysnametxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-name") + if self.sysnametxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-name") + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.linkaggretxenable: + if self.linkaggretxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv link-aggregation") + if self.linkaggretxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv link-aggregation") + if self.macphytxenable: + if self.macphytxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv mac-physic") + if self.macphytxenable == "true": + cli_str = "%s %s\n" % (cli_str, "undo lldp tlv-disable dot3-tlv mac-physic") + if self.maxframetxenable: + if self.maxframetxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv max-frame-size") + if self.maxframetxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv max-frame-size") + if self.eee: + if self.eee == "false": + cmds.append("lldp tlv-disable dot3-tlv eee") + if self.eee == "true": + cmds.append("undo lldp tlv-disable dot3-tlv eee") + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.protoidtxenable: + if self.protoidtxenable == "false": + cmds.append("undo lldp tlv-enable dot1-tlv protocol-identity") + if self.protoidtxenable == "true": + cmds.append("lldp tlv-enable dot1-tlv protocol-identity") + if self.type_tlv_enable == 'dcbx': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.dcbx: + if self.dcbx == "false": + cmds.append("undo lldp tlv-enable dcbx") + if self.dcbx == "true": + cmds.append("lldp tlv-enable dcbx") + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.txinterval: + cmds.append("lldp transmit fast-mode interval %s" % self.txinterval) + self.updates_cmd = cmds + + def work(self): + """Execute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_lldp_interface() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function""" + + argument_spec = dict( + lldpenable=dict(choices=['enabled', 'disabled']), + function_lldp_interface_flag=dict(choices=['disableINTERFACE', 'tlvdisableINTERFACE', 'tlvenableINTERFACE', 'intervalINTERFACE'], type='str'), + type_tlv_disable=dict(choices=['basic_tlv', 'dot3_tlv'], type='str'), + type_tlv_enable=dict(choices=['dot1_tlv', 'dcbx'], type='str'), + ifname=dict(type='str'), + lldpadminstatus=dict(choices=['txOnly', 'rxOnly', 'txAndRx', 'disabled'], type='str'), + manaddrtxenable=dict(type='bool'), + portdesctxenable=dict(type='bool'), + syscaptxenable=dict(type='bool'), + sysdesctxenable=dict(type='bool'), + sysnametxenable=dict(type='bool'), + portvlantxenable=dict(type='bool'), + protovlantxenable=dict(type='bool'), + txprotocolvlanid=dict(type='int'), + vlannametxenable=dict(type='bool'), + txvlannameid=dict(type='int'), + txinterval=dict(type='int'), + protoidtxenable=dict(type='bool'), + macphytxenable=dict(type='bool'), + linkaggretxenable=dict(type='bool'), + maxframetxenable=dict(type='bool'), + eee=dict(type='bool'), + dcbx=dict(type='bool'), + state=dict(type='str', choices=['absent', 'present'], default='present'), + ) + + lldp_interface_obj = Lldp_interface(argument_spec) + lldp_interface_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mdn_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mdn_interface.py new file mode 100644 index 00000000..45f588d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mdn_interface.py @@ -0,0 +1,399 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_mdn_interface +version_added: '0.2.0' +short_description: Manages MDN configuration on HUAWEI CloudEngine switches. +description: + - Manages MDN configuration on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +options: + lldpenable: + description: + - Set global LLDP enable state. + type: str + choices: ['enabled', 'disabled'] + mdnstatus: + description: + - Set interface MDN enable state. + type: str + choices: ['rxOnly', 'disabled'] + ifname: + description: + - Interface name. + type: str + state: + description: + - Manage the state of the resource. + default: present + type: str + choices: ['present','absent'] +notes: + - This module requires the netconf system service be enabled on + the remote device being managed. + - This module works with connection C(netconf). +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + community.network.ce_mdn_interface: + lldpenable: enabled + + - name: "Configure interface MDN enable state" + community.network.ce_mdn_interface: + ifname: 10GE1/0/1 + mdnstatus: rxOnly +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "rxOnly", + "state":"present" + } +existing: + description: k/v pairs of existing global LLDP configration + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "disabled" + } +end_state: + description: k/v pairs of global LLDP configration after module execution + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "rxOnly" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "interface 10ge 1/0/1", + "lldp mdn enable", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config, execute_nc_action + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_INTERFACE_MDNENABLE_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_MDNENABLE_CONFIG = """ + + + + + %s + %s + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('PORT-GROUP'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + return iftype.lower() + + +class Interface_mdn(object): + """Manage global lldp enable configration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # LLDP global configration info + self.lldpenable = self.module.params['lldpenable'] or None + self.ifname = self.module.params['ifname'] + self.mdnstatus = self.module.params['mdnstatus'] or None + self.state = self.module.params['state'] + self.lldp_conf = dict() + self.conf_exsit = False + self.enable_flag = 0 + self.check_params() + + # state + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_params(self): + """Check all input params""" + + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_interface_mdn(self): + """Configure lldp enabled and interface mdn enabled parameters""" + + if self.state == 'present': + if self.enable_flag == 0 and self.lldpenable == 'enabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.lldpenable == 'disabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.conf_exsit: + xml_str = CE_NC_MERGE_INTERFACE_MDNENABLE_CONFIG % (self.ifname, self.mdnstatus) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_MDN_ENABLE_CONFIG") + self.changed = True + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_interface_mdn_exist_config(self): + """Get lldp existed configure""" + + lldp_config = list() + lldp_dict = dict() + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable"]: + lldp_dict[ele_enable.tag] = ele_enable.text + + if self.state == "present": + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'])) + + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_MDNENABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("lldp/mdnInterfaces/mdnInterface") + for nexthop in ntpsite: + for ele in nexthop: + if ele.tag in ["ifName", "mdnStatus"]: + lldp_dict[ele.tag] = ele.text + if self.state == "present": + cur_interface_mdn_cfg = dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus']) + exp_interface_mdn_cfg = dict(ifname=self.ifname, mdnstatus=self.mdnstatus) + if self.ifname == lldp_dict['ifName']: + if cur_interface_mdn_cfg != exp_interface_mdn_cfg: + self.conf_exsit = True + lldp_config.append(dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus'])) + return lldp_config + lldp_config.append(dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus'])) + return lldp_config + + def get_existing(self): + """Get existing info""" + + self.existing = self.get_interface_mdn_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.lldpenable: + self.proposed = dict(lldpenable=self.lldpenable) + if self.enable_flag == 1: + if self.ifname: + self.proposed = dict(ifname=self.ifname, mdnstatus=self.mdnstatus) + + def get_end_state(self): + """Get end state info""" + + self.end_state = self.get_interface_mdn_exist_config() + + def get_update_cmd(self): + """Get updated commands""" + + update_list = list() + if self.state == "present": + if self.lldpenable == "enabled": + cli_str = "lldp enable" + update_list.append(cli_str) + if self.ifname: + cli_str = "%s %s" % ("interface", self.ifname) + update_list.append(cli_str) + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + cli_str = "lldp mdn enable" + update_list.append(cli_str) + else: + cli_str = "undo lldp mdn enable" + update_list.append(cli_str) + + elif self.lldpenable == "disabled": + cli_str = "undo lldp enable" + update_list.append(cli_str) + else: + if self.enable_flag == 1: + if self.ifname: + cli_str = "%s %s" % ("interface", self.ifname) + update_list.append(cli_str) + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + cli_str = "lldp mdn enable" + update_list.append(cli_str) + else: + cli_str = "undo lldp mdn enable" + update_list.append(cli_str) + + self.updates_cmd.append(update_list) + + def work(self): + """Excute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_interface_mdn() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + lldpenable=dict(type='str', choices=['enabled', 'disabled']), + mdnstatus=dict(type='str', choices=['rxOnly', 'disabled']), + ifname=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + lldp_obj = Interface_mdn(argument_spec) + lldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_config.py new file mode 100644 index 00000000..b3141188 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_config.py @@ -0,0 +1,912 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mlag_config +short_description: Manages MLAG configuration on HUAWEI CloudEngine switches. +description: + - Manages MLAG configuration on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + dfs_group_id: + description: + - ID of a DFS group. The value is 1. + default: present + nickname: + description: + - The nickname bound to a DFS group. The value is an integer that ranges from 1 to 65471. + pseudo_nickname: + description: + - A pseudo nickname of a DFS group. The value is an integer that ranges from 1 to 65471. + pseudo_priority: + description: + - The priority of a pseudo nickname. The value is an integer that ranges from 128 to 255. + The default value is 192. A larger value indicates a higher priority. + ip_address: + description: + - IP address bound to the DFS group. The value is in dotted decimal notation. + vpn_instance_name: + description: + - Name of the VPN instance bound to the DFS group. The value is a string of 1 to 31 case-sensitive + characters without spaces. If the character string is quoted by double quotation marks, the character + string can contain spaces. The value _public_ is reserved and cannot be used as the VPN instance name. + priority_id: + description: + - Priority of a DFS group. The value is an integer that ranges from 1 to 254. The default value is 100. + eth_trunk_id: + description: + - Name of the peer-link interface. The value is in the range from 0 to 511. + peer_link_id: + description: + - Number of the peer-link interface. The value is 1. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Mlag config module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create DFS Group id + community.network.ce_mlag_config: + dfs_group_id: 1 + provider: "{{ cli }}" + - name: Set dfs-group priority + community.network.ce_mlag_config: + dfs_group_id: 1 + priority_id: 3 + state: present + provider: "{{ cli }}" + - name: Set pseudo nickname + community.network.ce_mlag_config: + dfs_group_id: 1 + pseudo_nickname: 3 + pseudo_priority: 130 + state: present + provider: "{{ cli }}" + - name: Set ip + community.network.ce_mlag_config: + dfs_group_id: 1 + ip_address: 11.1.1.2 + vpn_instance_name: 6 + provider: "{{ cli }}" + - name: Set peer link + community.network.ce_mlag_config: + eth_trunk_id: 3 + peer_link_id: 2 + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { "eth_trunk_id": "3", + "peer_link_id": "1", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: { "eth_trunk_id": "Eth-Trunk3", + "peer_link_id": "1"} +updates: + description: command sent to the device + returned: always + type: list + sample: {"peer-link 1"} +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_DFS_GROUP_INFO = """ + + + + + + + + + + + + + + + + + +""" +CE_NC_GET_PEER_LINK_INFO = """ + + + + + + + + + + + +""" + +CE_NC_CREATE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_CREATE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_MERGE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER = """ + + + + + %s +""" + +CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL = """ + + + + +""" + +CE_NC_DELETE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_DELETE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_CREATE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" + +CE_NC_MERGE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" +CE_NC_DELETE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class MlagConfig(object): + """ + Manages Manages MLAG config information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.dfs_group_id = self.module.params['dfs_group_id'] + self.nickname = self.module.params['nickname'] + self.pseudo_nickname = self.module.params['pseudo_nickname'] + self.pseudo_priority = self.module.params['pseudo_priority'] + self.ip_address = self.module.params['ip_address'] + self.vpn_instance_name = self.module.params['vpn_instance_name'] + self.priority_id = self.module.params['priority_id'] + self.eth_trunk_id = self.module.params['eth_trunk_id'] + self.peer_link_id = self.module.params['peer_link_id'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + self.commands = list() + # DFS group info + self.dfs_group_info = None + # peer link info + self.peer_link_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed.""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dfs_group_info(self): + """ get dfs group attributes info.""" + + dfs_group_info = dict() + conf_str = CE_NC_GET_DFS_GROUP_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return dfs_group_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + dfs_info = root.findall( + "dfs/groupInstances/groupInstance") + if dfs_info: + for tmp in dfs_info: + for site in tmp: + if site.tag in ["groupId", "priority", "ipAddress", "srcVpnName"]: + dfs_group_info[site.tag] = site.text + + dfs_nick_info = root.findall( + "dfs/groupInstances/groupInstance/trillType") + + if dfs_nick_info: + for tmp in dfs_nick_info: + for site in tmp: + if site.tag in ["localNickname", "pseudoNickname", "pseudoPriority"]: + dfs_group_info[site.tag] = site.text + return dfs_group_info + + def get_peer_link_info(self): + """ get peer link info.""" + + peer_link_info = dict() + conf_str = CE_NC_GET_PEER_LINK_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return peer_link_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + link_info = root.findall( + "mlag/peerlinks/peerlink") + if link_info: + for tmp in link_info: + for site in tmp: + if site.tag in ["linkId", "portName"]: + peer_link_info[site.tag] = site.text + return peer_link_info + + def is_dfs_group_info_change(self): + """whether dfs group info""" + if not self.dfs_group_info: + return False + + if self.priority_id and self.dfs_group_info["priority"] != self.priority_id: + return True + if self.ip_address and self.dfs_group_info["ipAddress"] != self.ip_address: + return True + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] != self.vpn_instance_name: + return True + if self.nickname and self.dfs_group_info["localNickname"] != self.nickname: + return True + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] != self.pseudo_nickname: + return True + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] != self.pseudo_priority: + return True + return False + + def check_dfs_group_info_change(self): + """check dfs group info""" + if not self.dfs_group_info: + return True + + if self.priority_id and self.dfs_group_info["priority"] == self.priority_id: + return True + if self.ip_address and self.dfs_group_info["ipAddress"] == self.ip_address: + return True + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] == self.vpn_instance_name: + return True + if self.nickname and self.dfs_group_info["localNickname"] == self.nickname: + return True + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] == self.pseudo_nickname: + return True + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] == self.pseudo_priority: + return True + return False + + def modify_dfs_group(self): + """modify dfs group info""" + + if self.is_dfs_group_info_change(): + + conf_str = CE_NC_MERGE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + if self.priority_id and self.dfs_group_info["priority"] != self.priority_id: + conf_str += "%s" % self.priority_id + if self.ip_address and self.dfs_group_info["ipAddress"] != self.ip_address: + conf_str += "%s" % self.ip_address + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] != self.vpn_instance_name: + if not self.ip_address: + self.module.fail_json( + msg='Error: ip_address can not be null if vpn_instance_name is exist.') + conf_str += "%s" % self.vpn_instance_name + + if self.nickname or self.pseudo_nickname or self.pseudo_priority: + conf_str += "" + if self.nickname and self.dfs_group_info["localNickname"] != self.nickname: + conf_str += "%s" % self.nickname + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] != self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] != self.pseudo_priority: + if not self.pseudo_nickname: + self.module.fail_json( + msg='Error: pseudo_nickname can not be null if pseudo_priority is exist.') + conf_str += "%s" % self.pseudo_priority + conf_str += "" + + conf_str += CE_NC_MERGE_DFS_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge DFS group info failed.') + + self.updates_cmd.append("dfs-group 1") + if self.priority_id: + self.updates_cmd.append("priority %s" % self.priority_id) + if self.ip_address: + if self.vpn_instance_name: + self.updates_cmd.append( + "source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + self.updates_cmd.append("source ip %s" % self.ip_address) + if self.nickname: + self.updates_cmd.append("source nickname %s" % self.nickname) + if self.pseudo_nickname: + if self.pseudo_priority: + self.updates_cmd.append( + "pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + else: + self.updates_cmd.append( + "pseudo-nickname %s" % self.pseudo_nickname) + + self.changed = True + + def create_dfs_group(self): + """create dfs group info""" + + conf_str = CE_NC_CREATE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + if self.priority_id and self.priority_id != 100: + conf_str += "%s" % self.priority_id + if self.ip_address: + conf_str += "%s" % self.ip_address + if self.vpn_instance_name: + if not self.ip_address: + self.module.fail_json( + msg='Error: ip_address can not be null if vpn_instance_name is exist.') + conf_str += "%s" % self.vpn_instance_name + + if self.nickname or self.pseudo_nickname or self.pseudo_priority: + conf_str += "" + if self.nickname: + conf_str += "%s" % self.nickname + if self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + if self.pseudo_priority: + if not self.pseudo_nickname: + self.module.fail_json( + msg='Error: pseudo_nickname can not be null if pseudo_priority is exist.') + conf_str += "%s" % self.pseudo_priority + conf_str += "" + + conf_str += CE_NC_CREATE_DFS_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge DFS group info failed.') + + self.updates_cmd.append("dfs-group 1") + if self.priority_id: + self.updates_cmd.append("priority %s" % self.priority_id) + if self.ip_address: + if self.vpn_instance_name: + self.updates_cmd.append( + "source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + self.updates_cmd.append("source ip %s" % self.ip_address) + if self.nickname: + self.updates_cmd.append("source nickname %s" % self.nickname) + if self.pseudo_nickname: + if self.pseudo_priority: + self.updates_cmd.append( + "pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + else: + self.updates_cmd.append( + "pseudo-nickname %s" % self.pseudo_nickname) + + self.changed = True + + def delete_dfs_group(self): + """delete dfg group""" + + conf_str = CE_NC_DELETE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + conf_str += CE_NC_DELETE_DFS_GROUP_INFO_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group id failed.') + self.updates_cmd.append("undo dfs-group 1") + self.changed = True + + def delete_dfs_group_attribute(self): + """delete dfg group attribute info""" + + conf_str = CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER % self.dfs_group_id + change = False + if self.priority_id and self.dfs_group_info["priority"] == self.priority_id: + conf_str += "%s" % self.priority_id + change = True + self.updates_cmd.append("undo priority %s" % self.priority_id) + if self.ip_address and self.dfs_group_info["ipAddress"] == self.ip_address: + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] == self.vpn_instance_name: + conf_str += "%s" % self.ip_address + conf_str += "%s" % self.vpn_instance_name + self.updates_cmd.append( + "undo source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + conf_str += "%s" % self.ip_address + self.updates_cmd.append("undo source ip %s" % self.ip_address) + change = True + + conf_str += CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL + + if change: + self.updates_cmd.append("undo dfs-group 1") + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group attribute failed.') + self.changed = True + + def delete_dfs_group_nick(self): + + conf_str = CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER % self.dfs_group_id + conf_str = conf_str.replace('', '') + change = False + + if self.nickname or self.pseudo_nickname: + conf_str += "" + if self.nickname and self.dfs_group_info["localNickname"] == self.nickname: + conf_str += "%s" % self.nickname + change = True + self.updates_cmd.append("undo source nickname %s" % self.nickname) + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] == self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] == self.pseudo_priority: + self.updates_cmd.append( + "undo pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + if not self.pseudo_priority: + self.updates_cmd.append( + "undo pseudo-nickname %s" % self.pseudo_nickname) + change = True + conf_str += "" + + conf_str += CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL + + if change: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group attribute failed.') + self.changed = True + + def modify_peer_link(self): + """modify peer link info""" + + eth_trunk_id = "Eth-Trunk" + eth_trunk_id += self.eth_trunk_id + if self.eth_trunk_id and eth_trunk_id != self.peer_link_info.get("portName"): + conf_str = CE_NC_MERGE_PEER_LINK_INFO % ( + self.peer_link_id, eth_trunk_id) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge peer link failed.') + self.updates_cmd.append("peer-link %s" % self.peer_link_id) + self.changed = True + + def delete_peer_link(self): + """delete peer link info""" + + eth_trunk_id = "Eth-Trunk" + eth_trunk_id += self.eth_trunk_id + if self.eth_trunk_id and eth_trunk_id == self.peer_link_info.get("portName"): + conf_str = CE_NC_DELETE_PEER_LINK_INFO % ( + self.peer_link_id, eth_trunk_id) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete peer link failed.') + self.updates_cmd.append("undo peer-link %s" % self.peer_link_id) + self.changed = True + + def check_params(self): + """Check all input params""" + + # dfs_group_id check + if self.dfs_group_id: + if self.dfs_group_id != "1": + self.module.fail_json( + msg='Error: The value of dfs_group_id must be 1.') + + # nickname check + if self.nickname: + if not self.nickname.isdigit(): + self.module.fail_json( + msg='Error: The value of nickname is an integer.') + if int(self.nickname) < 1 or int(self.nickname) > 65471: + self.module.fail_json( + msg='Error: The nickname is not in the range from 1 to 65471.') + + # pseudo_nickname check + if self.pseudo_nickname: + if not self.pseudo_nickname.isdigit(): + self.module.fail_json( + msg='Error: The value of pseudo_nickname is an integer.') + if int(self.pseudo_nickname) < 1 or int(self.pseudo_nickname) > 65471: + self.module.fail_json( + msg='Error: The pseudo_nickname is not in the range from 1 to 65471.') + + # pseudo_priority check + if self.pseudo_priority: + if not self.pseudo_priority.isdigit(): + self.module.fail_json( + msg='Error: The value of pseudo_priority is an integer.') + if int(self.pseudo_priority) < 128 or int(self.pseudo_priority) > 255: + self.module.fail_json( + msg='Error: The pseudo_priority is not in the range from 128 to 255.') + + # ip_address check + if self.ip_address: + if not is_valid_address(self.ip_address): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.ip_address) + + # vpn_instance_name check + if self.vpn_instance_name: + if len(self.vpn_instance_name) > 31 \ + or len(self.vpn_instance_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: The length of vpn_instance_name is not in the range from 1 to 31.') + + # priority_id check + if self.priority_id: + if not self.priority_id.isdigit(): + self.module.fail_json( + msg='Error: The value of priority_id is an integer.') + if int(self.priority_id) < 1 or int(self.priority_id) > 254: + self.module.fail_json( + msg='Error: The priority_id is not in the range from 1 to 254.') + + # peer_link_id check + if self.peer_link_id: + if self.peer_link_id != "1": + self.module.fail_json( + msg='Error: The value of peer_link_id must be 1.') + + # eth_trunk_id check + if self.eth_trunk_id: + if not self.eth_trunk_id.isdigit(): + self.module.fail_json( + msg='Error: The value of eth_trunk_id is an integer.') + if int(self.eth_trunk_id) < 0 or int(self.eth_trunk_id) > 511: + self.module.fail_json( + msg='Error: The value of eth_trunk_id is not in the range from 0 to 511.') + + def get_proposed(self): + """get proposed info""" + + if self.dfs_group_id: + self.proposed["dfs_group_id"] = self.dfs_group_id + if self.nickname: + self.proposed["nickname"] = self.nickname + if self.pseudo_nickname: + self.proposed["pseudo_nickname"] = self.pseudo_nickname + if self.pseudo_priority: + self.proposed["pseudo_priority"] = self.pseudo_priority + if self.ip_address: + self.proposed["ip_address"] = self.ip_address + if self.vpn_instance_name: + self.proposed["vpn_instance_name"] = self.vpn_instance_name + if self.priority_id: + self.proposed["priority_id"] = self.priority_id + if self.eth_trunk_id: + self.proposed["eth_trunk_id"] = self.eth_trunk_id + if self.peer_link_id: + self.proposed["peer_link_id"] = self.peer_link_id + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + if self.dfs_group_id: + self.dfs_group_info = self.get_dfs_group_info() + if self.peer_link_id and self.eth_trunk_id: + self.peer_link_info = self.get_peer_link_info() + if self.dfs_group_info: + if self.dfs_group_id: + self.existing["dfs_group_id"] = self.dfs_group_info["groupId"] + if self.nickname: + self.existing["nickname"] = self.dfs_group_info[ + "localNickname"] + if self.pseudo_nickname: + self.existing["pseudo_nickname"] = self.dfs_group_info[ + "pseudoNickname"] + if self.pseudo_priority: + self.existing["pseudo_priority"] = self.dfs_group_info[ + "pseudoPriority"] + if self.ip_address: + self.existing["ip_address"] = self.dfs_group_info["ipAddress"] + if self.vpn_instance_name: + self.existing["vpn_instance_name"] = self.dfs_group_info[ + "srcVpnName"] + if self.priority_id: + self.existing["priority_id"] = self.dfs_group_info["priority"] + if self.peer_link_info: + if self.eth_trunk_id: + self.existing["eth_trunk_id"] = self.peer_link_info["portName"] + if self.peer_link_id: + self.existing["peer_link_id"] = self.peer_link_info["linkId"] + + def get_end_state(self): + """get end state info""" + if self.dfs_group_id: + self.dfs_group_info = self.get_dfs_group_info() + if self.peer_link_id and self.eth_trunk_id: + self.peer_link_info = self.get_peer_link_info() + + if self.dfs_group_info: + if self.dfs_group_id: + self.end_state["dfs_group_id"] = self.dfs_group_info["groupId"] + if self.nickname: + self.end_state["nickname"] = self.dfs_group_info[ + "localNickname"] + if self.pseudo_nickname: + self.end_state["pseudo_nickname"] = self.dfs_group_info[ + "pseudoNickname"] + if self.pseudo_priority: + self.end_state["pseudo_priority"] = self.dfs_group_info[ + "pseudoPriority"] + if self.ip_address: + self.end_state["ip_address"] = self.dfs_group_info["ipAddress"] + if self.vpn_instance_name: + self.end_state["vpn_instance_name"] = self.dfs_group_info[ + "srcVpnName"] + if self.priority_id: + self.end_state["priority_id"] = self.dfs_group_info["priority"] + if self.peer_link_info: + if self.eth_trunk_id: + self.end_state[ + "eth_trunk_id"] = self.peer_link_info["portName"] + if self.peer_link_id: + self.end_state["peer_link_id"] = self.peer_link_info["linkId"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + if self.dfs_group_id: + if self.state == "present": + if self.dfs_group_info: + if self.nickname or self.pseudo_nickname or self.pseudo_priority or self.priority_id \ + or self.ip_address or self.vpn_instance_name: + if self.nickname: + if self.dfs_group_info["ipAddress"] not in ["0.0.0.0", None]: + self.module.fail_json(msg='Error: nickname and ip_address can not be exist at the ' + 'same time.') + if self.ip_address: + if self.dfs_group_info["localNickname"] not in ["0", None]: + self.module.fail_json(msg='Error: nickname and ip_address can not be exist at the ' + 'same time.') + self.modify_dfs_group() + else: + self.create_dfs_group() + else: + if not self.dfs_group_info: + self.module.fail_json( + msg='Error: DFS Group does not exist.') + if not self.nickname and not self.pseudo_nickname and not self.pseudo_priority and not self.priority_id\ + and not self.ip_address and not self.vpn_instance_name: + self.delete_dfs_group() + else: + self.updates_cmd.append("dfs-group 1") + self.delete_dfs_group_attribute() + self.delete_dfs_group_nick() + if "undo dfs-group 1" in self.updates_cmd: + self.updates_cmd = ["undo dfs-group 1"] + + if self.eth_trunk_id and not self.peer_link_id: + self.module.fail_json( + msg='Error: eth_trunk_id and peer_link_id must be config at the same time.') + if self.peer_link_id and not self.eth_trunk_id: + self.module.fail_json( + msg='Error: eth_trunk_id and peer_link_id must be config at the same time.') + + if self.eth_trunk_id and self.peer_link_id: + if self.state == "present": + self.modify_peer_link() + else: + if self.peer_link_info: + self.delete_peer_link() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + dfs_group_id=dict(type='str'), + nickname=dict(type='str'), + pseudo_nickname=dict(type='str'), + pseudo_priority=dict(type='str'), + ip_address=dict(type='str'), + vpn_instance_name=dict(type='str'), + priority_id=dict(type='str'), + eth_trunk_id=dict(type='str'), + peer_link_id=dict(type='str'), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = MlagConfig(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_interface.py new file mode 100644 index 00000000..0c2f07cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mlag_interface.py @@ -0,0 +1,1038 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mlag_interface +short_description: Manages MLAG interfaces on HUAWEI CloudEngine switches. +description: + - Manages MLAG interface attributes on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + eth_trunk_id: + description: + - Name of the local M-LAG interface. The value is ranging from 0 to 511. + dfs_group_id: + description: + - ID of a DFS group.The value is 1. + default: present + mlag_id: + description: + - ID of the M-LAG. The value is an integer that ranges from 1 to 2048. + mlag_system_id: + description: + - M-LAG global LACP system MAC address. The value is a string of 0 to 255 characters. The default value + is the MAC address of the Ethernet port of MPU. + mlag_priority_id: + description: + - M-LAG global LACP system priority. The value is an integer ranging from 0 to 65535. + The default value is 32768. + interface: + description: + - Name of the interface that enters the Error-Down state when the peer-link fails. + The value is a string of 1 to 63 characters. + mlag_error_down: + description: + - Configure the interface on the slave device to enter the Error-Down state. + choices: ['enable','disable'] + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + +''' + +EXAMPLES = ''' +- name: Mlag interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Set interface mlag error down + community.network.ce_mlag_interface: + interface: 10GE2/0/1 + mlag_error_down: enable + provider: "{{ cli }}" + - name: Create mlag + community.network.ce_mlag_interface: + eth_trunk_id: 1 + dfs_group_id: 1 + mlag_id: 4 + provider: "{{ cli }}" + - name: Set mlag global attribute + community.network.ce_mlag_interface: + mlag_system_id: 0020-1409-0407 + mlag_priority_id: 5 + provider: "{{ cli }}" + - name: Set mlag interface attribute + community.network.ce_mlag_interface: + eth_trunk_id: 1 + mlag_system_id: 0020-1409-0400 + mlag_priority_id: 3 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { "interface": "eth-trunk1", + "mlag_error_down": "disable", + "state": "present" + } +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { "mlagErrorDownInfos": [ + { + "dfsgroupId": "1", + "portName": "Eth-Trunk1" + } + ] + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: { "interface eth-trunk1", + "undo m-lag unpaired-port suspend"} +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_MLAG_INFO = """ + + + + + %s + + + + +""" + +CE_NC_CREATE_MLAG_INFO = """ + + + + + %s + %s + %s + + + + +""" + +CE_NC_DELETE_MLAG_INFO = """ + + + + + %s + %s + + + + +""" + +CE_NC_GET_LACP_MLAG_INFO = """ + + + + + %s + + + + + + + + +""" + +CE_NC_SET_LACP_MLAG_INFO_HEAD = """ + + + + + %s + +""" + +CE_NC_SET_LACP_MLAG_INFO_TAIL = """ + + + + + +""" + +CE_NC_GET_GLOBAL_LACP_MLAG_INFO = """ + + + + + + + + + + +""" + +CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD = """ + + + + +""" + +CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL = """ + + + + +""" + +CE_NC_GET_MLAG_ERROR_DOWN_INFO = """ + + + + + + + + + + + + +""" + +CE_NC_CREATE_MLAG_ERROR_DOWN_INFO = """ + + + + + 1 + %s + + + + +""" + +CE_NC_DELETE_MLAG_ERROR_DOWN_INFO = """ + + + + + 1 + %s + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class MlagInterface(object): + """ + Manages Manages MLAG interface information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.eth_trunk_id = self.module.params['eth_trunk_id'] + self.dfs_group_id = self.module.params['dfs_group_id'] + self.mlag_id = self.module.params['mlag_id'] + self.mlag_system_id = self.module.params['mlag_system_id'] + self.mlag_priority_id = self.module.params['mlag_priority_id'] + self.interface = self.module.params['interface'] + self.mlag_error_down = self.module.params['mlag_error_down'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # mlag info + self.commands = list() + self.mlag_info = None + self.mlag_global_info = None + self.mlag_error_down_info = None + self.mlag_trunk_attribute_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_mlag_info(self): + """ get mlag info.""" + + mlag_info = dict() + conf_str = CE_NC_GET_MLAG_INFO % ("Eth-Trunk%s" % self.eth_trunk_id) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + mlag_info["mlagInfos"] = list() + root = ElementTree.fromstring(xml_str) + dfs_mlag_infos = root.findall( + "./mlag/mlagInstances/mlagInstance") + + if dfs_mlag_infos: + for dfs_mlag_info in dfs_mlag_infos: + mlag_dict = dict() + for ele in dfs_mlag_info: + if ele.tag in ["dfsgroupId", "mlagId", "localMlagPort"]: + mlag_dict[ele.tag] = ele.text + mlag_info["mlagInfos"].append(mlag_dict) + return mlag_info + + def get_mlag_global_info(self): + """ get mlag global info.""" + + mlag_global_info = dict() + conf_str = CE_NC_GET_GLOBAL_LACP_MLAG_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "./ifmtrunk/lacpSysInfo/lacpMlagGlobal") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["lacpMlagSysId", "lacpMlagPriority"]: + mlag_global_info[site.tag] = site.text + return mlag_global_info + + def get_mlag_trunk_attribute_info(self): + """ get mlag global info.""" + + mlag_trunk_attribute_info = dict() + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + conf_str = CE_NC_GET_LACP_MLAG_INFO % eth_trunk + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_trunk_attribute_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "./ifmtrunk/TrunkIfs/TrunkIf/lacpMlagIf") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["lacpMlagSysId", "lacpMlagPriority"]: + mlag_trunk_attribute_info[site.tag] = site.text + return mlag_trunk_attribute_info + + def get_mlag_error_down_info(self): + """ get error down info.""" + + mlag_error_down_info = dict() + conf_str = CE_NC_GET_MLAG_ERROR_DOWN_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_error_down_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + mlag_error_down_info["mlagErrorDownInfos"] = list() + root = ElementTree.fromstring(xml_str) + mlag_error_infos = root.findall( + "./mlag/errordowns/errordown") + + if mlag_error_infos: + for mlag_error_info in mlag_error_infos: + mlag_error_dict = dict() + for ele in mlag_error_info: + if ele.tag in ["dfsgroupId", "portName"]: + mlag_error_dict[ele.tag] = ele.text + mlag_error_down_info[ + "mlagErrorDownInfos"].append(mlag_error_dict) + return mlag_error_down_info + + def check_macaddr(self): + """check mac-address whether valid""" + + valid_char = '0123456789abcdef-' + mac = self.mlag_system_id + + if len(mac) > 16: + return False + + mac_list = re.findall(r'([0-9a-fA-F]+)', mac) + if len(mac_list) != 3: + return False + + if mac.count('-') != 2: + return False + + for _, value in enumerate(mac, start=0): + if value.lower() not in valid_char: + return False + if all((int(mac_list[0], base=16) == 0, int(mac_list[1], base=16) == 0, int(mac_list[2], base=16) == 0)): + return False + a = "000" + mac_list[0] + b = "000" + mac_list[1] + c = "000" + mac_list[2] + self.mlag_system_id = "-".join([a[-4:], b[-4:], c[-4:]]) + return True + + def check_params(self): + """Check all input params""" + + # eth_trunk_id check + if self.eth_trunk_id: + if not self.eth_trunk_id.isdigit(): + self.module.fail_json( + msg='Error: The value of eth_trunk_id is an integer.') + if int(self.eth_trunk_id) < 0 or int(self.eth_trunk_id) > 511: + self.module.fail_json( + msg='Error: The value of eth_trunk_id is not in the range from 0 to 511.') + + # dfs_group_id check + if self.dfs_group_id: + if self.dfs_group_id != "1": + self.module.fail_json( + msg='Error: The value of dfs_group_id must be 1.') + + # mlag_id check + if self.mlag_id: + if not self.mlag_id.isdigit(): + self.module.fail_json( + msg='Error: The value of mlag_id is an integer.') + if int(self.mlag_id) < 1 or int(self.mlag_id) > 2048: + self.module.fail_json( + msg='Error: The value of mlag_id is not in the range from 1 to 2048.') + + # mlag_system_id check + if self.mlag_system_id: + if not self.check_macaddr(): + self.module.fail_json( + msg="Error: mlag_system_id has invalid value %s." % self.mlag_system_id) + + # mlag_priority_id check + if self.mlag_priority_id: + if not self.mlag_priority_id.isdigit(): + self.module.fail_json( + msg='Error: The value of mlag_priority_id is an integer.') + if int(self.mlag_priority_id) < 0 or int(self.mlag_priority_id) > 254: + self.module.fail_json( + msg='Error: The value of mlag_priority_id is not in the range from 0 to 254.') + + # interface check + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + def is_mlag_info_change(self): + """whether mlag info change""" + + if not self.mlag_info: + return True + + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + for info in self.mlag_info["mlagInfos"]: + if info["mlagId"] == self.mlag_id and info["localMlagPort"] == eth_trunk: + return False + return True + + def is_mlag_info_exist(self): + """whether mlag info exist""" + + if not self.mlag_info: + return False + + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + + for info in self.mlag_info["mlagInfos"]: + if info["localMlagPort"] == eth_trunk: + return True + return False + + def is_mlag_error_down_info_change(self): + """whether mlag error down info change""" + + if not self.mlag_error_down_info: + return True + + for info in self.mlag_error_down_info["mlagErrorDownInfos"]: + if info["portName"].upper() == self.interface.upper(): + return False + return True + + def is_mlag_error_down_info_exist(self): + """whether mlag error down info exist""" + + if not self.mlag_error_down_info: + return False + + for info in self.mlag_error_down_info["mlagErrorDownInfos"]: + if info["portName"].upper() == self.interface.upper(): + return True + return False + + def is_mlag_interface_info_change(self): + """whether mlag interface attribute info change""" + + if not self.mlag_trunk_attribute_info: + return True + + if self.mlag_system_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] != self.mlag_system_id: + return True + if self.mlag_priority_id: + if self.mlag_trunk_attribute_info["lacpMlagPriority"] != self.mlag_priority_id: + return True + return False + + def is_mlag_interface_info_exist(self): + """whether mlag interface attribute info exist""" + + if not self.mlag_trunk_attribute_info: + return False + + if self.mlag_system_id: + if self.mlag_priority_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id: + return True + + if self.mlag_priority_id: + if self.mlag_system_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + + return False + + def is_mlag_global_info_change(self): + """whether mlag global attribute info change""" + + if not self.mlag_global_info: + return True + + if self.mlag_system_id: + if self.mlag_global_info["lacpMlagSysId"] != self.mlag_system_id: + return True + if self.mlag_priority_id: + if self.mlag_global_info["lacpMlagPriority"] != self.mlag_priority_id: + return True + return False + + def is_mlag_global_info_exist(self): + """whether mlag global attribute info exist""" + + if not self.mlag_global_info: + return False + + if self.mlag_system_id: + if self.mlag_priority_id: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id: + return True + + if self.mlag_priority_id: + if self.mlag_system_id: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + + return False + + def create_mlag(self): + """create mlag info""" + + if self.is_mlag_info_change(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_CREATE_MLAG_INFO % ( + self.dfs_group_id, self.mlag_id, mlag_port) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create mlag info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + self.updates_cmd.append("dfs-group %s m-lag %s" % + (self.dfs_group_id, self.mlag_id)) + self.changed = True + + def delete_mlag(self): + """delete mlag info""" + + if self.is_mlag_info_exist(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_DELETE_MLAG_INFO % ( + self.dfs_group_id, mlag_port) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete mlag info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + self.updates_cmd.append( + "undo dfs-group %s m-lag %s" % (self.dfs_group_id, self.mlag_id)) + self.changed = True + + def create_mlag_error_down(self): + """create mlag error down info""" + + if self.is_mlag_error_down_info_change(): + conf_str = CE_NC_CREATE_MLAG_ERROR_DOWN_INFO % self.interface + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create mlag error down info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("m-lag unpaired-port suspend") + self.changed = True + + def delete_mlag_error_down(self): + """delete mlag error down info""" + + if self.is_mlag_error_down_info_exist(): + + conf_str = CE_NC_DELETE_MLAG_ERROR_DOWN_INFO % self.interface + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete mlag error down info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("undo m-lag unpaired-port suspend") + self.changed = True + + def set_mlag_interface(self): + """set mlag interface attribute info""" + + if self.is_mlag_interface_info_change(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_SET_LACP_MLAG_INFO_HEAD % mlag_port + if self.mlag_priority_id: + conf_str += "%s" % self.mlag_priority_id + if self.mlag_system_id: + conf_str += "%s" % self.mlag_system_id + conf_str += CE_NC_SET_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface attribute info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + if self.mlag_priority_id: + self.updates_cmd.append( + "lacp m-lag priority %s" % self.mlag_priority_id) + + if self.mlag_system_id: + self.updates_cmd.append( + "lacp m-lag system-id %s" % self.mlag_system_id) + self.changed = True + + def delete_mlag_interface(self): + """delete mlag interface attribute info""" + + if self.is_mlag_interface_info_exist(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_SET_LACP_MLAG_INFO_HEAD % mlag_port + cmd = "interface %s" % mlag_port + self.cli_add_command(cmd) + + if self.mlag_priority_id: + cmd = "lacp m-lag priority %s" % self.mlag_priority_id + conf_str += "" + self.cli_add_command(cmd, True) + + if self.mlag_system_id: + cmd = "lacp m-lag system-id %s" % self.mlag_system_id + conf_str += "" + self.cli_add_command(cmd, True) + + if self.commands: + conf_str += CE_NC_SET_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface atrribute info failed.') + + self.changed = True + + def set_mlag_global(self): + """set mlag global attribute info""" + + if self.is_mlag_global_info_change(): + conf_str = CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD + if self.mlag_priority_id: + conf_str += "%s" % self.mlag_priority_id + if self.mlag_system_id: + conf_str += "%s" % self.mlag_system_id + conf_str += CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface attribute info failed.') + + if self.mlag_priority_id: + self.updates_cmd.append( + "lacp m-lag priority %s" % self.mlag_priority_id) + + if self.mlag_system_id: + self.updates_cmd.append( + "lacp m-lag system-id %s" % self.mlag_system_id) + self.changed = True + + def delete_mlag_global(self): + """delete mlag global attribute info""" + + xml_str = '' + if self.is_mlag_global_info_exist(): + if self.mlag_priority_id: + cmd = "lacp m-lag priority %s" % self.mlag_priority_id + xml_str += '' + self.cli_add_command(cmd, True) + + if self.mlag_system_id: + cmd = "lacp m-lag system-id %s" % self.mlag_system_id + xml_str += '' + self.cli_add_command(cmd, True) + + if xml_str != '': + conf_str = CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD + xml_str + CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface atrribute info failed.') + self.changed = True + + def get_proposed(self): + """get proposed info""" + + if self.eth_trunk_id: + self.proposed["eth_trunk_id"] = self.eth_trunk_id + if self.dfs_group_id: + self.proposed["dfs_group_id"] = self.dfs_group_id + if self.mlag_id: + self.proposed["mlag_id"] = self.mlag_id + if self.mlag_system_id: + self.proposed["mlag_system_id"] = self.mlag_system_id + if self.mlag_priority_id: + self.proposed["mlag_priority_id"] = self.mlag_priority_id + if self.interface: + self.proposed["interface"] = self.interface + if self.mlag_error_down: + self.proposed["mlag_error_down"] = self.mlag_error_down + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + self.mlag_info = self.get_mlag_info() + self.mlag_global_info = self.get_mlag_global_info() + self.mlag_error_down_info = self.get_mlag_error_down_info() + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + if not self.mlag_system_id and not self.mlag_priority_id: + if self.mlag_info: + self.existing["mlagInfos"] = self.mlag_info["mlagInfos"] + + if self.mlag_system_id or self.mlag_priority_id: + if self.eth_trunk_id: + if self.mlag_trunk_attribute_info: + if self.mlag_system_id: + self.existing["lacpMlagSysId"] = self.mlag_trunk_attribute_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.existing["lacpMlagPriority"] = self.mlag_trunk_attribute_info[ + "lacpMlagPriority"] + else: + if self.mlag_global_info: + if self.mlag_system_id: + self.existing["lacpMlagSysId"] = self.mlag_global_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.existing["lacpMlagPriority"] = self.mlag_global_info[ + "lacpMlagPriority"] + + if self.interface or self.mlag_error_down: + if self.mlag_error_down_info: + self.existing["mlagErrorDownInfos"] = self.mlag_error_down_info[ + "mlagErrorDownInfos"] + + def get_end_state(self): + """get end state info""" + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + self.mlag_info = self.get_mlag_info() + if not self.mlag_system_id and not self.mlag_priority_id: + if self.mlag_info: + self.end_state["mlagInfos"] = self.mlag_info["mlagInfos"] + + if self.mlag_system_id or self.mlag_priority_id: + if self.eth_trunk_id: + self.mlag_trunk_attribute_info = self.get_mlag_trunk_attribute_info() + if self.mlag_trunk_attribute_info: + if self.mlag_system_id: + self.end_state["lacpMlagSysId"] = self.mlag_trunk_attribute_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.end_state["lacpMlagPriority"] = self.mlag_trunk_attribute_info[ + "lacpMlagPriority"] + else: + self.mlag_global_info = self.get_mlag_global_info() + if self.mlag_global_info: + if self.mlag_system_id: + self.end_state["lacpMlagSysId"] = self.mlag_global_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.end_state["lacpMlagPriority"] = self.mlag_global_info[ + "lacpMlagPriority"] + + if self.interface or self.mlag_error_down: + self.mlag_error_down_info = self.get_mlag_error_down_info() + if self.mlag_error_down_info: + self.end_state["mlagErrorDownInfos"] = self.mlag_error_down_info[ + "mlagErrorDownInfos"] + + def work(self): + """worker""" + + self.check_params() + self.get_proposed() + self.get_existing() + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + self.mlag_info = self.get_mlag_info() + if self.eth_trunk_id and self.dfs_group_id and self.mlag_id: + if self.state == "present": + self.create_mlag() + else: + self.delete_mlag() + else: + if not self.mlag_system_id and not self.mlag_priority_id: + self.module.fail_json( + msg='Error: eth_trunk_id, dfs_group_id, mlag_id must be config at the same time.') + + if self.mlag_system_id or self.mlag_priority_id: + + if self.eth_trunk_id: + self.mlag_trunk_attribute_info = self.get_mlag_trunk_attribute_info() + if self.mlag_system_id or self.mlag_priority_id: + if self.state == "present": + self.set_mlag_interface() + else: + self.delete_mlag_interface() + else: + self.mlag_global_info = self.get_mlag_global_info() + if self.mlag_system_id or self.mlag_priority_id: + if self.state == "present": + self.set_mlag_global() + else: + self.delete_mlag_global() + + if self.interface or self.mlag_error_down: + self.mlag_error_down_info = self.get_mlag_error_down_info() + if self.interface and self.mlag_error_down: + if self.mlag_error_down == "enable": + self.create_mlag_error_down() + else: + self.delete_mlag_error_down() + else: + self.module.fail_json( + msg='Error: interface, mlag_error_down must be config at the same time.') + + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + eth_trunk_id=dict(type='str'), + dfs_group_id=dict(type='str'), + mlag_id=dict(type='str'), + mlag_system_id=dict(type='str'), + mlag_priority_id=dict(type='str'), + interface=dict(type='str'), + mlag_error_down=dict(type='str', choices=['enable', 'disable']), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = MlagInterface(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mtu.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mtu.py new file mode 100644 index 00000000..db902480 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_mtu.py @@ -0,0 +1,581 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mtu +short_description: Manages MTU settings on HUAWEI CloudEngine switches. +description: + - Manages MTU settings on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - Either C(sysmtu) param is required or C(interface) AND C(mtu) params are req'd. + - C(state=absent) unconfigures a given MTU if that value is currently present. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/22. + mtu: + description: + - MTU for a specific interface. + The value is an integer ranging from 46 to 9600, in bytes. + jumbo_max: + description: + - Maximum frame size. The default value is 9216. + The value is an integer and expressed in bytes. The value range is 1536 to 12224 for the CE12800 + and 1536 to 12288 for ToR switches. + jumbo_min: + description: + - Non-jumbo frame size threshold. The default value is 1518. + The value is an integer that ranges from 1518 to jumbo_max, in bytes. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Mtu test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config jumboframe on 40GE1/0/22" + community.network.ce_mtu: + interface: 40GE1/0/22 + jumbo_max: 9000 + jumbo_min: 8000 + provider: "{{ cli }}" + + - name: "Config mtu on 40GE1/0/22 (routed interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 1600 + provider: "{{ cli }}" + + - name: "Config mtu on 40GE1/0/23 (switched interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 9216 + provider: "{{ cli }}" + + - name: "Config mtu and jumboframe on 40GE1/0/22 (routed interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 1601 + jumbo_max: 9001 + jumbo_min: 8001 + provider: "{{ cli }}" + + - name: "Unconfigure mtu and jumboframe on a given interface" + community.network.ce_mtu: + state: absent + interface: 40GE1/0/22 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} +existing: + description: k/v pairs of existing mtu/sysmtu on the interface/system + returned: always + type: dict + sample: {"mtu": "1600", "jumbo_max": "9216", "jumbo_min": "1518"} +end_state: + description: k/v pairs of mtu/sysmtu values after module execution + returned: always + type: dict + sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface 40GE1/0/23", "mtu 1700", "jumboframe enable 9000 8000"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +import copy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible.module_utils.connection import exec_command + + +def is_interface_support_setjumboframe(interface): + """is interface support set jumboframe""" + + if interface is None: + return False + support_flag = False + if interface.upper().startswith('GE'): + support_flag = True + elif interface.upper().startswith('10GE'): + support_flag = True + elif interface.upper().startswith('25GE'): + support_flag = True + elif interface.upper().startswith('4X10GE'): + support_flag = True + elif interface.upper().startswith('40GE'): + support_flag = True + elif interface.upper().startswith('100GE'): + support_flag = True + else: + support_flag = False + return support_flag + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Mtu(object): + """set mtu""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface info + self.interface = self.module.params['interface'] + self.mtu = self.module.params['mtu'] + self.state = self.module.params['state'] + self.jbf_max = self.module.params['jumbo_max'] or None + self.jbf_min = self.module.params['jumbo_min'] or None + self.jbf_config = list() + self.jbf_cli = "" + self.commands = list() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intf_info = dict() # one interface info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """ init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + intf_info = dict() + + flags = list() + exp = r"| ignore-case section include ^#\s+interface %s\s+" % ifname.replace(" ", "") + flags.append(exp) + output = self.get_config(flags) + output_list = output.split('\n') + if output_list is None: + return intf_info + + mtu = None + for config in output_list: + config = config.strip() + if config.startswith('mtu'): + mtu = re.findall(r'.*mtu\s*([0-9]*)', output)[0] + + intf_info = dict(ifName=ifname, + ifMtu=mtu) + + return intf_info + + def prase_jumboframe_para(self, config_str): + """prase_jumboframe_para""" + + interface_cli = "interface %s" % (self.interface.replace(" ", "").lower()) + if config_str.find(interface_cli) == -1: + self.module.fail_json(msg='Error: Interface does not exist.') + + try: + npos1 = config_str.index('jumboframe enable') + except ValueError: + # return default vale + return [9216, 1518] + try: + npos2 = config_str.index('\n', npos1) + config_str_tmp = config_str[npos1:npos2] + except ValueError: + config_str_tmp = config_str[npos1:] + + return re.findall(r'([0-9]+)', config_str_tmp) + + def cli_load_config(self): + """load config by cli""" + + if not self.module.check_mode: + if len(self.commands) > 1: + load_config(self.module, self.commands) + self.changed = True + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + + def get_jumboframe_config(self): + """ get_jumboframe_config""" + + flags = list() + exp = r"| ignore-case section include ^#\s+interface %s\s+" % self.interface.replace(" ", "") + flags.append(exp) + output = self.get_config(flags) + output = output.replace('*', '').lower() + + return self.prase_jumboframe_para(output) + + def set_jumboframe(self): + """ set_jumboframe""" + + if self.state == "present": + if not self.jbf_max and not self.jbf_min: + return + + jbf_value = self.get_jumboframe_config() + self.jbf_config = copy.deepcopy(jbf_value) + if len(jbf_value) == 1: + jbf_value.append("1518") + self.jbf_config.append("1518") + if not self.jbf_max: + return + + if (len(jbf_value) > 2) or (len(jbf_value) == 0): + self.module.fail_json( + msg='Error: Get jubmoframe config value num error.') + if self.jbf_min is None: + if jbf_value[0] == self.jbf_max: + return + else: + if (jbf_value[0] == self.jbf_max) \ + and (jbf_value[1] == self.jbf_min): + return + if jbf_value[0] != self.jbf_max: + jbf_value[0] = self.jbf_max + if (jbf_value[1] != self.jbf_min) and (self.jbf_min is not None): + jbf_value[1] = self.jbf_min + else: + jbf_value.pop(1) + else: + jbf_value = self.get_jumboframe_config() + self.jbf_config = copy.deepcopy(jbf_value) + if (jbf_value == [9216, 1518]): + return + jbf_value = [9216, 1518] + + if len(jbf_value) == 2: + self.jbf_cli = "jumboframe enable %s %s" % ( + jbf_value[0], jbf_value[1]) + else: + self.jbf_cli = "jumboframe enable %s" % (jbf_value[0]) + self.cli_add_command(self.jbf_cli) + + if self.state == "present": + if self.jbf_min: + self.updates_cmd.append( + "jumboframe enable %s %s" % (self.jbf_max, self.jbf_min)) + else: + self.updates_cmd.append("jumboframe enable %s" % (self.jbf_max)) + else: + self.updates_cmd.append("undo jumboframe enable") + + return + + def merge_interface(self, ifname, mtu): + """ Merge interface mtu.""" + + xmlstr = '' + change = False + + command = "interface %s" % ifname + self.cli_add_command(command) + + if self.state == "present": + if mtu and self.intf_info["ifMtu"] != mtu: + command = "mtu %s" % mtu + self.cli_add_command(command) + self.updates_cmd.append("mtu %s" % mtu) + change = True + else: + if self.intf_info["ifMtu"] != '1500' and self.intf_info["ifMtu"]: + command = "mtu 1500" + self.cli_add_command(command) + self.updates_cmd.append("undo mtu") + change = True + + return + + def check_params(self): + """Check all input params""" + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface %s is error.') + + # mtu check mtu + if self.mtu: + if not self.mtu.isdigit(): + self.module.fail_json(msg='Error: Mtu is invalid.') + # check mtu range + if int(self.mtu) < 46 or int(self.mtu) > 9600: + self.module.fail_json( + msg='Error: Mtu is not in the range from 46 to 9600.') + # get interface info + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: interface does not exist.') + + # check interface can set jumbo frame + if self.state == 'present': + if self.jbf_max: + if not is_interface_support_setjumboframe(self.interface): + self.module.fail_json( + msg='Error: Interface %s does not support jumboframe set.' % self.interface) + if not self.jbf_max.isdigit(): + self.module.fail_json( + msg='Error: Max jumboframe is not digit.') + if (int(self.jbf_max) > 12288) or (int(self.jbf_max) < 1536): + self.module.fail_json( + msg='Error: Max jumboframe is between 1536 to 12288.') + + if self.jbf_min: + if not self.jbf_min.isdigit(): + self.module.fail_json( + msg='Error: Min jumboframe is not digit.') + if not self.jbf_max: + self.module.fail_json( + msg='Error: please specify max jumboframe value.') + if (int(self.jbf_min) > int(self.jbf_max)) or (int(self.jbf_min) < 1518): + self.module.fail_json( + msg='Error: Min jumboframe is between ' + '1518 to jumboframe max value.') + + if self.jbf_min is not None: + if self.jbf_max is None: + self.module.fail_json( + msg='Error: please input MAX jumboframe ' + 'value.') + + def get_proposed(self): + """ get_proposed""" + + self.proposed['state'] = self.state + if self.interface: + self.proposed["interface"] = self.interface + + if self.state == 'present': + if self.mtu: + self.proposed["mtu"] = self.mtu + if self.jbf_max: + if self.jbf_min: + self.proposed["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_max, self.jbf_min) + else: + self.proposed[ + "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) + + def get_existing(self): + """ get_existing""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + self.existing["mtu"] = self.intf_info["ifMtu"] + + if self.intf_info: + if not self.existing["interface"]: + self.existing["interface"] = self.interface + + if len(self.jbf_config) != 2: + return + + self.existing["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_config[0], self.jbf_config[1]) + + def get_end_state(self): + """ get_end_state""" + + if self.intf_info: + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + self.end_state["mtu"] = end_info["ifMtu"] + if self.intf_info: + if not self.end_state["interface"]: + self.end_state["interface"] = self.interface + + if self.state == 'absent': + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + 9216, 1518) + elif not self.jbf_max and not self.jbf_min: + if len(self.jbf_config) != 2: + return + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_config[0], self.jbf_config[1]) + elif self.jbf_min: + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_max, self.jbf_min) + else: + self.end_state[ + "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + self.check_params() + + self.get_proposed() + + self.merge_interface(self.interface, self.mtu) + self.set_jumboframe() + self.cli_load_config() + + self.get_existing() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + mtu=dict(type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + jumbo_max=dict(type='str'), + jumbo_min=dict(type='str'), + ) + argument_spec.update(ce_argument_spec) + interface = Mtu(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_global.py new file mode 100644 index 00000000..e6ab24fd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_global.py @@ -0,0 +1,286 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_multicast_global +version_added: '0.2.0' +author: xuxiaowei0512 (@xuxiaowei0512) +short_description: Manages multicast global configuration on HUAWEI CloudEngine switches. +description: + - Manages multicast global on HUAWEI CloudEngine switches. +notes: + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + vrf: + description: + - VPN instance of destination ip address. + type: str + state: + description: + - Specify desired state of the resource. + type: str + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +--- + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + state: absent + provider: "{{ cli }}" + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + state: present + provider: "{{ cli }}" + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + vrf: vrf1 + provider: "{{ cli }}" + +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["multicast routing-enable"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_MULTICAST_GLOBAL = """ + + + + + %s + %s + + + + +""" +CE_NC_MERGE_MULTICAST_GLOBAL = """ + + + + %s + %s + + + +""" +CE_NC_DELETE_MULTICAST_GLOBAL = """ + + + + %s + %s + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +class MulticastGlobal(object): + """multicast global module""" + + def __init__(self, argument_spec): + """multicast global info""" + self.spec = argument_spec + self.module = None + self._initmodule_() + + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.version = "ipv4unicast" + else: + self.version = "ipv6unicast" + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.multicast_global_info = dict() + + def _initmodule_(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_change_state(self): + """set change state""" + state = self.state + change = False + self.get_multicast_global() + # new or edit + if state == 'present': + if not self.multicast_global_info.get('multicast_global'): + # i.e. self.multicast_global_info['multicast_global'] has not value + change = True + else: + # delete + if self.multicast_global_info.get('multicast_global'): + # i.e. self.multicast_global_info['multicast_global'] has value + change = True + self.changed = change + + def get_multicast_global(self): + """get one data""" + self.multicast_global_info["multicast_global"] = list() + getxmlstr = CE_NC_GET_MULTICAST_GLOBAL % ( + self.version, self.vrf) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + mcast_enable = root.findall( + "mcastbase/mcastAfsEnables/mcastAfsEnable") + if mcast_enable: + # i.e. mcast_enable = [{vrfName:11,addressFamily:'xx'},{vrfName:22,addressFamily:'xx'}...] + for mcast_enable_key in mcast_enable: + # i.e. mcast_enable_key = {vrfName:11,addressFamily:'xx'} + mcast_info = dict() + for ele in mcast_enable_key: + if ele.tag in ["vrfName", "addressFamily"]: + mcast_info[ele.tag] = ele.text + self.multicast_global_info['multicast_global'].append(mcast_info) + + def get_existing(self): + """get existing information""" + self.set_change_state() + self.existing["multicast_global"] = self.multicast_global_info["multicast_global"] + + def get_proposed(self): + """get proposed information""" + self.proposed['addressFamily'] = self.version + self.proposed['state'] = self.state + self.proposed['vrfName'] = self.vrf + + def set_multicast_global(self): + """set multicast global""" + if not self.changed: + return + version = self.version + state = self.state + if state == "present": + configxmlstr = CE_NC_MERGE_MULTICAST_GLOBAL % (self.vrf, version) + else: + configxmlstr = CE_NC_DELETE_MULTICAST_GLOBAL % (self.vrf, version) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_MULTICAST_GLOBAL") + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('multicast routing-enable') + else: + self.updates_cmd.append('undo multicast routing-enable') + + def get_end_state(self): + """get end state information""" + self.get_multicast_global() + self.end_state["multicast_global"] = self.multicast_global_info["multicast_global"] + + def work(self): + """worker""" + self.get_existing() + self.get_proposed() + self.set_multicast_global() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['existing'] = self.existing + self.results['proposed'] = self.proposed + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + aftype=dict(choices=['v4', 'v6'], required=True), + vrf=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], default='present', required=False), + ) + interface = MulticastGlobal(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_igmp_enable.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_igmp_enable.py new file mode 100644 index 00000000..1432f19a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_multicast_igmp_enable.py @@ -0,0 +1,543 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_multicast_igmp_enable +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages multicast igmp enable configuration on HUAWEI CloudEngine switches. +description: + - Manages multicast igmp on HUAWEI CloudEngine switches. +notes: + - If no vrf is supplied, vrf is set to default. + If I(state=absent), the route will be removed, regardless of the + non-required parameters. + - This module requires the netconf system service be enabled on + the remote device being managed. + - This module works with connection C(netconf). +options: + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + features: + description: + - Distinguish between Globally Enabled IGMP or + - Enabled IGMP under vlanID. + required: true + type: str + choices: ['global','vlan'] + vlan_id: + description: + - Virtual LAN identity. + type: int + igmp: + description: + - Enable Layer 2 multicast Snooping in a VLAN. + type: bool + default: false + version: + description: + - Specifies the IGMP version that can be processed. + default: 2 + type: int + proxy: + description: + - Layer 2 multicast snooping proxy is enabled. + type: bool + default: false + state: + description: + - Specify desired state of the resource. + choices: ['present','absent'] + default: present + type: str +''' + +EXAMPLES = ''' + + - name: Configure global igmp enable + community.network.ce_multicast_igmp_enable: + aftype: v4 + features: 'global' + state: present + + - name: Configure global igmp disable + community.network.ce_multicast_igmp_enable: + features: 'global' + aftype: v4 + state: absent + + - name: Configure vlan igmp enable + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + igmp: true + + - name: New proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + proxy: true + igmp: true + version: 1 + + - name: Modify proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + version: 2 + + - name: Delete proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + state: absent +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addrFamily": "ipv4unicast", "features": "vlan", "proxyEnable": "false", + "snoopingEnable": "false", "state": "absent", "version": 2, "vlanId": 1} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["undo igmp snooping enable", + "undo igmp snooping version", + "undo igmp snooping proxy"] +changed: + description: check if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_IGMP_GLOBAL = """ + + + + + %s + + + + +""" +CE_NC_MERGE_IGMP_SYSVIEW = """ + + + + %s + + + +""" +CE_NC_DELETE_IGMP_SYSVIEW = """ + + + + %s + + + +""" +CE_NC_GET_IGMP_VLAN_INFO = """ + + + + + + %s + %s + + + + + + + + +""" +CE_NC_MERGE_IGMP_VLANVIEW = """ + + + + + %s + %s%s%s%s + + + + +""" +CE_NC_MERGE_IGMP_VLANVIEW_SNOENABLE = """ +%s +""" +CE_NC_MERGE_IGMP_VLANVIEW_VERSION = """ +%s +""" +CE_NC_MERGE_IGMP_VLANVIEW_PROXYENABLE = """ +%s +""" +CE_NC_DELETE_IGMP_VLANVIEW = """ + + + + + %s + %s + + + + +""" + + +def get_xml(xml, value): + """operate xml""" + tempxml = xml % value + return tempxml + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +class IgmpSnoop(object): + """igmp snooping module""" + + def __init__(self, argument_spec): + """igmp snooping info""" + self.spec = argument_spec + self.module = None + self._initmodule_() + + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.addr_family = "ipv4unicast" + else: + self.addr_family = "ipv6unicast" + self.features = self.module.params['features'] + self.vlan_id = self.module.params['vlan_id'] + self.igmp = str(self.module.params['igmp']).lower() + self.version = self.module.params['version'] + if self.version is None: + self.version = 2 + self.proxy = str(self.module.params['proxy']).lower() + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.igmp_info_data = dict() + + def _initmodule_(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def _checkparams_(self): + """check all input params""" + # check vlan id + if self.features == 'vlan': + if not self.vlan_id: + self.module.fail_json(msg='Error: missing required arguments: vlan_id.') + + if self.vlan_id: + if self.vlan_id <= 0 or self.vlan_id > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + # check version + if self.version: + if self.version <= 0 or self.version > 3: + self.module.fail_json( + msg='Error: Version id is not in the range from 1 to 3.') + + def set_change_state(self): + """set change state""" + state = self.state + change = False + # vlan view igmp + if self.features == 'vlan': + self.get_igmp_vlan() + change = self.compare_data() + else: + # sys view igmp(global) + self.get_igmp_global() + # new or edit + if state == 'present': + if not self.igmp_info_data["igmp_info"]: + # igmp_info_data has not igmp_info value. + change = True + else: + # delete + if self.igmp_info_data["igmp_info"]: + # igmp_info_data has not igmp_info value. + change = True + self.changed = change + + def compare_data(self): + """compare new data and old data""" + state = self.state + change = False + # new or edit + if state == 'present': + # edit + if self.igmp_info_data["igmp_info"]: + for data in self.igmp_info_data["igmp_info"]: + if self.addr_family == data["addrFamily"] and str(self.vlan_id) == data["vlanId"]: + if self.igmp: + if self.igmp != data["snoopingEnable"]: + change = True + if self.version: + if str(self.version) != data["version"]: + change = True + if self.proxy: + if self.proxy != data["proxyEnable"]: + change = True + # new + else: + change = True + else: + # delete + if self.igmp_info_data["igmp_info"]: + change = True + return change + + def get_igmp_vlan(self): + """get igmp vlan info data""" + self.igmp_info_data["igmp_info"] = list() + getxmlstr = CE_NC_GET_IGMP_VLAN_INFO % (self.addr_family, self.vlan_id) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + igmp_enable = root.findall( + "l2mc/vlan/l2McVlanCfgs/l2McVlanCfg") + if igmp_enable: + # igmp_enable = [{addressFamily:'xx'}] + for igmp_enable_key in igmp_enable: + # igmp_enable_key = {addressFamily:'xx'} + igmp_global_info = dict() + for ele in igmp_enable_key: + if ele.tag in ["addrFamily", "vlanId", "snoopingEnable", "version", "proxyEnable"]: + igmp_global_info[ele.tag] = ele.text + self.igmp_info_data["igmp_info"].append(igmp_global_info) + + def get_igmp_global(self): + """get igmp global data""" + self.igmp_info_data["igmp_info"] = list() + getxmlstr = CE_NC_GET_IGMP_GLOBAL % ( + self.addr_family) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + igmp_enable = root.findall( + 'l2mc/l2McSnpgEnables/l2McSnpgEnable') + if igmp_enable: + # igmp_enable = [{addressFamily:'xx'}] + for igmp_enable_key in igmp_enable: + # igmp_enable_key = {addressFamily:'xx'} + igmp_global_info = dict() + for ele in igmp_enable_key: + if ele.tag in ["addrFamily"]: + igmp_global_info[ele.tag] = ele.text + self.igmp_info_data["igmp_info"].append(igmp_global_info) + + def set_vlanview_igmp(self): + """set igmp of vlanview""" + if not self.changed: + return + addr_family = self.addr_family + state = self.state + igmp_xml = """\n""" + version_xml = """\n""" + proxy_xml = """\n""" + if state == "present": + if self.igmp: + igmp_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_SNOENABLE, self.igmp.lower()) + if str(self.version): + version_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_VERSION, self.version) + if self.proxy: + proxy_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_PROXYENABLE, self.proxy.lower()) + configxmlstr = CE_NC_MERGE_IGMP_VLANVIEW % ( + addr_family, self.vlan_id, igmp_xml, version_xml, proxy_xml) + else: + configxmlstr = CE_NC_DELETE_IGMP_VLANVIEW % (addr_family, self.vlan_id) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_VLANVIEW_IGMP") + + def set_sysview_igmp(self): + """set igmp of sysview""" + if not self.changed: + return + version = self.addr_family + state = self.state + if state == "present": + configxmlstr = CE_NC_MERGE_IGMP_SYSVIEW % (version) + else: + configxmlstr = CE_NC_DELETE_IGMP_SYSVIEW % (version) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_SYSVIEW_IGMP") + + def set_sysview_cmd(self): + """set sysview update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('igmp snooping enable') + else: + self.updates_cmd.append('undo igmp snooping enable') + + def set_vlanview_cmd(self): + """set vlanview update command""" + if not self.changed: + return + if self.state == "present": + if self.igmp: + if self.igmp.lower() == 'true': + self.updates_cmd.append('igmp snooping enable') + else: + self.updates_cmd.append('undo igmp snooping enable') + if str(self.version): + self.updates_cmd.append('igmp snooping version %s' % (self.version)) + else: + self.updates_cmd.append('undo igmp snooping version') + if self.proxy: + if self.proxy.lower() == 'true': + self.updates_cmd.append('igmp snooping proxy') + else: + self.updates_cmd.append('undo igmp snooping proxy') + + else: + self.updates_cmd.append('undo igmp snooping enable') + self.updates_cmd.append('undo igmp snooping version') + self.updates_cmd.append('undo igmp snooping proxy') + + def get_existing(self): + """get existing information""" + self.set_change_state() + self.existing["igmp_info"] = self.igmp_info_data["igmp_info"] + + def get_proposed(self): + """get proposed information""" + self.proposed['addrFamily'] = self.addr_family + self.proposed['features'] = self.features + if self.features == 'vlan': + self.proposed['snoopingEnable'] = self.igmp + self.proposed['version'] = self.version + self.proposed['vlanId'] = self.vlan_id + self.proposed['proxyEnable'] = self.proxy + self.proposed['state'] = self.state + + def set_igmp_netconf(self): + """config netconf""" + if self.features == 'vlan': + self.set_vlanview_igmp() + else: + self.set_sysview_igmp() + + def set_update_cmd(self): + """set update command""" + if self.features == 'vlan': + self.set_vlanview_cmd() + else: + self.set_sysview_cmd() + + def get_end_state(self): + """get end state information""" + if self.features == 'vlan': + self.get_igmp_vlan() + else: + # sys view igmp(global) + self.get_igmp_global() + self.end_state["igmp_info"] = self.igmp_info_data["igmp_info"] + + def work(self): + """worker""" + self._checkparams_() + self.get_existing() + self.get_proposed() + self.set_igmp_netconf() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['existing'] = self.existing + self.results['proposed'] = self.proposed + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """main""" + argument_spec = dict( + aftype=dict(choices=['v4', 'v6'], required=True), + features=dict(required=True, choices=['global', 'vlan'], type='str'), + vlan_id=dict(type='int'), + igmp=dict(type='bool', default=False), + version=dict(type='int', default=2), + proxy=dict(type='bool', default=False), + state=dict(choices=['absent', 'present'], default='present'), + ) + interface = IgmpSnoop(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netconf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netconf.py new file mode 100644 index 00000000..4efbca3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netconf.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netconf +short_description: Run an arbitrary netconf command on HUAWEI CloudEngine switches. +description: + - Sends an arbitrary netconf command on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + rpc: + description: + - The type of rpc. + required: true + choices: ['get', 'edit-config', 'execute-action', 'execute-cli'] + cfg_xml: + description: + - The config xml string. + required: true +''' + +EXAMPLES = ''' + +- name: CloudEngine netconf test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Netconf get operation" + community.network.ce_netconf: + rpc: get + cfg_xml: ' + + + + 10 + + + + + + + + + ' + provider: "{{ cli }}" + + - name: "Netconf edit-config operation" + community.network.ce_netconf: + rpc: edit-config + cfg_xml: ' + + + + default_wdz + local + invalid + + + + ' + provider: "{{ cli }}" + + - name: "Netconf execute-action operation" + community.network.ce_netconf: + rpc: execute-action + cfg_xml: ' + + + ipv4unicast + + + ' + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"result": ["ok"]} +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import execute_nc_action, ce_argument_spec, execute_nc_cli + + +def main(): + """ main """ + + argument_spec = dict( + rpc=dict(choices=['get', 'edit-config', + 'execute-action', 'execute-cli'], required=True), + cfg_xml=dict(required=True) + ) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + rpc = module.params['rpc'] + cfg_xml = module.params['cfg_xml'] + changed = False + end_state = dict() + + if rpc == "get": + + response = get_nc_config(module, cfg_xml) + + if "" in response: + end_state["result"] = "" + else: + tmp1 = response.split(r"") + tmp2 = tmp1[1].split(r"") + result = tmp2[0].split("\n") + + end_state["result"] = result + + elif rpc == "edit-config": + + response = set_nc_config(module, cfg_xml) + + if "" not in response: + module.fail_json(msg='rpc edit-config failed.') + + changed = True + end_state["result"] = "ok" + + elif rpc == "execute-action": + + response = execute_nc_action(module, cfg_xml) + + if "" not in response: + module.fail_json(msg='rpc execute-action failed.') + + changed = True + end_state["result"] = "ok" + + elif rpc == "execute-cli": + + response = execute_nc_cli(module, cfg_xml) + + if "" in response: + end_state["result"] = "" + else: + tmp1 = response.split(r"") + tmp2 = tmp1[1].split(r"") + result = tmp2[0].split("\n") + + end_state["result"] = result + + else: + module.fail_json(msg='please input correct rpc.') + + results = dict() + results['changed'] = changed + results['end_state'] = end_state + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_aging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_aging.py new file mode 100644 index 00000000..8f4c046a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_aging.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_aging +short_description: Manages timeout mode of NetStream on HUAWEI CloudEngine switches. +description: + - Manages timeout mode of NetStream on HUAWEI CloudEngine switches. +author: YangYang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + timeout_interval: + description: + - Netstream timeout interval. + If is active type the interval is 1-60. + If is inactive ,the interval is 5-600. + default: 30 + type: + description: + - Specifies the packet type of netstream timeout active interval. + choices: ['ip', 'vxlan'] + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present + timeout_type: + description: + - Netstream timeout type. + choices: ['active', 'inactive', 'tcp-session', 'manual'] + manual_slot: + description: + - Specifies the slot number of netstream manual timeout. +''' + +EXAMPLES = ''' +- name: Netstream aging module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure netstream ip timeout active interval , the interval is 40 minutes. + community.network.ce_netstream_aging: + timeout_interval: 40 + type: ip + timeout_type: active + state: present + provider: "{{ cli }}" + + - name: Configure netstream vxlan timeout active interval , the interval is 40 minutes. + community.network.ce_netstream_aging: + timeout_interval: 40 + type: vxlan + timeout_type: active + active_state: present + provider: "{{ cli }}" + + - name: Delete netstream ip timeout active interval , set the ip timeout interval to 30 minutes. + community.network.ce_netstream_aging: + type: ip + timeout_type: active + state: absent + provider: "{{ cli }}" + + - name: Delete netstream vxlan timeout active interval , set the vxlan timeout interval to 30 minutes. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: active + state: absent + provider: "{{ cli }}" + + - name: Enable netstream ip tcp session timeout. + community.network.ce_netstream_aging: + type: ip + timeout_type: tcp-session + state: present + provider: "{{ cli }}" + + - name: Enable netstream vxlan tcp session timeout. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: tcp-session + state: present + provider: "{{ cli }}" + + - name: Disable netstream ip tcp session timeout. + community.network.ce_netstream_aging: + type: ip + timeout_type: tcp-session + state: absent + provider: "{{ cli }}" + + - name: Disable netstream vxlan tcp session timeout. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: tcp-session + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"timeout_interval": "40", + "type": "ip", + "state": "absent", + "timeout_type": active} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"active_timeout": [ + { + "ip": "40", + "vxlan": 30 + } + ], + "inactive_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "tcp_timeout": [ + { + "ip": "disable", + "vxlan": "disable" + } + ]} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"active_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "inactive_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "tcp_timeout": [ + { + "ip": "disable", + "vxlan": "disable" + } + ]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["undo netstream timeout ip active 40"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class NetStreamAging(object): + """ + Manages netstream aging. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.timeout_interval = self.module.params['timeout_interval'] + self.type = self.module.params['type'] + self.state = self.module.params['state'] + self.timeout_type = self.module.params['timeout_type'] + self.manual_slot = self.module.params['manual_slot'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # local parameters + self.existing["active_timeout"] = list() + self.existing["inactive_timeout"] = list() + self.existing["tcp_timeout"] = list() + self.end_state["active_timeout"] = list() + self.end_state["inactive_timeout"] = list() + self.end_state["tcp_timeout"] = list() + self.active_changed = False + self.inactive_changed = False + self.tcp_changed = False + + def init_module(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) + + def get_exist_timer_out_para(self): + """Get exist netstream timeout parameters""" + + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + active_tmp["ip"] = "30" + active_tmp["vxlan"] = "30" + inactive_tmp["ip"] = "30" + inactive_tmp["vxlan"] = "30" + tcp_tmp["ip"] = "absent" + tcp_tmp["vxlan"] = "absent" + + cmd = "display current-configuration | include ^netstream timeout" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 4 and config_mem_list[2] == "ip": + if config_mem_list[3] == "active": + active_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "inactive": + inactive_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "tcp-session": + tcp_tmp["ip"] = "present" + if len(config_mem_list) > 4 and config_mem_list[2] == "vxlan": + if config_mem_list[4] == "active": + active_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "inactive": + inactive_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "tcp-session": + tcp_tmp["vxlan"] = "present" + self.existing["active_timeout"].append(active_tmp) + self.existing["inactive_timeout"].append(inactive_tmp) + self.existing["tcp_timeout"].append(tcp_tmp) + + def get_end_timer_out_para(self): + """Get end netstream timeout parameters""" + + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + active_tmp["ip"] = "30" + active_tmp["vxlan"] = "30" + inactive_tmp["ip"] = "30" + inactive_tmp["vxlan"] = "30" + tcp_tmp["ip"] = "absent" + tcp_tmp["vxlan"] = "absent" + cmd = "display current-configuration | include ^netstream timeout" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 4 and config_mem_list[2] == "ip": + if config_mem_list[3] == "active": + active_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "inactive": + inactive_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "tcp-session": + tcp_tmp["ip"] = "present" + if len(config_mem_list) > 4 and config_mem_list[2] == "vxlan": + if config_mem_list[4] == "active": + active_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "inactive": + inactive_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "tcp-session": + tcp_tmp["vxlan"] = "present" + self.end_state["active_timeout"].append(active_tmp) + self.end_state["inactive_timeout"].append(inactive_tmp) + self.end_state["tcp_timeout"].append(tcp_tmp) + + def check_params(self): + """Check all input params""" + + # interval check + if not str(self.timeout_interval).isdigit(): + self.module.fail_json( + msg='Error: Timeout interval should be numerical.') + if self.timeout_type == "active": + if int(self.timeout_interval) < 1 or int(self.timeout_interval) > 60: + self.module.fail_json( + msg="Error: Active interval should between 1 - 60 minutes.") + if self.timeout_type == "inactive": + if int(self.timeout_interval) < 5 or int(self.timeout_interval) > 600: + self.module.fail_json( + msg="Error: Inactive interval should between 5 - 600 seconds.") + if self.timeout_type == "manual": + if not self.manual_slot: + self.module.fail_json( + msg="Error: If use manual timeout mode,slot number is needed.") + if re.match(r'^\d+(\/\d*)?$', self.manual_slot) is None: + self.module.fail_json( + msg='Error: Slot number should be numerical.') + + def get_proposed(self): + """get proposed info""" + + if self.timeout_interval: + self.proposed["timeout_interval"] = self.timeout_interval + if self.timeout_type: + self.proposed["timeout_type"] = self.timeout_type + if self.type: + self.proposed["type"] = self.type + if self.state: + self.proposed["state"] = self.state + if self.manual_slot: + self.proposed["manual_slot"] = self.manual_slot + + def get_existing(self): + """get existing info""" + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + + self.get_exist_timer_out_para() + + if self.timeout_type == "active": + for active_tmp in self.existing["active_timeout"]: + if self.state == "present": + if str(active_tmp[self.type]) != self.timeout_interval: + self.active_changed = True + else: + if self.timeout_interval != "30": + if str(active_tmp[self.type]) != "30": + if str(active_tmp[self.type]) != self.timeout_interval: + self.module.fail_json( + msg='Error: The specified active interval do not exist.') + if str(active_tmp[self.type]) != "30": + self.timeout_interval = active_tmp[self.type] + self.active_changed = True + if self.timeout_type == "inactive": + for inactive_tmp in self.existing["inactive_timeout"]: + if self.state == "present": + if str(inactive_tmp[self.type]) != self.timeout_interval: + self.inactive_changed = True + else: + if self.timeout_interval != "30": + if str(inactive_tmp[self.type]) != "30": + if str(inactive_tmp[self.type]) != self.timeout_interval: + self.module.fail_json( + msg='Error: The specified inactive interval do not exist.') + if str(inactive_tmp[self.type]) != "30": + self.timeout_interval = inactive_tmp[self.type] + self.inactive_changed = True + if self.timeout_type == "tcp-session": + for tcp_tmp in self.existing["tcp_timeout"]: + if str(tcp_tmp[self.type]) != self.state: + self.tcp_changed = True + + def operate_time_out(self): + """configure timeout parameters""" + + cmd = "" + if self.timeout_type == "manual": + if self.type == "ip": + self.cli_add_command("quit") + cmd = "reset netstream cache ip slot %s" % self.manual_slot + self.cli_add_command(cmd) + elif self.type == "vxlan": + self.cli_add_command("quit") + cmd = "reset netstream cache vxlan inner-ip slot %s" % self.manual_slot + self.cli_add_command(cmd) + + if not self.active_changed and not self.inactive_changed and not self.tcp_changed: + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + return + + if self.active_changed or self.inactive_changed: + if self.type == "ip": + cmd = "netstream timeout ip %s %s" % (self.timeout_type, self.timeout_interval) + elif self.type == "vxlan": + cmd = "netstream timeout vxlan inner-ip %s %s" % (self.timeout_type, self.timeout_interval) + if self.state == "absent": + self.cli_add_command(cmd, undo=True) + else: + self.cli_add_command(cmd) + if self.timeout_type == "tcp-session" and self.tcp_changed: + if self.type == "ip": + if self.state == "present": + cmd = "netstream timeout ip tcp-session" + else: + cmd = "undo netstream timeout ip tcp-session" + + elif self.type == "vxlan": + if self.state == "present": + cmd = "netstream timeout vxlan inner-ip tcp-session" + else: + cmd = "undo netstream timeout vxlan inner-ip tcp-session" + self.cli_add_command(cmd) + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def get_end_state(self): + """get end state info""" + + self.get_end_timer_out_para() + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_time_out() + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + timeout_interval=dict(required=False, type='str', default='30'), + type=dict(required=False, choices=['ip', 'vxlan']), + state=dict(required=False, choices=['present', 'absent'], default='present'), + timeout_type=dict(required=False, choices=['active', 'inactive', 'tcp-session', 'manual']), + manual_slot=dict(required=False, type='str'), + ) + argument_spec.update(ce_argument_spec) + module = NetStreamAging(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_export.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_export.py new file mode 100644 index 00000000..ac6e0287 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_export.py @@ -0,0 +1,557 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_export +short_description: Manages netstream export on HUAWEI CloudEngine switches. +description: + - Configure NetStream flow statistics exporting and versions for exported packets on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + type: + description: + - Specifies NetStream feature. + required: true + choices: ['ip', 'vxlan'] + source_ip: + description: + - Specifies source address which can be IPv6 or IPv4 of the exported NetStream packet. + host_ip: + description: + - Specifies destination address which can be IPv6 or IPv4 of the exported NetStream packet. + host_port: + description: + - Specifies the destination UDP port number of the exported packets. + The value is an integer that ranges from 1 to 65535. + host_vpn: + description: + - Specifies the VPN instance of the exported packets carrying flow statistics. + Ensure the VPN instance has been created on the device. + version: + description: + - Sets the version of exported packets. + choices: ['5', '9'] + as_option: + description: + - Specifies the AS number recorded in the statistics as the original or the peer AS number. + choices: ['origin', 'peer'] + bgp_nexthop: + description: + - Configures the statistics to carry BGP next hop information. Currently, only V9 supports the exported + packets carrying BGP next hop information. + choices: ['enable','disable'] + default: 'disable' + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Netstream export module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configures the source address for the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + source_ip: 192.8.2.2 + provider: "{{ cli }}" + + - name: Configures the source IP address for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + source_ip: 192.8.2.3 + provider: "{{ cli }}" + + - name: Configures the destination IP address and destination UDP port number for the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + host_ip: 192.8.2.4 + host_port: 25 + host_vpn: test + provider: "{{ cli }}" + + - name: Configures the destination IP address and destination UDP port number for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + host_ip: 192.8.2.5 + host_port: 26 + host_vpn: test + provider: "{{ cli }}" + + - name: Configures the version number of the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + version: 9 + as_option: origin + bgp_nexthop: enable + provider: "{{ cli }}" + + - name: Configures the version for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + version: 9 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "as_option": "origin", + "bgp_nexthop": "enable", + "host_ip": "192.8.5.6", + "host_port": "26", + "host_vpn": "test", + "source_ip": "192.8.2.5", + "state": "present", + "type": "ip", + "version": "9" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "as_option": null, + "bgp_nexthop": "disable", + "host_ip": null, + "host_port": null, + "host_vpn": null, + "source_ip": null, + "type": "ip", + "version": null + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "as_option": "origin", + "bgp_nexthop": "enable", + "host_ip": "192.8.5.6", + "host_port": "26", + "host_vpn": "test", + "source_ip": "192.8.2.5", + "type": "ip", + "version": "9" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "netstream export ip source 192.8.2.5", + "netstream export ip host 192.8.5.6 26 vpn-instance test", + "netstream export ip version 9 origin-as bgp-nexthop" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def is_ipv4_addr(ip_addr): + """check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + + return bool(re.match(ipv4_regex, ip_addr)) + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + test_cfg_tmp = test_cfg + ' *$' + '|' + test_cfg + ' *\n' + obj = re.compile(test_cfg_tmp) + result = re.findall(obj, cmp_cfg) + if not result: + return False + return True + + +class NetstreamExport(object): + """Manage NetStream export""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # NetStream export configuration parameters + self.type = self.module.params['type'] + self.source_ip = self.module.params['source_ip'] + self.host_ip = self.module.params['host_ip'] + self.host_port = self.module.params['host_port'] + self.host_vpn = self.module.params['host_vpn'] + self.version = self.module.params['version'] + self.as_option = self.module.params['as_option'] + self.bgp_netxhop = self.module.params['bgp_nexthop'] + self.state = self.module.params['state'] + + self.commands = list() + self.config = None + self.exist_conf = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_netstream_config(self): + """get current netstream configuration""" + + cmd = "display current-configuration | include ^netstream export" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + return config + + def get_existing(self): + """get existing config""" + + self.existing = dict(type=self.type, + source_ip=self.exist_conf['source_ip'], + host_ip=self.exist_conf['host_ip'], + host_port=self.exist_conf['host_port'], + host_vpn=self.exist_conf['host_vpn'], + version=self.exist_conf['version'], + as_option=self.exist_conf['as_option'], + bgp_nexthop=self.exist_conf['bgp_netxhop']) + + def get_proposed(self): + """get proposed config""" + + self.proposed = dict(type=self.type, + source_ip=self.source_ip, + host_ip=self.host_ip, + host_port=self.host_port, + host_vpn=self.host_vpn, + version=self.version, + as_option=self.as_option, + bgp_nexthop=self.bgp_netxhop, + state=self.state) + + def get_end_state(self): + """get end config""" + self.get_config_data() + self.end_state = dict(type=self.type, + source_ip=self.exist_conf['source_ip'], + host_ip=self.exist_conf['host_ip'], + host_port=self.exist_conf['host_port'], + host_vpn=self.exist_conf['host_vpn'], + version=self.exist_conf['version'], + as_option=self.exist_conf['as_option'], + bgp_nexthop=self.exist_conf['bgp_netxhop']) + + def show_result(self): + """show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + if cmd not in self.updates_cmd: + self.updates_cmd.append(cmd) # show updates result + + def config_nets_export_src_addr(self): + """Configures the source address for the exported packets""" + + if is_ipv4_addr(self.source_ip): + if self.type == 'ip': + cmd = "netstream export ip source %s" % self.source_ip + else: + cmd = "netstream export vxlan inner-ip source %s" % self.source_ip + else: + if self.type == 'ip': + cmd = "netstream export ip source ipv6 %s" % self.source_ip + else: + cmd = "netstream export vxlan inner-ip source ipv6 %s" % self.source_ip + + if is_config_exist(self.config, cmd): + self.exist_conf['source_ip'] = self.source_ip + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_host_addr(self): + """Configures the destination IP address and destination UDP port number""" + + if is_ipv4_addr(self.host_ip): + if self.type == 'ip': + cmd = 'netstream export ip host %s %s' % (self.host_ip, self.host_port) + else: + cmd = 'netstream export vxlan inner-ip host %s %s' % (self.host_ip, self.host_port) + else: + if self.type == 'ip': + cmd = 'netstream export ip host ipv6 %s %s' % (self.host_ip, self.host_port) + else: + cmd = 'netstream export vxlan inner-ip host ipv6 %s %s' % (self.host_ip, self.host_port) + + if self.host_vpn: + cmd += " vpn-instance %s" % self.host_vpn + + if is_config_exist(self.config, cmd): + self.exist_conf['host_ip'] = self.host_ip + self.exist_conf['host_port'] = self.host_port + if self.host_vpn: + self.exist_conf['host_vpn'] = self.host_vpn + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_vxlan_ver(self): + """Configures the version for the exported packets carrying VXLAN flexible flow statistics""" + + cmd = 'netstream export vxlan inner-ip version 9' + + if is_config_exist(self.config, cmd): + self.exist_conf['version'] = self.version + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_ip_ver(self): + """Configures the version number of the exported packets carrying IPv4 flow statistics""" + + cmd = 'netstream export ip version %s' % self.version + if self.version == '5': + if self.as_option == 'origin': + cmd += ' origin-as' + elif self.as_option == 'peer': + cmd += ' peer-as' + else: + if self.as_option == 'origin': + cmd += ' origin-as' + elif self.as_option == 'peer': + cmd += ' peer-as' + + if self.bgp_netxhop == 'enable': + cmd += ' bgp-nexthop' + + if cmd == 'netstream export ip version 5': + cmd_tmp = "netstream export ip version" + if cmd_tmp in self.config: + if self.state == 'present': + self.cli_add_command(cmd, False) + else: + self.exist_conf['version'] = self.version + return + + if is_config_exist(self.config, cmd): + self.exist_conf['version'] = self.version + self.exist_conf['as_option'] = self.as_option + self.exist_conf['bgp_netxhop'] = self.bgp_netxhop + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_netstream_export(self): + """configure netstream export""" + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + if not self.type: + self.module.fail_json(msg='Error: The value of type cannot be empty.') + + if self.host_port: + if not self.host_port.isdigit(): + self.module.fail_json(msg='Error: Host port is invalid.') + if int(self.host_port) < 1 or int(self.host_port) > 65535: + self.module.fail_json(msg='Error: Host port is not in the range from 1 to 65535.') + + if self.host_vpn: + if self.host_vpn == '_public_': + self.module.fail_json( + msg='Error: The host vpn name _public_ is reserved.') + if len(self.host_vpn) < 1 or len(self.host_vpn) > 31: + self.module.fail_json(msg='Error: The host vpn name length is not in the range from 1 to 31.') + + if self.type == 'vxlan' and self.version == '5': + self.module.fail_json(msg="Error: When type is vxlan, version must be 9.") + + if self.type == 'ip' and self.version == '5' and self.bgp_netxhop == 'enable': + self.module.fail_json(msg="Error: When type=ip and version=5, bgp_netxhop is not supported.") + + if (self.host_ip and not self.host_port) or (self.host_port and not self.host_ip): + self.module.fail_json(msg="Error: host_ip and host_port must both exist or not exist.") + + def get_config_data(self): + """get configuration commands and current configuration""" + + self.exist_conf['type'] = self.type + self.exist_conf['source_ip'] = None + self.exist_conf['host_ip'] = None + self.exist_conf['host_port'] = None + self.exist_conf['host_vpn'] = None + self.exist_conf['version'] = None + self.exist_conf['as_option'] = None + self.exist_conf['bgp_netxhop'] = 'disable' + + self.config = self.get_netstream_config() + + if self.type and self.source_ip: + self.config_nets_export_src_addr() + + if self.type and self.host_ip and self.host_port: + self.config_nets_export_host_addr() + + if self.type == 'vxlan' and self.version == '9': + self.config_nets_export_vxlan_ver() + + if self.type == 'ip' and self.version: + self.config_nets_export_ip_ver() + + def work(self): + """execute task""" + + self.check_params() + self.get_proposed() + self.get_config_data() + self.get_existing() + + self.config_netstream_export() + + self.get_end_state() + self.show_result() + + +def main(): + """main function entry""" + + argument_spec = dict( + type=dict(required=True, type='str', choices=['ip', 'vxlan']), + source_ip=dict(required=False, type='str'), + host_ip=dict(required=False, type='str'), + host_port=dict(required=False, type='str'), + host_vpn=dict(required=False, type='str'), + version=dict(required=False, type='str', choices=['5', '9']), + as_option=dict(required=False, type='str', choices=['origin', 'peer']), + bgp_nexthop=dict(required=False, type='str', choices=['enable', 'disable'], default='disable'), + state=dict(choices=['absent', 'present'], default='present', required=False) + ) + argument_spec.update(ce_argument_spec) + netstream_export = NetstreamExport(argument_spec) + netstream_export.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_global.py new file mode 100644 index 00000000..d2d45d90 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_global.py @@ -0,0 +1,942 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_global +short_description: Manages global parameters of NetStream on HUAWEI CloudEngine switches. +description: + - Manages global parameters of NetStream on HUAWEI CloudEngine switches. +author: YangYang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + type: + description: + - Specifies the type of netstream global. + choices: ['ip', 'vxlan'] + default: 'ip' + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present + interface: + description: + - Netstream global interface. + required: true + sampler_interval: + description: + - Specifies the netstream sampler interval, length is 1 - 65535. + sampler_direction: + description: + - Specifies the netstream sampler direction. + choices: ['inbound', 'outbound'] + statistics_direction: + description: + - Specifies the netstream statistic direction. + choices: ['inbound', 'outbound'] + statistics_record: + description: + - Specifies the flexible netstream statistic record, length is 1 - 32. + index_switch: + description: + - Specifies the netstream index-switch. + choices: ['16', '32'] + default: '16' +''' + +EXAMPLES = ''' +- name: Netstream global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure a netstream sampler at interface 10ge1/0/2, direction is outbound,interval is 30. + community.network.ce_netstream_global: + interface: 10ge1/0/2 + type: ip + sampler_interval: 30 + sampler_direction: outbound + state: present + provider: "{{ cli }}" + - name: Configure a netstream flexible statistic at interface 10ge1/0/2, record is test1, type is ip. + community.network.ce_netstream_global: + type: ip + interface: 10ge1/0/2 + statistics_record: test1 + provider: "{{ cli }}" + - name: Set the vxlan index-switch to 32. + community.network.ce_netstream_global: + type: vxlan + interface: all + index_switch: 32 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"index_switch": "16", + "interface": "10ge1/0/2", + "state": "present", + "statistics_record": "test", + "type": "vxlan"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"flexible_statistic": [ + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "ip" + }, + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "vxlan" + } + ], + "index-switch": [ + { + "index-switch": "16", + "type": "ip" + }, + { + "index-switch": "16", + "type": "vxlan" + } + ], + "ip_record": [ + "test", + "test1" + ], + "sampler": [ + { + "interface": "all", + "sampler_direction": "null", + "sampler_interval": "null" + } + ], + "statistic": [ + { + "interface": "10ge1/0/2", + "statistics_direction": [], + "type": "null" + } + ], + "vxlan_record": [ + "test" + ]} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"flexible_statistic": [ + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "ip" + }, + { + "interface": "10ge1/0/2", + "statistics_record": [ + "test" + ], + "type": "vxlan" + } + ], + "index-switch": [ + { + "index-switch": "16", + "type": "ip" + }, + { + "index-switch": "16", + "type": "vxlan" + } + ], + "sampler": [ + { + "interface": "all", + "sampler_direction": "null", + "sampler_interval": "null" + } + ], + "statistic": [ + { + "interface": "10ge1/0/2", + "statistics_direction": [], + "type": "null" + } + ]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10ge1/0/2", + "netstream record test vxlan inner-ip"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_connection, rm_config_prefix +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('ALL'): + iftype = 'all' + else: + return None + + return iftype.lower() + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache + """ + time_stamp_regex = re.compile(r'\s*\d{4}-\d{1,2}-\d{1,2}\s+\d{2}\:\d{2}\:\d{2}\.\d+\s*') + flags = [] if flags is None else flags + if isinstance(flags, str): + flags = [flags] + elif not isinstance(flags, list): + flags = [] + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + conn = get_connection(module) + rc, out, err = conn.exec_command(cmd) + if rc != 0: + module.fail_json(msg=err) + cfg = str(out).strip() + # remove default configuration prefix '~' + for flag in flags: + if "include-default" in flag: + cfg = rm_config_prefix(cfg) + break + lines = cfg.split('\n') + lines = [l for l in lines if time_stamp_regex.match(l) is None] + if cfg.startswith('display'): + if len(lines) > 1: + lines.pop(0) + else: + return '' + return '\n'.join(lines) + + +class NetStreamGlobal(object): + """ + Manages netstream global parameters. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.type = self.module.params['type'] + self.interface = self.module.params['interface'] + self.sampler_interval = self.module.params['sampler_interval'] + self.sampler_direction = self.module.params['sampler_direction'] + self.statistics_direction = self.module.params['statistics_direction'] + self.statistics_record = self.module.params['statistics_record'] + self.index_switch = self.module.params['index_switch'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # local parameters + self.existing["sampler"] = list() + self.existing["statistic"] = list() + self.existing["flexible_statistic"] = list() + self.existing["index-switch"] = list() + self.existing["ip_record"] = list() + self.existing["vxlan_record"] = list() + self.end_state["sampler"] = list() + self.end_state["statistic"] = list() + self.end_state["flexible_statistic"] = list() + self.end_state["index-switch"] = list() + self.sampler_changed = False + self.statistic_changed = False + self.flexible_changed = False + self.index_switch_changed = False + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) + + def get_exist_sampler_interval(self): + """get exist netstream sampler interval""" + + sampler_tmp = dict() + sampler_tmp1 = dict() + flags = list() + exp = " | ignore-case include ^netstream sampler random-packets" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp["sampler_interval"] = "null" + sampler_tmp["sampler_direction"] = "null" + sampler_tmp["interface"] = "null" + else: + config_list = config.split(' ') + config_num = len(config_list) + sampler_tmp["sampler_direction"] = config_list[config_num - 1] + sampler_tmp["sampler_interval"] = config_list[config_num - 2] + sampler_tmp["interface"] = "all" + self.existing["sampler"].append(sampler_tmp) + if self.interface != "all": + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream sampler random-packets" % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp1["sampler_interval"] = "null" + sampler_tmp1["sampler_direction"] = "null" + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + sampler_tmp1 = dict() + config_mem_list = config_mem.split(' ') + config_num = len(config_mem_list) + if config_num > 1: + sampler_tmp1["sampler_direction"] = config_mem_list[ + config_num - 1] + sampler_tmp1["sampler_interval"] = config_mem_list[ + config_num - 2] + sampler_tmp1["interface"] = self.interface + self.existing["sampler"].append(sampler_tmp1) + + def get_exist_statistic_record(self): + """get exist netstream statistic record parameter""" + + if self.statistics_record and self.statistics_direction: + self.module.fail_json( + msg='Error: The statistic direction and record can not exist at the same time.') + statistic_tmp = dict() + statistic_tmp1 = dict() + statistic_tmp["statistics_record"] = list() + statistic_tmp["interface"] = self.interface + statistic_tmp1["statistics_record"] = list() + statistic_tmp1["interface"] = self.interface + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream record"\ + % (self.interface) + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp["type"] = "ip" + self.existing["flexible_statistic"].append(statistic_tmp) + statistic_tmp1["type"] = "vxlan" + self.existing["flexible_statistic"].append(statistic_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + statistic_tmp["statistics_record"] = list() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "ip": + statistic_tmp["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp["type"] = "ip" + self.existing["flexible_statistic"].append(statistic_tmp) + for config_mem in config_list: + statistic_tmp1["statistics_record"] = list() + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "vxlan": + statistic_tmp1["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp1["type"] = "vxlan" + self.existing["flexible_statistic"].append(statistic_tmp1) + + def get_exist_interface_statistic(self): + """get exist netstream interface statistic parameter""" + + statistic_tmp1 = dict() + statistic_tmp1["statistics_direction"] = list() + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream inbound|outbound"\ + % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp1["type"] = "null" + else: + statistic_tmp1["type"] = "ip" + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 1: + statistic_tmp1["statistics_direction"].append( + str(config_mem_list[1])) + statistic_tmp1["interface"] = self.interface + self.existing["statistic"].append(statistic_tmp1) + + def get_exist_index_switch(self): + """get exist netstream index-switch""" + + index_switch_tmp = dict() + index_switch_tmp1 = dict() + index_switch_tmp["index-switch"] = "16" + index_switch_tmp["type"] = "ip" + index_switch_tmp1["index-switch"] = "16" + index_switch_tmp1["type"] = "vxlan" + flags = list() + exp = " | ignore-case include index-switch" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + self.existing["index-switch"].append(index_switch_tmp) + self.existing["index-switch"].append(index_switch_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "ip": + index_switch_tmp["index-switch"] = "32" + index_switch_tmp["type"] = "ip" + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "vxlan": + index_switch_tmp1["index-switch"] = "32" + index_switch_tmp1["type"] = "vxlan" + self.existing["index-switch"].append(index_switch_tmp) + self.existing["index-switch"].append(index_switch_tmp1) + + def get_exist_record(self): + """get exist netstream record""" + + flags = list() + exp = " | ignore-case include netstream record" + flags.append(exp) + config = get_config(self.module, flags) + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and config_mem_list[3] == "ip": + self.existing["ip_record"].append(config_mem_list[2]) + if len(config_mem_list) > 3 and config_mem_list[3] == "vxlan": + self.existing["vxlan_record"].append(config_mem_list[2]) + + def get_end_sampler_interval(self): + """get end netstream sampler interval""" + + sampler_tmp = dict() + sampler_tmp1 = dict() + flags = list() + exp = " | ignore-case include ^netstream sampler random-packets" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp["sampler_interval"] = "null" + sampler_tmp["sampler_direction"] = "null" + else: + config_list = config.split(' ') + config_num = len(config_list) + if config_num > 1: + sampler_tmp["sampler_direction"] = config_list[config_num - 1] + sampler_tmp["sampler_interval"] = config_list[config_num - 2] + sampler_tmp["interface"] = "all" + self.end_state["sampler"].append(sampler_tmp) + if self.interface != "all": + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream sampler random-packets" % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp1["sampler_interval"] = "null" + sampler_tmp1["sampler_direction"] = "null" + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + sampler_tmp1 = dict() + config_mem_list = config_mem.split(' ') + config_num = len(config_mem_list) + if config_num > 1: + sampler_tmp1["sampler_direction"] = config_mem_list[ + config_num - 1] + sampler_tmp1["sampler_interval"] = config_mem_list[ + config_num - 2] + sampler_tmp1["interface"] = self.interface + self.end_state["sampler"].append(sampler_tmp1) + + def get_end_statistic_record(self): + """get end netstream statistic record parameter""" + + if self.statistics_record and self.statistics_direction: + self.module.fail_json( + msg='Error: The statistic direction and record can not exist at the same time.') + statistic_tmp = dict() + statistic_tmp1 = dict() + statistic_tmp["statistics_record"] = list() + statistic_tmp["interface"] = self.interface + statistic_tmp1["statistics_record"] = list() + statistic_tmp1["interface"] = self.interface + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream record"\ + % (self.interface) + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp["type"] = "ip" + self.end_state["flexible_statistic"].append(statistic_tmp) + statistic_tmp1["type"] = "vxlan" + self.end_state["flexible_statistic"].append(statistic_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + statistic_tmp["statistics_record"] = list() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "ip": + statistic_tmp["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp["type"] = "ip" + self.end_state["flexible_statistic"].append(statistic_tmp) + for config_mem in config_list: + statistic_tmp1["statistics_record"] = list() + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "vxlan": + statistic_tmp1["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp1["type"] = "vxlan" + self.end_state["flexible_statistic"].append(statistic_tmp1) + + def get_end_interface_statistic(self): + """get end netstream interface statistic parameters""" + + statistic_tmp1 = dict() + statistic_tmp1["statistics_direction"] = list() + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream inbound|outbound"\ + % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp1["type"] = "null" + else: + statistic_tmp1["type"] = "ip" + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 1: + statistic_tmp1["statistics_direction"].append( + str(config_mem_list[1])) + statistic_tmp1["interface"] = self.interface + self.end_state["statistic"].append(statistic_tmp1) + + def get_end_index_switch(self): + """get end netstream index switch""" + + index_switch_tmp = dict() + index_switch_tmp1 = dict() + index_switch_tmp["index-switch"] = "16" + index_switch_tmp["type"] = "ip" + index_switch_tmp1["index-switch"] = "16" + index_switch_tmp1["type"] = "vxlan" + flags = list() + exp = " | ignore-case include index-switch" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + self.end_state["index-switch"].append(index_switch_tmp) + self.end_state["index-switch"].append(index_switch_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "ip": + index_switch_tmp["index-switch"] = "32" + index_switch_tmp["type"] = "ip" + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "vxlan": + index_switch_tmp1["index-switch"] = "32" + index_switch_tmp1["type"] = "vxlan" + self.end_state["index-switch"].append(index_switch_tmp) + self.end_state["index-switch"].append(index_switch_tmp1) + + def check_params(self): + """check all input params""" + + # netstream parameters check + if not get_interface_type(self.interface): + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + if self.sampler_interval: + if not str(self.sampler_interval).isdigit(): + self.module.fail_json( + msg='Error: Active interval should be numerical.') + if int(self.sampler_interval) < 1 or int(self.sampler_interval) > 65535: + self.module.fail_json( + msg="Error: Sampler interval should between 1 - 65535.") + if self.statistics_record: + if len(self.statistics_record) < 1 or len(self.statistics_record) > 32: + self.module.fail_json( + msg="Error: Statistic record length should between 1 - 32.") + if self.interface == "all": + if self.statistics_record or self.statistics_direction: + self.module.fail_json( + msg="Error: Statistic function should be used at interface.") + if self.statistics_direction: + if self.type == "vxlan": + self.module.fail_json( + msg="Error: Vxlan do not support inbound or outbound statistic.") + if (self.sampler_interval and not self.sampler_direction) \ + or (self.sampler_direction and not self.sampler_interval): + self.module.fail_json( + msg="Error: Sampler interval and direction must be set at the same time.") + + if self.statistics_record and not self.type: + self.module.fail_json( + msg="Error: Statistic type and record must be set at the same time.") + + self.get_exist_record() + if self.statistics_record: + if self.type == "ip": + if self.statistics_record not in self.existing["ip_record"]: + self.module.fail_json( + msg="Error: The statistic record is not exist.") + if self.type == "vxlan": + if self.statistics_record not in self.existing["vxlan_record"]: + self.module.fail_json( + msg="Error: The statistic record is not exist.") + + def get_proposed(self): + """get proposed info""" + + if self.type: + self.proposed["type"] = self.type + if self.interface: + self.proposed["interface"] = self.interface + if self.sampler_interval: + self.proposed["sampler_interval"] = self.sampler_interval + if self.sampler_direction: + self.proposed["sampler_direction"] = self.sampler_direction + if self.statistics_direction: + self.proposed["statistics_direction"] = self.statistics_direction + if self.statistics_record: + self.proposed["statistics_record"] = self.statistics_record + if self.index_switch: + self.proposed["index_switch"] = self.index_switch + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + sampler_tmp = dict() + statistic_tmp = dict() + statistic_tmp1 = dict() + index_tmp = dict() + temp = False + + self.get_exist_sampler_interval() + self.get_exist_interface_statistic() + self.get_exist_statistic_record() + self.get_exist_index_switch() + + if self.state == "present": + for sampler_tmp in self.existing["sampler"]: + if self.interface == str(sampler_tmp["interface"]): + temp = True + if (self.sampler_interval and str(sampler_tmp["sampler_interval"]) != self.sampler_interval) \ + or (self.sampler_direction and + str(sampler_tmp["sampler_direction"]) != self.sampler_direction): + self.sampler_changed = True + if not temp: + if self.sampler_direction or self.sampler_interval: + self.sampler_changed = True + for statistic_tmp in self.existing["statistic"]: + if str(statistic_tmp["interface"]) == self.interface and self.interface != "all": + if self.type == "vxlan": + if statistic_tmp["statistics_direction"] \ + and 'outbound' in statistic_tmp["statistics_direction"]: + self.module.fail_json( + msg='Error: The NetStream record vxlan ' + 'cannot be configured because the port has been configured NetStream outbound ip.') + if statistic_tmp["statistics_direction"] and self.statistics_direction: + if self.statistics_direction not in statistic_tmp["statistics_direction"]: + self.statistic_changed = True + else: + if self.statistics_direction: + self.statistic_changed = True + for statistic_tmp1 in self.existing["flexible_statistic"]: + if self.interface != "all" \ + and self.type == str(statistic_tmp1["type"]) \ + and self.interface == str(statistic_tmp1["interface"]): + if statistic_tmp1["statistics_record"] and self.statistics_record: + if self.statistics_record not in statistic_tmp1["statistics_record"]: + self.flexible_changed = True + else: + if self.statistics_record: + self.flexible_changed = True + for index_tmp in self.existing["index-switch"]: + if self.type == str(index_tmp["type"]): + if self.index_switch != str(index_tmp["index-switch"]): + self.index_switch_changed = True + else: + for sampler_tmp in self.existing["sampler"]: + if self.interface == str(sampler_tmp["interface"]): + if (self.sampler_interval and str(sampler_tmp["sampler_interval"]) == self.sampler_interval) \ + and (self.sampler_direction and str(sampler_tmp["sampler_direction"]) == self.sampler_direction): + self.sampler_changed = True + for statistic_tmp in self.existing["statistic"]: + if str(statistic_tmp["interface"]) == self.interface and self.interface != "all": + if len(statistic_tmp["statistics_direction"]) and self.statistics_direction: + if self.statistics_direction in statistic_tmp["statistics_direction"]: + self.statistic_changed = True + for statistic_tmp1 in self.existing["flexible_statistic"]: + if self.interface != "all" \ + and self.type == str(statistic_tmp1["type"]) \ + and self.interface == str(statistic_tmp1["interface"]): + if len(statistic_tmp1["statistics_record"]) and self.statistics_record: + if self.statistics_record in statistic_tmp1["statistics_record"]: + self.flexible_changed = True + for index_tmp in self.existing["index-switch"]: + if self.type == str(index_tmp["type"]): + if self.index_switch == str(index_tmp["index-switch"]): + if self.index_switch != "16": + self.index_switch_changed = True + + def operate_ns_gloabl(self): + """configure netstream global parameters""" + + cmd = "" + if not self.sampler_changed and not self.statistic_changed \ + and not self.flexible_changed and not self.index_switch_changed: + self.changed = False + return + + if self.sampler_changed is True: + if self.type == "vxlan": + self.module.fail_json( + msg="Error: Netstream do not support vxlan sampler.") + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + cmd = "netstream sampler random-packets %s %s" % ( + self.sampler_interval, self.sampler_direction) + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.statistic_changed is True: + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + cmd = "netstream %s ip" % self.statistics_direction + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.flexible_changed is True: + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + if self.state == "present": + for statistic_tmp in self.existing["flexible_statistic"]: + tmp_list = statistic_tmp["statistics_record"] + if self.type == statistic_tmp["type"]: + if self.type == "ip": + if len(tmp_list) > 0: + cmd = "netstream record %s ip" % tmp_list[0] + self.cli_add_command(cmd, undo=True) + cmd = "netstream record %s ip" % self.statistics_record + self.cli_add_command(cmd) + if self.type == "vxlan": + if len(tmp_list) > 0: + cmd = "netstream record %s vxlan inner-ip" % tmp_list[ + 0] + self.cli_add_command(cmd, undo=True) + cmd = "netstream record %s vxlan inner-ip" % self.statistics_record + self.cli_add_command(cmd) + else: + if self.type == "ip": + cmd = "netstream record %s ip" % self.statistics_record + self.cli_add_command(cmd, undo=True) + if self.type == "vxlan": + cmd = "netstream record %s vxlan inner-ip" % self.statistics_record + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.index_switch_changed is True: + if self.interface != "all": + self.module.fail_json( + msg="Error: Index-switch function should be used globally.") + if self.type == "ip": + cmd = "netstream export ip index-switch %s" % self.index_switch + else: + cmd = "netstream export vxlan inner-ip index-switch %s" % self.index_switch + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def get_end_state(self): + """get end state info""" + + self.get_end_sampler_interval() + self.get_end_interface_statistic() + self.get_end_statistic_record() + self.get_end_index_switch() + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_ns_gloabl() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + type=dict(required=False, choices=['ip', 'vxlan'], default='ip'), + interface=dict(required=True, type='str'), + sampler_interval=dict(required=False, type='str'), + sampler_direction=dict(required=False, choices=['inbound', 'outbound']), + statistics_direction=dict(required=False, choices=['inbound', 'outbound']), + statistics_record=dict(required=False, type='str'), + index_switch=dict(required=False, choices=['16', '32'], default='16'), + state=dict(required=False, choices=['present', 'absent'], default='present'), + ) + argument_spec.update(ce_argument_spec) + module = NetStreamGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_template.py new file mode 100644 index 00000000..38d9fede --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_netstream_template.py @@ -0,0 +1,494 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_template +short_description: Manages NetStream template configuration on HUAWEI CloudEngine switches. +description: + - Manages NetStream template configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + type: + description: + - Configure the type of netstream record. + required: true + choices: ['ip', 'vxlan'] + record_name: + description: + - Configure the name of netstream record. + The value is a string of 1 to 32 case-insensitive characters. + match: + description: + - Configure flexible flow statistics template keywords. + choices: ['destination-address', 'destination-port', 'tos', 'protocol', 'source-address', 'source-port'] + collect_counter: + description: + - Configure the number of packets and bytes that are included in the flexible flow statistics sent to NSC. + choices: ['bytes', 'packets'] + collect_interface: + description: + - Configure the input or output interface that are included in the flexible flow statistics sent to NSC. + choices: ['input', 'output'] + description: + description: + - Configure the description of netstream record. + The value is a string of 1 to 80 case-insensitive characters. +''' + +EXAMPLES = ''' +- name: Netstream template module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config ipv4 netstream record + community.network.ce_netstream_template: + state: present + type: ip + record_name: test + provider: "{{ cli }}" + - name: Undo ipv4 netstream record + community.network.ce_netstream_template: + state: absent + type: ip + record_name: test + provider: "{{ cli }}" + - name: Config ipv4 netstream record collect_counter + community.network.ce_netstream_template: + state: present + type: ip + record_name: test + collect_counter: bytes + provider: "{{ cli }}" + - name: Undo ipv4 netstream record collect_counter + community.network.ce_netstream_template: + state: absent + type: ip + record_name: test + collect_counter: bytes + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"record_name": "test", + "type": "ip", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"record_name": "test", + "type": "ip"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["netstream record test ip"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_connection, rm_config_prefix +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + if isinstance(flags, str): + flags = [flags] + elif not isinstance(flags, list): + flags = [] + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + conn = get_connection(module) + rc, out, err = conn.exec_command(cmd) + if rc != 0: + module.fail_json(msg=err) + cfg = str(out).strip() + # remove default configuration prefix '~' + for flag in flags: + if "include-default" in flag: + cfg = rm_config_prefix(cfg) + break + if cfg.startswith('display'): + lines = cfg.split('\n') + if len(lines) > 1: + return '\n'.join(lines[1:]) + else: + return '' + return cfg + + +class NetstreamTemplate(object): + """ Manages netstream template configuration """ + + def __init__(self, **kwargs): + """ Netstream template module init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # netstream config + self.netstream_cfg = None + + # module args + self.state = self.module.params['state'] or None + self.type = self.module.params['type'] or None + self.record_name = self.module.params['record_name'] or None + self.match = self.module.params['match'] or None + self.collect_counter = self.module.params['collect_counter'] or None + self.collect_interface = self.module.params['collect_interface'] or None + self.description = self.module.params['description'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def cli_load_config(self, commands): + """ Cli load configuration """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_netstream_config(self): + """ Cli get netstream configuration """ + + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + flags = list() + regular = "| section include %s" % cmd + flags.append(regular) + self.netstream_cfg = get_config(self.module, flags) + + def check_args(self): + """ Check module args """ + + if not self.type or not self.record_name: + self.module.fail_json( + msg='Error: Please input type and record_name.') + + if self.record_name: + if len(self.record_name) < 1 or len(self.record_name) > 32: + self.module.fail_json( + msg='Error: The len of record_name is out of [1 - 32].') + + if self.description: + if len(self.description) < 1 or len(self.description) > 80: + self.module.fail_json( + msg='Error: The len of description is out of [1 - 80].') + + def get_proposed(self): + """ Get module proposed """ + + self.proposed["state"] = self.state + + if self.type: + self.proposed["type"] = self.type + if self.record_name: + self.proposed["record_name"] = self.record_name + if self.match: + self.proposed["match"] = self.match + if self.collect_counter: + self.proposed["collect_counter"] = self.collect_counter + if self.collect_interface: + self.proposed["collect_interface"] = self.collect_interface + if self.description: + self.proposed["description"] = self.description + + def get_existing(self): + """ Get existing configuration """ + + self.cli_get_netstream_config() + + if self.netstream_cfg is not None and "netstream record" in self.netstream_cfg: + self.existing["type"] = self.type + self.existing["record_name"] = self.record_name + + if self.description: + tmp_value = re.findall(r'description (.*)', self.netstream_cfg) + if tmp_value is not None and len(tmp_value) > 0: + self.existing["description"] = tmp_value[0] + + if self.match: + if self.type == "ip": + tmp_value = re.findall(r'match ip (.*)', self.netstream_cfg) + else: + tmp_value = re.findall(r'match inner-ip (.*)', self.netstream_cfg) + + if tmp_value: + self.existing["match"] = tmp_value + + if self.collect_counter: + tmp_value = re.findall(r'collect counter (.*)', self.netstream_cfg) + if tmp_value: + self.existing["collect_counter"] = tmp_value + + if self.collect_interface: + tmp_value = re.findall(r'collect interface (.*)', self.netstream_cfg) + if tmp_value: + self.existing["collect_interface"] = tmp_value + + def get_end_state(self): + """ Get end state """ + + self.cli_get_netstream_config() + + if self.netstream_cfg is not None and "netstream record" in self.netstream_cfg: + self.end_state["type"] = self.type + self.end_state["record_name"] = self.record_name + + if self.description: + tmp_value = re.findall(r'description (.*)', self.netstream_cfg) + if tmp_value is not None and len(tmp_value) > 0: + self.end_state["description"] = tmp_value[0] + + if self.match: + if self.type == "ip": + tmp_value = re.findall(r'match ip (.*)', self.netstream_cfg) + else: + tmp_value = re.findall(r'match inner-ip (.*)', self.netstream_cfg) + + if tmp_value: + self.end_state["match"] = tmp_value + + if self.collect_counter: + tmp_value = re.findall(r'collect counter (.*)', self.netstream_cfg) + if tmp_value: + self.end_state["collect_counter"] = tmp_value + + if self.collect_interface: + tmp_value = re.findall(r'collect interface (.*)', self.netstream_cfg) + if tmp_value: + self.end_state["collect_interface"] = tmp_value + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def present_netstream(self): + """ Present netstream configuration """ + + cmds = list() + need_create_record = False + + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + cmds.append(cmd) + + if self.existing.get('record_name') != self.record_name: + self.updates_cmd.append(cmd) + need_create_record = True + + if self.description: + cmd = "description %s" % self.description.strip() + if need_create_record or not self.netstream_cfg or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.match: + if self.type == "ip": + cmd = "match ip %s" % self.match + cfg = "match ip" + else: + cmd = "match inner-ip %s" % self.match + cfg = "match inner-ip" + + if need_create_record or cfg not in self.netstream_cfg or self.match != self.existing["match"][0]: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_counter: + cmd = "collect counter %s" % self.collect_counter + if need_create_record or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_interface: + cmd = "collect interface %s" % self.collect_interface + if need_create_record or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def absent_netstream(self): + """ Absent netstream configuration """ + + cmds = list() + absent_netstream_attr = False + + if not self.netstream_cfg: + return + + if self.description or self.match or self.collect_counter or self.collect_interface: + absent_netstream_attr = True + + if absent_netstream_attr: + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + + cmds.append(cmd) + + if self.description: + cfg = "description %s" % self.description + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo description %s" % self.description + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.match: + if self.type == "ip": + cfg = "match ip %s" % self.match + else: + cfg = "match inner-ip %s" % self.match + if self.netstream_cfg and cfg in self.netstream_cfg: + if self.type == "ip": + cmd = "undo match ip %s" % self.match + else: + cmd = "undo match inner-ip %s" % self.match + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_counter: + cfg = "collect counter %s" % self.collect_counter + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo collect counter %s" % self.collect_counter + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_interface: + cfg = "collect interface %s" % self.collect_interface + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo collect interface %s" % self.collect_interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if len(cmds) > 1: + self.cli_load_config(cmds) + self.changed = True + + else: + if self.type == "ip": + cmd = "undo netstream record %s ip" % self.record_name + else: + cmd = "undo netstream record %s vxlan inner-ip" % self.record_name + + cmds.append(cmd) + self.updates_cmd.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + self.present_netstream() + else: + self.absent_netstream() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + type=dict(choices=['ip', 'vxlan'], required=True), + record_name=dict(type='str'), + match=dict(choices=['destination-address', 'destination-port', + 'tos', 'protocol', 'source-address', 'source-port']), + collect_counter=dict(choices=['bytes', 'packets']), + collect_interface=dict(choices=['input', 'output']), + description=dict(type='str') + ) + argument_spec.update(ce_argument_spec) + module = NetstreamTemplate(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp.py new file mode 100644 index 00000000..3603658c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp.py @@ -0,0 +1,615 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ntp +short_description: Manages core NTP configuration on HUAWEI CloudEngine switches. +description: + - Manages core NTP configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + server: + description: + - Network address of NTP server. + peer: + description: + - Network address of NTP peer. + key_id: + description: + - Authentication key identifier to use with given NTP server or peer. + is_preferred: + description: + - Makes given NTP server or peer the preferred NTP server or peer for the device. + choices: ['enable', 'disable'] + vpn_name: + description: + - Makes the device communicate with the given + NTP server or peer over a specific vpn. + default: '_public_' + source_int: + description: + - Local source interface from which NTP messages are sent. + Must be fully qualified interface name, i.e. C(40GE1/0/22), C(vlanif10). + Interface types, such as C(10GE), C(40GE), C(100GE), C(Eth-Trunk), C(LoopBack), + C(MEth), C(NULL), C(Tunnel), C(Vlanif). + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: NTP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Set NTP Server with parameters" + community.network.ce_ntp: + server: 192.8.2.6 + vpn_name: js + source_int: vlanif4001 + is_preferred: enable + key_id: 32 + provider: "{{ cli }}" + + - name: "Set NTP Peer with parameters" + community.network.ce_ntp: + peer: 192.8.2.6 + vpn_name: js + source_int: vlanif4001 + is_preferred: enable + key_id: 32 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "48", + "is_preferred": "enable", "vpn_name":"js", + "source_int": "vlanif4002", "state":"present"} +existing: + description: k/v pairs of existing ntp server/peer + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "32", + "is_preferred": "disable", "vpn_name":"js", + "source_int": "vlanif4002"} +end_state: + description: k/v pairs of ntp info after module execution + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "48", + "is_preferred": "enable", "vpn_name":"js", + "source_int": "vlanif4002"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["ntp server 2.2.2.2 authentication-keyid 48 source-interface vlanif4002 vpn-instance js preferred"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config + +CE_NC_GET_NTP_CONFIG = """ + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_NTP_CONFIG = """ + + + + + %s + %s + %s + %s + %s + %s + %s + %s + 0-0 + + + + +""" + +CE_NC_DELETE_NTP_CONFIG = """ + + + + + %s + %s + %s + %s + %s + 0-0 + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Ntp(object): + """Ntp class""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.mutually_exclusive = [('server', 'peer')] + self.init_module() + + # ntp configuration info + self.server = self.module.params['server'] or None + self.peer = self.module.params['peer'] or None + self.key_id = self.module.params['key_id'] + self.is_preferred = self.module.params['is_preferred'] + self.vpn_name = self.module.params['vpn_name'] + self.interface = self.module.params['source_int'] or "" + self.state = self.module.params['state'] + self.ntp_conf = dict() + self.conf_exsit = False + self.ip_ver = 'IPv4' + + if self.server: + self.peer_type = 'Server' + self.address = self.server + elif self.peer: + self.peer_type = 'Peer' + self.address = self.peer + else: + self.peer_type = None + self.address = None + + self.check_params() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + self.init_data() + + def init_data(self): + """Init data""" + + if self.interface is not None: + self.interface = self.interface.lower() + + if not self.key_id: + self.key_id = "" + + if not self.is_preferred: + self.is_preferred = 'disable' + + def init_module(self): + """Init module""" + + required_one_of = [("server", "peer")] + self.module = AnsibleModule( + argument_spec=self.spec, + supports_check_mode=True, + required_one_of=required_one_of, + mutually_exclusive=self.mutually_exclusive + ) + + def check_ipaddr_validate(self): + """Check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + ipv6_regex = '^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$' + + flag = False + if bool(re.match(ipv4_regex, self.address)): + flag = True + self.ip_ver = "IPv4" + if not self.ntp_ucast_ipv4_validate(): + flag = False + elif bool(re.match(ipv6_regex, self.address)): + flag = True + self.ip_ver = "IPv6" + else: + flag = True + self.ip_ver = "IPv6" + + if not flag: + if self.peer_type == "Server": + self.module.fail_json(msg='Error: Illegal server ip-address.') + else: + self.module.fail_json(msg='Error: Illegal peer ip-address.') + + def ntp_ucast_ipv4_validate(self): + """Check ntp ucast ipv4 address""" + + addr_list = re.findall(r'(.*)\.(.*)\.(.*)\.(.*)', self.address) + if not addr_list: + self.module.fail_json(msg='Error: Match ip-address fail.') + + value = ((int(addr_list[0][0])) * 0x1000000) + (int(addr_list[0][1]) * 0x10000) + \ + (int(addr_list[0][2]) * 0x100) + (int(addr_list[0][3])) + if (value & (0xff000000) == 0x7f000000) or (value & (0xF0000000) == 0xF0000000) \ + or (value & (0xF0000000) == 0xE0000000) or (value == 0): + return False + return True + + def check_params(self): + """Check all input params""" + + # check interface type + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if self.vpn_name: + if (len(self.vpn_name) < 1) or (len(self.vpn_name) > 31): + self.module.fail_json( + msg='Error: VPN name length is between 1 and 31.') + + if self.address: + self.check_ipaddr_validate() + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_ntp(self, *args): + """Configure ntp parameters""" + + if self.state == 'present': + if self.ip_ver == 'IPv4': + xml_str = CE_NC_MERGE_NTP_CONFIG % ( + args[0], args[1], '::', args[2], args[3], args[4], args[5], args[6]) + elif self.ip_ver == 'IPv6': + xml_str = CE_NC_MERGE_NTP_CONFIG % ( + args[0], '0.0.0.0', args[1], args[2], args[3], args[4], args[5], args[6]) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "NTP_CORE_CONFIG") + else: + if self.ip_ver == 'IPv4': + xml_str = CE_NC_DELETE_NTP_CONFIG % ( + args[0], args[1], '::', args[2], args[3]) + elif self.ip_ver == 'IPv6': + xml_str = CE_NC_DELETE_NTP_CONFIG % ( + args[0], '0.0.0.0', args[1], args[2], args[3]) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "UNDO_NTP_CORE_CONFIG") + + def config_ntp(self): + """Config ntp""" + + if self.state == "present": + if self.address and not self.conf_exsit: + if self.is_preferred == 'enable': + is_preferred = 'true' + else: + is_preferred = 'false' + self.set_ntp(self.ip_ver, self.address, self.peer_type, + self.vpn_name, self.key_id, is_preferred, self.interface) + self.changed = True + else: + if self.address: + self.set_ntp(self.ip_ver, self.address, + self.peer_type, self.vpn_name, '', '', '') + self.changed = True + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_ntp_exist_config(self): + """Get ntp existed configure""" + + ntp_config = list() + conf_str = CE_NC_GET_NTP_CONFIG + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return ntp_config + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("ntp/ntpUCastCfgs/ntpUCastCfg") + for nexthop in ntpsite: + ntp_dict = dict() + for ele in nexthop: + if ele.tag in ["addrFamily", "vpnName", "ifName", "ipv4Addr", + "ipv6Addr", "type", "isPreferred", "keyId"]: + ntp_dict[ele.tag] = ele.text + + ip_addr = ntp_dict['ipv6Addr'] + if ntp_dict['addrFamily'] == "IPv4": + ip_addr = ntp_dict['ipv4Addr'] + if ntp_dict['ifName'] is None: + ntp_dict['ifName'] = "" + if ntp_dict['isPreferred'] == 'true': + is_preferred = 'enable' + else: + is_preferred = 'disable' + + if self.state == "present": + key_id = ntp_dict['keyId'] or "" + cur_ntp_cfg = dict(vpn_name=ntp_dict['vpnName'], source_int=ntp_dict['ifName'].lower(), address=ip_addr, + peer_type=ntp_dict['type'], prefer=is_preferred, key_id=key_id) + exp_ntp_cfg = dict(vpn_name=self.vpn_name, source_int=self.interface.lower(), address=self.address, + peer_type=self.peer_type, prefer=self.is_preferred, key_id=self.key_id) + if cur_ntp_cfg == exp_ntp_cfg: + self.conf_exsit = True + + vpn_name = ntp_dict['vpnName'] + if ntp_dict['vpnName'] == "_public_": + vpn_name = None + + if_name = ntp_dict['ifName'] + if if_name == "": + if_name = None + if self.peer_type == 'Server': + ntp_config.append(dict(vpn_name=vpn_name, + source_int=if_name, server=ip_addr, + is_preferred=is_preferred, key_id=ntp_dict['keyId'])) + else: + ntp_config.append(dict(vpn_name=vpn_name, + source_int=if_name, peer=ip_addr, + is_preferred=is_preferred, key_id=ntp_dict['keyId'])) + + return ntp_config + + def get_existing(self): + """Get existing info""" + + if self.address: + self.existing = self.get_ntp_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.address: + vpn_name = self.vpn_name + if vpn_name == "_public_": + vpn_name = None + + if_name = self.interface + if if_name == "": + if_name = None + + key_id = self.key_id + if key_id == "": + key_id = None + if self.peer_type == 'Server': + self.proposed = dict(state=self.state, vpn_name=vpn_name, + source_int=if_name, server=self.address, + is_preferred=self.is_preferred, key_id=key_id) + else: + self.proposed = dict(state=self.state, vpn_name=vpn_name, + source_int=if_name, peer=self.address, + is_preferred=self.is_preferred, key_id=key_id) + + def get_end_state(self): + """Get end state info""" + + if self.address: + self.end_state = self.get_ntp_exist_config() + + def get_update_cmd(self): + """Get updated commands""" + + if self.conf_exsit: + return + + cli_str = "" + if self.state == "present": + if self.address: + if self.peer_type == 'Server': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "ntp unicast-server", self.address) + else: + cli_str = "%s %s" % ( + "ntp unicast-server ipv6", self.address) + elif self.peer_type == 'Peer': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ("ntp unicast-peer", self.address) + else: + cli_str = "%s %s" % ( + "ntp unicast-peer ipv6", self.address) + + if self.key_id: + cli_str = "%s %s %s" % ( + cli_str, "authentication-keyid", self.key_id) + if self.interface: + cli_str = "%s %s %s" % ( + cli_str, "source-interface", self.interface) + if (self.vpn_name) and (self.vpn_name != '_public_'): + cli_str = "%s %s %s" % ( + cli_str, "vpn-instance", self.vpn_name) + if self.is_preferred == "enable": + cli_str = "%s %s" % (cli_str, "preferred") + else: + if self.address: + if self.peer_type == 'Server': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "undo ntp unicast-server", self.address) + else: + cli_str = "%s %s" % ( + "undo ntp unicast-server ipv6", self.address) + elif self.peer_type == 'Peer': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "undo ntp unicast-peer", self.address) + else: + cli_str = "%s %s" % ( + "undo ntp unicast-peer ipv6", self.address) + if (self.vpn_name) and (self.vpn_name != '_public_'): + cli_str = "%s %s %s" % ( + cli_str, "vpn-instance", self.vpn_name) + + self.updates_cmd.append(cli_str) + + def work(self): + """Execute task""" + + self.get_existing() + self.get_proposed() + + self.config_ntp() + + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + server=dict(type='str'), + peer=dict(type='str'), + key_id=dict(type='str'), + is_preferred=dict(type='str', choices=['enable', 'disable']), + vpn_name=dict(type='str', default='_public_'), + source_int=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + ntp_obj = Ntp(argument_spec) + ntp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp_auth.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp_auth.py new file mode 100644 index 00000000..e44d65f6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ntp_auth.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_ntp_auth +short_description: Manages NTP authentication configuration on HUAWEI CloudEngine switches. +description: + - Manages NTP authentication configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - 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) and C(authentication=on), authentication will be turned on. + - If C(state=absent) and C(authentication=off), authentication will be turned off. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + key_id: + description: + - Authentication key identifier (numeric). + required: true + auth_pwd: + description: + - Plain text with length of 1 to 255, encrypted text with length of 20 to 392. + auth_mode: + description: + - Specify authentication algorithm. + choices: ['hmac-sha256', 'md5'] + auth_type: + description: + - Whether the given password is in cleartext or + has been encrypted. If in cleartext, the device + will encrypt it before storing it. + default: encrypt + choices: ['text', 'encrypt'] + trusted_key: + description: + - Whether the given key is required to be supplied by a time source + for the device to synchronize to the time source. + default: 'disable' + choices: ['enable', 'disable'] + authentication: + description: + - Configure ntp authentication enable or unconfigure ntp authentication enable. + choices: ['enable', 'disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: NTP AUTH test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure ntp authentication key-id" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + provider: "{{ cli }}" + + - name: "Configure ntp authentication key-id and trusted authentication keyid" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + trusted_key: enable + provider: "{{ cli }}" + + - name: "Configure ntp authentication key-id and authentication enable" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + authentication: enable + provider: "{{ cli }}" + + - name: "Unconfigure ntp authentication key-id and trusted authentication keyid" + community.network.ce_ntp_auth: + key_id: 32 + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure ntp authentication key-id and authentication enable" + community.network.ce_ntp_auth: + key_id: 32 + authentication: enable + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "auth_type": "text", + "authentication": "enable", + "key_id": "32", + "auth_pwd": "1111", + "auth_mode": "md5", + "trusted_key": "enable", + "state": "present" + } +existing: + description: k/v pairs of existing ntp authentication + returned: always + type: dict + sample: { + "authentication": "off", + "authentication-keyid": [ + { + "auth_mode": "md5", + "key_id": "1", + "trusted_key": "disable" + } + ] + } +end_state: + description: k/v pairs of ntp authentication after module execution + returned: always + type: dict + sample: { + "authentication": "off", + "authentication-keyid": [ + { + "auth_mode": "md5", + "key_id": "1", + "trusted_key": "disable" + }, + { + "auth_mode": "md5", + "key_id": "32", + "trusted_key": "enable" + } + ] + } +state: + description: state as sent in from the playbook + returned: always + type: str + sample: "present" +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "ntp authentication-key 32 md5 1111", + "ntp trusted-key 32", + "ntp authentication enable" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible.module_utils.connection import exec_command + + +class NtpAuth(object): + """Manage ntp authentication""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # ntp_auth configuration info + self.key_id = self.module.params['key_id'] + self.password = self.module.params['auth_pwd'] or None + self.auth_mode = self.module.params['auth_mode'] or None + self.auth_type = self.module.params['auth_type'] + self.trusted_key = self.module.params['trusted_key'] + self.authentication = self.module.params['authentication'] or None + self.state = self.module.params['state'] + self.check_params() + + self.ntp_auth_conf = dict() + self.key_id_exist = False + self.cur_trusted_key = 'disable' + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + self.get_ntp_auth_exist_config() + + def check_params(self): + """Check all input params""" + + if not self.key_id.isdigit(): + self.module.fail_json( + msg='Error: key_id is not digit.') + + if (int(self.key_id) < 1) or (int(self.key_id) > 4294967295): + self.module.fail_json( + msg='Error: The length of key_id is between 1 and 4294967295.') + if self.state == "present" and not self.password: + self.module.fail_json( + msg='Error: The password cannot be empty.') + if self.state == "present" and self.password: + if (self.auth_type == 'encrypt') and\ + ((len(self.password) < 20) or (len(self.password) > 392)): + self.module.fail_json( + msg='Error: The length of encrypted password is between 20 and 392.') + elif (self.auth_type == 'text') and\ + ((len(self.password) < 1) or (len(self.password) > 255)): + self.module.fail_json( + msg='Error: The length of text password is between 1 and 255.') + + def init_module(self): + """Init module object""" + + required_if = [("state", "present", ("auth_pwd", "auth_mode"))] + self.module = AnsibleModule( + argument_spec=self.spec, + required_if=required_if, + supports_check_mode=True + ) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_ntp_auth_enable(self): + """Get ntp authentication enable state""" + + flags = list() + exp = "| exclude undo | include ntp authentication" + flags.append(exp) + config = self.get_config(flags) + auth_en = re.findall( + r'.*ntp\s*authentication\s*enable.*', config) + if auth_en: + self.ntp_auth_conf['authentication'] = 'enable' + else: + self.ntp_auth_conf['authentication'] = 'disable' + + def get_ntp_all_auth_keyid(self): + """Get all authentication keyid info""" + + ntp_auth_conf = list() + + flags = list() + exp = "| include authentication-keyid %s" % self.key_id + flags.append(exp) + config = self.get_config(flags) + ntp_config_list = config.split('\n') + if not ntp_config_list: + self.ntp_auth_conf["authentication-keyid"] = "None" + return ntp_auth_conf + + self.key_id_exist = True + cur_auth_mode = "" + cur_auth_pwd = "" + for ntp_config in ntp_config_list: + ntp_auth_mode = re.findall(r'.*authentication-mode(\s\S*)\s\S*\s(\S*)', ntp_config) + ntp_auth_trust = re.findall(r'.*trusted.*', ntp_config) + if ntp_auth_trust: + self.cur_trusted_key = 'enable' + if ntp_auth_mode: + cur_auth_mode = ntp_auth_mode[0][0].strip() + cur_auth_pwd = ntp_auth_mode[0][1].strip() + ntp_auth_conf.append(dict(key_id=self.key_id, + auth_mode=cur_auth_mode, + auth_pwd=cur_auth_pwd, + trusted_key=self.cur_trusted_key)) + self.ntp_auth_conf["authentication-keyid"] = ntp_auth_conf + + return ntp_auth_conf + + def get_ntp_auth_exist_config(self): + """Get ntp authentication existed configure""" + + self.get_ntp_auth_enable() + self.get_ntp_all_auth_keyid() + + def config_ntp_auth_keyid(self): + """Config ntp authentication keyid""" + + commands = list() + if self.auth_type == 'encrypt': + config_cli = "ntp authentication-keyid %s authentication-mode %s cipher %s" % ( + self.key_id, self.auth_mode, self.password) + else: + config_cli = "ntp authentication-keyid %s authentication-mode %s %s" % ( + self.key_id, self.auth_mode, self.password) + + commands.append(config_cli) + + if self.trusted_key != self.cur_trusted_key: + if self.trusted_key == 'enable': + config_cli_trust = "ntp trusted authentication-keyid %s" % (self.key_id) + commands.append(config_cli_trust) + else: + config_cli_trust = "undo ntp trusted authentication-keyid %s" % (self.key_id) + commands.append(config_cli_trust) + + self.cli_load_config(commands) + + def config_ntp_auth_enable(self): + """Config ntp authentication enable""" + + commands = list() + if self.ntp_auth_conf['authentication'] != self.authentication: + if self.authentication == 'enable': + config_cli = "ntp authentication enable" + else: + config_cli = "undo ntp authentication enable" + commands.append(config_cli) + + self.cli_load_config(commands) + + def undo_config_ntp_auth_keyid(self): + """Undo ntp authentication key-id""" + + commands = list() + config_cli = "undo ntp authentication-keyid %s" % self.key_id + commands.append(config_cli) + + self.cli_load_config(commands) + + def cli_load_config(self, commands): + """Load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def config_ntp_auth(self): + """Config ntp authentication""" + + if self.state == "present": + self.config_ntp_auth_keyid() + else: + if not self.key_id_exist: + self.module.fail_json( + msg='Error: The Authentication-keyid does not exist.') + self.undo_config_ntp_auth_keyid() + + if self.authentication: + self.config_ntp_auth_enable() + + self.changed = True + + def get_existing(self): + """Get existing info""" + + self.existing = copy.deepcopy(self.ntp_auth_conf) + + def get_proposed(self): + """Get proposed result""" + + auth_type = self.auth_type + trusted_key = self.trusted_key + if self.state == 'absent': + auth_type = None + trusted_key = None + self.proposed = dict(key_id=self.key_id, auth_pwd=self.password, + auth_mode=self.auth_mode, auth_type=auth_type, + trusted_key=trusted_key, authentication=self.authentication, + state=self.state) + + def get_update_cmd(self): + """Get updated commands""" + + cli_str = "" + if self.state == "present": + cli_str = "ntp authentication-keyid %s authentication-mode %s " % ( + self.key_id, self.auth_mode) + if self.auth_type == 'encrypt': + cli_str = "%s cipher %s" % (cli_str, self.password) + else: + cli_str = "%s %s" % (cli_str, self.password) + else: + cli_str = "undo ntp authentication-keyid %s" % self.key_id + + self.updates_cmd.append(cli_str) + + if self.authentication: + cli_str = "" + + if self.ntp_auth_conf['authentication'] != self.authentication: + if self.authentication == 'enable': + cli_str = "ntp authentication enable" + else: + cli_str = "undo ntp authentication enable" + + if cli_str != "": + self.updates_cmd.append(cli_str) + + cli_str = "" + if self.state == "present": + if self.trusted_key != self.cur_trusted_key: + if self.trusted_key == 'enable': + cli_str = "ntp trusted authentication-keyid %s" % self.key_id + else: + cli_str = "undo ntp trusted authentication-keyid %s" % self.key_id + else: + cli_str = "undo ntp trusted authentication-keyid %s" % self.key_id + + if cli_str != "": + self.updates_cmd.append(cli_str) + + def get_end_state(self): + """Get end state info""" + + self.ntp_auth_conf = dict() + self.get_ntp_auth_exist_config() + self.end_state = copy.deepcopy(self.ntp_auth_conf) + if self.end_state == self.existing: + self.changed = False + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Execute task""" + + self.get_existing() + self.get_proposed() + self.get_update_cmd() + + self.config_ntp_auth() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + key_id=dict(required=True, type='str'), + auth_pwd=dict(type='str', no_log=True), + auth_mode=dict(choices=['md5', 'hmac-sha256'], type='str'), + auth_type=dict(choices=['text', 'encrypt'], default='encrypt'), + trusted_key=dict(choices=['enable', 'disable'], default='disable'), + authentication=dict(choices=['enable', 'disable']), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + ntp_auth_obj = NtpAuth(argument_spec) + ntp_auth_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf.py new file mode 100644 index 00000000..6f7a6646 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf.py @@ -0,0 +1,968 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ospf +short_description: Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + process_id: + description: + - Specifies a process ID. + The value is an integer ranging from 1 to 4294967295. + required: true + area: + description: + - Specifies the area ID. The area with the area-id being 0 is a backbone area. + Valid values are a string, formatted as an IP address + (i.e. "0.0.0.0") or as an integer between 1 and 4294967295. + addr: + description: + - Specifies the address of the network segment where the interface resides. + The value is in dotted decimal notation. + mask: + description: + - IP network wildcard bits in decimal format between 0 and 32. + auth_mode: + description: + - Specifies the authentication type. + choices: ['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'] + auth_text_simple: + description: + - Specifies a password for simple authentication. + The value is a string of 1 to 8 characters. + auth_key_id: + description: + - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5. + Valid value is an integer is in the range from 1 to 255. + auth_text_md5: + description: + - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication. + The value is a string of 1 to 255 case-sensitive characters, spaces not supported. + nexthop_addr: + description: + - IPv4 address for configure next-hop address's weight. + Valid values are a string, formatted as an IP address. + nexthop_weight: + description: + - Indicates the weight of the next hop. + The smaller the value is, the higher the preference of the route is. + It is an integer that ranges from 1 to 254. + max_load_balance: + description: + - The maximum number of paths for forward packets over multiple paths. + Valid value is an integer in the range from 1 to 64. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Ospf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure ospf + community.network.ce_ospf: + process_id: 1 + area: 100 + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "100"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"process_id": "1", "areas": [], "nexthops":[], "max_load_balance": "32"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"process_id": "1", + "areas": [{"areaId": "0.0.0.100", "areaType": "Normal"}], + "nexthops":[], "max_load_balance": "32"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["ospf 1", "area 0.0.0.100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF = """ + + + + + + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_CREATE_PROCESS = """ + + + + + + %s + + + + + +""" + +CE_NC_DELETE_PROCESS = """ + + + + + + %s + + + + + +""" + +CE_NC_XML_BUILD_MERGE_PROCESS = """ + + + + + + %s + %s + + + + + +""" + +CE_NC_XML_BUILD_PROCESS = """ + + + + + + %s + %s + + + + + +""" + +CE_NC_XML_BUILD_MERGE_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_BUILD_DELETE_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_BUILD_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_SET_AUTH_MODE = """ + %s +""" +CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """ + %s +""" + +CE_NC_XML_SET_AUTH_MD5 = """ + %s + %s +""" + + +CE_NC_XML_MERGE_NETWORKS = """ + + + %s + %s + + +""" + +CE_NC_XML_DELETE_NETWORKS = """ + + + %s + %s + + +""" + +CE_NC_XML_SET_LB = """ + %s +""" + + +CE_NC_XML_BUILD_MERGE_TOPO = """ + + + base + %s + + + +""" + +CE_NC_XML_BUILD_TOPO = """ + + + base + %s + + + +""" + +CE_NC_XML_MERGE_NEXTHOP = """ + + + %s + %s + + +""" + +CE_NC_XML_DELETE_NEXTHOP = """ + + + %s + + +""" + + +class OSPF(object): + """ + Manages configuration of an ospf instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.process_id = self.module.params['process_id'] + self.area = self.module.params['area'] + self.addr = self.module.params['addr'] + self.mask = self.module.params['mask'] + self.auth_mode = self.module.params['auth_mode'] + self.auth_text_simple = self.module.params['auth_text_simple'] + self.auth_key_id = self.module.params['auth_key_id'] + self.auth_text_md5 = self.module.params['auth_text_md5'] + self.nexthop_addr = self.module.params['nexthop_addr'] + self.nexthop_weight = self.module.params['nexthop_weight'] + self.max_load_balance = self.module.params['max_load_balance'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + + required_together = [ + ("addr", "mask"), + ("auth_key_id", "auth_text_md5"), + ("nexthop_addr", "nexthop_weight") + ] + self.module = AnsibleModule( + argument_spec=self.spec, required_together=required_together, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_wildcard_mask(self): + """convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255""" + + mask_int = ["255"] * 4 + length = int(self.mask) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int(~(0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + def get_area_ip(self): + """convert integer to ip address""" + + if not self.area.isdigit(): + return self.area + + addr_int = ['0'] * 4 + addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF) + addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF) + addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF) + addr_int[3] = str(int(self.area) & 0xFF) + + return '.'.join(addr_int) + + def get_ospf_dict(self, process_id): + """ get one ospf attributes dict.""" + + ospf_info = dict() + conf_str = CE_NC_GET_OSPF % process_id + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return ospf_info + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get process base info + root = ElementTree.fromstring(xml_str) + ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite") + if ospfsite: + for site in ospfsite: + if site.tag in ["processId", "routerId", "vrfName"]: + ospf_info[site.tag] = site.text + + # get Topology info + topo = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology") + if topo: + for eles in topo: + if eles.tag in ["maxLoadBalancing"]: + ospf_info[eles.tag] = eles.text + + # get nexthop info + ospf_info["nexthops"] = list() + nexthops = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology/nexthopMTs/nexthopMT") + if nexthops: + for nexthop in nexthops: + nh_dict = dict() + for ele in nexthop: + if ele.tag in ["ipAddress", "weight"]: + nh_dict[ele.tag] = ele.text + ospf_info["nexthops"].append(nh_dict) + + # get areas info + ospf_info["areas"] = list() + areas = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area") + if areas: + for area in areas: + area_dict = dict() + for ele in area: + if ele.tag in ["areaId", "authTextSimple", "areaType", + "authenticationMode", "keyId", "authTextMd5"]: + area_dict[ele.tag] = ele.text + if ele.tag == "networks": + # get networks info + area_dict["networks"] = list() + for net in ele: + net_dict = dict() + for net_ele in net: + if net_ele.tag in ["ipAddress", "wildcardMask"]: + net_dict[net_ele.tag] = net_ele.text + area_dict["networks"].append(net_dict) + + ospf_info["areas"].append(area_dict) + return ospf_info + + def is_area_exist(self): + """is ospf area exist""" + if not self.ospf_info: + return False + for area in self.ospf_info["areas"]: + if area["areaId"] == self.get_area_ip(): + return True + + return False + + def is_network_exist(self): + """is ospf area network exist""" + if not self.ospf_info: + return False + + for area in self.ospf_info["areas"]: + if area["areaId"] == self.get_area_ip(): + if not area.get("networks"): + return False + for network in area.get("networks"): + if network["ipAddress"] == self.addr and network["wildcardMask"] == self.get_wildcard_mask(): + return True + return False + + def is_nexthop_exist(self): + """is ospf nexthop exist""" + + if not self.ospf_info: + return False + for nexthop in self.ospf_info["nexthops"]: + if nexthop["ipAddress"] == self.nexthop_addr: + return True + + return False + + def is_nexthop_change(self): + """is ospf nexthop change""" + if not self.ospf_info: + return True + + for nexthop in self.ospf_info["nexthops"]: + if nexthop["ipAddress"] == self.nexthop_addr: + if nexthop["weight"] == self.nexthop_weight: + return False + else: + return True + + return True + + def create_process(self): + """Create ospf process""" + + xml_area = "" + self.updates_cmd.append("ospf %s" % self.process_id) + xml_create = CE_NC_CREATE_PROCESS % self.process_id + set_nc_config(self.module, xml_create) + + # nexthop weight + xml_nh = "" + if self.nexthop_addr: + xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( + self.nexthop_addr, self.nexthop_weight) + self.updates_cmd.append("nexthop %s weight %s" % ( + self.nexthop_addr, self.nexthop_weight)) + + # max load balance + xml_lb = "" + if self.max_load_balance: + xml_lb = CE_NC_XML_SET_LB % self.max_load_balance + self.updates_cmd.append( + "maximum load-balancing %s" % self.max_load_balance) + + xml_topo = "" + if xml_lb or xml_nh: + xml_topo = CE_NC_XML_BUILD_TOPO % (xml_nh + xml_lb) + + if self.area: + self.updates_cmd.append("area %s" % self.get_area_ip()) + xml_auth = "" + xml_network = "" + + # networks + if self.addr and self.mask: + xml_network = CE_NC_XML_MERGE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + self.updates_cmd.append("network %s %s" % ( + self.addr, self.get_wildcard_mask())) + + # authentication mode + if self.auth_mode: + xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo authentication-mode") + else: + self.updates_cmd.append( + "authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and self.auth_text_md5: + xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + if xml_network or xml_auth or not self.is_area_exist(): + xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( + self.get_area_ip(), xml_network + xml_auth) + + xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( + self.process_id, xml_topo + xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CREATE_PROCESS") + self.changed = True + + def delete_process(self): + """Delete ospf process""" + + xml_str = CE_NC_DELETE_PROCESS % self.process_id + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_PROCESS") + self.updates_cmd.append("undo ospf %s" % self.process_id) + self.changed = True + + def merge_process(self): + """merge ospf process""" + + xml_area = "" + xml_str = "" + self.updates_cmd.append("ospf %s" % self.process_id) + + # nexthop weight + xml_nh = "" + if self.nexthop_addr and self.is_nexthop_change(): + xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( + self.nexthop_addr, self.nexthop_weight) + self.updates_cmd.append("nexthop %s weight %s" % ( + self.nexthop_addr, self.nexthop_weight)) + + # max load balance + xml_lb = "" + if self.max_load_balance and self.ospf_info.get("maxLoadBalancing") != self.max_load_balance: + xml_lb = CE_NC_XML_SET_LB % self.max_load_balance + self.updates_cmd.append( + "maximum load-balancing %s" % self.max_load_balance) + + xml_topo = "" + if xml_lb or xml_nh: + xml_topo = CE_NC_XML_BUILD_MERGE_TOPO % (xml_nh + xml_lb) + + if self.area: + self.updates_cmd.append("area %s" % self.get_area_ip()) + xml_network = "" + xml_auth = "" + if self.addr and self.mask: + if not self.is_network_exist(): + xml_network += CE_NC_XML_MERGE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + self.updates_cmd.append("network %s %s" % ( + self.addr, self.get_wildcard_mask())) + + # NOTE: for security, authentication config will always be update + if self.auth_mode: + xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo authentication-mode") + else: + self.updates_cmd.append( + "authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and self.auth_text_md5: + xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + if xml_network or xml_auth or not self.is_area_exist(): + xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( + self.get_area_ip(), xml_network + xml_auth) + elif self.is_area_exist(): + self.updates_cmd.pop() # remove command: area + else: + pass + + if xml_area or xml_topo: + xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( + self.process_id, xml_topo + xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_PROCESS") + self.changed = True + + def remove_area_network(self): + """remvoe ospf area network""" + + if not self.is_network_exist(): + return + + xml_network = CE_NC_XML_DELETE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + xml_area = CE_NC_XML_BUILD_AREA % (self.get_area_ip(), xml_network) + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_AREA_NETWORK") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + self.updates_cmd.append("undo network %s %s" % + (self.addr, self.get_wildcard_mask())) + self.changed = True + + def remove_area(self): + """remove ospf area""" + + if not self.is_area_exist(): + return + + xml_area = CE_NC_XML_BUILD_DELETE_AREA % (self.get_area_ip(), "") + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_AREA") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("undo area %s" % self.get_area_ip()) + self.changed = True + + def remove_nexthop(self): + """remove ospf nexthop weight""" + + if not self.is_nexthop_exist(): + return + + xml_nh = CE_NC_XML_DELETE_NEXTHOP % self.nexthop_addr + xml_topo = CE_NC_XML_BUILD_TOPO % xml_nh + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_topo) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_NEXTHOP_WEIGHT") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("undo nexthop %s" % self.nexthop_addr) + self.changed = True + + def is_valid_v4addr(self, addr): + """check is ipv4 addr is valid""" + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + def convert_ip_to_network(self): + """convert ip to subnet address""" + + ip_list = self.addr.split('.') + mask_list = self.get_wildcard_mask().split('.') + + for i in range(len(ip_list)): + ip_list[i] = str((int(ip_list[i]) & (~int(mask_list[i]))) & 0xff) + + self.addr = '.'.join(ip_list) + + def check_params(self): + """Check all input params""" + + # process_id check + if not self.process_id.isdigit(): + self.module.fail_json(msg="Error: process_id is not digit.") + if int(self.process_id) < 1 or int(self.process_id) > 4294967295: + self.module.fail_json( + msg="Error: process_id must be an integer between 1 and 4294967295.") + + if self.area: + # area check + if self.area.isdigit(): + if int(self.area) < 0 or int(self.area) > 4294967295: + self.module.fail_json( + msg="Error: area id (Integer) must be between 0 and 4294967295.") + + else: + if not self.is_valid_v4addr(self.area): + self.module.fail_json(msg="Error: area id is invalid.") + + # area network check + if self.addr: + if not self.is_valid_v4addr(self.addr): + self.module.fail_json( + msg="Error: network addr is invalid.") + if not self.mask.isdigit(): + self.module.fail_json( + msg="Error: network mask is not digit.") + if int(self.mask) < 0 or int(self.mask) > 32: + self.module.fail_json( + msg="Error: network mask is invalid.") + + # area authentication check + if self.state == "present" and self.auth_mode: + if self.auth_mode == "simple": + if self.auth_text_simple and len(self.auth_text_simple) > 8: + self.module.fail_json( + msg="Error: auth_text_simple is not in the range from 1 to 8.") + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id: + if not self.auth_key_id.isdigit(): + self.module.fail_json( + msg="Error: auth_key_id is not digit.") + if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255: + self.module.fail_json( + msg="Error: auth_key_id is not in the range from 1 to 255.") + if self.auth_text_md5 and len(self.auth_text_md5) > 255: + self.module.fail_json( + msg="Error: auth_text_md5 is not in the range from 1 to 255.") + + # process max load balance check + if self.state == "present" and self.max_load_balance: + if not self.max_load_balance.isdigit(): + self.module.fail_json( + msg="Error: max_load_balance is not digit.") + if int(self.max_load_balance) < 1 or int(self.max_load_balance) > 64: + self.module.fail_json( + msg="Error: max_load_balance is not in the range from 1 to 64.") + + # process nexthop weight check + if self.nexthop_addr: + if not self.is_valid_v4addr(self.nexthop_addr): + self.module.fail_json(msg="Error: nexthop_addr is invalid.") + if not self.nexthop_weight.isdigit(): + self.module.fail_json( + msg="Error: nexthop_weight is not digit.") + if int(self.nexthop_weight) < 1 or int(self.nexthop_weight) > 254: + self.module.fail_json( + msg="Error: nexthop_weight is not in the range from 1 to 254.") + + if self.addr: + self.convert_ip_to_network() + + def get_proposed(self): + """get proposed info""" + + self.proposed["process_id"] = self.process_id + self.proposed["area"] = self.area + if self.area: + self.proposed["addr"] = self.addr + self.proposed["mask"] = self.mask + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.auth_mode == "simple": + self.proposed["auth_text_simple"] = self.auth_text_simple + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + self.proposed["auth_key_id"] = self.auth_key_id + self.proposed["auth_text_md5"] = self.auth_text_md5 + + if self.nexthop_addr: + self.proposed["nexthop_addr"] = self.nexthop_addr + self.proposed["nexthop_weight"] = self.nexthop_weight + self.proposed["max_load_balance"] = self.max_load_balance + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.ospf_info: + return + + self.existing["process_id"] = self.process_id + self.existing["areas"] = self.ospf_info["areas"] + self.existing["nexthops"] = self.ospf_info["nexthops"] + self.existing["max_load_balance"] = self.ospf_info.get( + "maxLoadBalancing") + + def get_end_state(self): + """get end state info""" + + ospf_info = self.get_ospf_dict(self.process_id) + + if not ospf_info: + return + + self.end_state["process_id"] = self.process_id + self.end_state["areas"] = ospf_info["areas"] + self.end_state["nexthops"] = ospf_info["nexthops"] + self.end_state["max_load_balance"] = ospf_info.get("maxLoadBalancing") + + if self.end_state == self.existing: + if not self.auth_text_simple and not self.auth_text_md5: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.ospf_info = self.get_ospf_dict(self.process_id) + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.ospf_info: + # create ospf process + self.create_process() + else: + # merge ospf + self.merge_process() + else: + if self.ospf_info: + if self.area: + if self.addr: + # remove ospf area network + self.remove_area_network() + else: + # remove ospf area + self.remove_area() + if self.nexthop_addr: + # remove ospf nexthop weight + self.remove_nexthop() + + if not self.area and not self.nexthop_addr: + # remove ospf process + self.delete_process() + else: + self.module.fail_json(msg='Error: ospf process does not exist') + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + process_id=dict(required=True, type='str'), + area=dict(required=False, type='str'), + addr=dict(required=False, type='str'), + mask=dict(required=False, type='str'), + auth_mode=dict(required=False, + choices=['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'), + auth_text_simple=dict(required=False, type='str', no_log=True), + auth_key_id=dict(required=False, type='str'), + auth_text_md5=dict(required=False, type='str', no_log=True), + nexthop_addr=dict(required=False, type='str'), + nexthop_weight=dict(required=False, type='str'), + max_load_balance=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = OSPF(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf_vrf.py new file mode 100644 index 00000000..56c0e5ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_ospf_vrf.py @@ -0,0 +1,1619 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ospf_vrf +short_description: Manages configuration of an OSPF VPN instance on HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF VPN instance on HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + ospf: + description: + - The ID of the ospf process. + Valid values are an integer, 1 - 4294967295, the default value is 1. + required: true + route_id: + description: + - Specifies the ospf private route id,. + Valid values are a string, formatted as an IP address + (i.e. "10.1.1.1") the length is 0 - 20. + vrf: + description: + - Specifies the vpn instance which use ospf,length is 1 - 31. + Valid values are a string. + default: _public_ + description: + description: + - Specifies the description information of ospf process. + bandwidth: + description: + - Specifies the reference bandwidth used to assign ospf cost. + Valid values are an integer, in Mbps, 1 - 2147483648, the default value is 100. + lsaalflag: + description: + - Specifies the mode of timer to calculate interval of arrive LSA. + If set the parameter but not specifies value, the default will be used. + If true use general timer. + If false use intelligent timer. + type: bool + default: 'no' + lsaainterval: + description: + - Specifies the interval of arrive LSA when use the general timer. + Valid value is an integer, in millisecond, from 0 to 10000. + lsaamaxinterval: + description: + - Specifies the max interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 1000. + lsaastartinterval: + description: + - Specifies the start interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 500. + lsaaholdinterval: + description: + - Specifies the hold interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 500. + lsaointervalflag: + description: + - Specifies whether cancel the interval of LSA originate or not. + If set the parameter but noe specifies value, the default will be used. + true:cancel the interval of LSA originate, the interval is 0. + false:do not cancel the interval of LSA originate. + type: bool + default: 'no' + lsaointerval: + description: + - Specifies the interval of originate LSA . + Valid value is an integer, in second, from 0 to 10, the default value is 5. + lsaomaxinterval: + description: + - Specifies the max interval of originate LSA . + Valid value is an integer, in millisecond, from 1 to 10000, the default value is 5000. + lsaostartinterval: + description: + - Specifies the start interval of originate LSA . + Valid value is an integer, in millisecond, from 0 to 1000, the default value is 500. + lsaoholdinterval: + description: + - Specifies the hold interval of originate LSA . + Valid value is an integer, in millisecond, from 0 to 5000, the default value is 1000. + spfintervaltype: + description: + - Specifies the mode of timer which used to calculate SPF. + If set the parameter but noe specifies value, the default will be used. + If is intelligent-timer, then use intelligent timer. + If is timer, then use second level timer. + If is millisecond, then use millisecond level timer. + choices: ['intelligent-timer','timer','millisecond'] + default: intelligent-timer + spfinterval: + description: + - Specifies the interval to calculate SPF when use second level timer. + Valid value is an integer, in second, from 1 to 10. + spfintervalmi: + description: + - Specifies the interval to calculate SPF when use millisecond level timer. + Valid value is an integer, in millisecond, from 1 to 10000. + spfmaxinterval: + description: + - Specifies the max interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 20000, the default value is 5000. + spfstartinterval: + description: + - Specifies the start interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 1000, the default value is 50. + spfholdinterval: + description: + - Specifies the hold interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 5000, the default value is 200. + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present +''' + +EXAMPLES = ''' +- name: Ospf vrf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure ospf route id + community.network.ce_ospf_vrf: + ospf: 2 + route_id: 2.2.2.2 + lsaointervalflag: False + lsaointerval: 2 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "bandwidth": "100", + "description": null, + "lsaaholdinterval": "500", + "lsaainterval": null, + "lsaamaxinterval": "1000", + "lsaastartinterval": "500", + "lsaalflag": "False", + "lsaoholdinterval": "1000", + "lsaointerval": "2", + "lsaointervalflag": "False", + "lsaomaxinterval": "5000", + "lsaostartinterval": "500", + "process_id": "2", + "route_id": "2.2.2.2", + "spfholdinterval": "1000", + "spfinterval": null, + "spfintervalmi": null, + "spfintervaltype": "intelligent-timer", + "spfmaxinterval": "10000", + "spfstartinterval": "500", + "vrf": "_public_" + } +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: { + "bandwidthReference": "100", + "description": null, + "lsaArrivalFlag": "false", + "lsaArrivalHoldInterval": "500", + "lsaArrivalInterval": null, + "lsaArrivalMaxInterval": "1000", + "lsaArrivalStartInterval": "500", + "lsaOriginateHoldInterval": "1000", + "lsaOriginateInterval": "2", + "lsaOriginateIntervalFlag": "false", + "lsaOriginateMaxInterval": "5000", + "lsaOriginateStartInterval": "500", + "processId": "2", + "routerId": "2.2.2.2", + "spfScheduleHoldInterval": "1000", + "spfScheduleInterval": null, + "spfScheduleIntervalMillisecond": null, + "spfScheduleIntervalType": "intelligent-timer", + "spfScheduleMaxInterval": "10000", + "spfScheduleStartInterval": "500", + "vrfName": "_public_" + } +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: { + "bandwidthReference": "100", + "description": null, + "lsaArrivalFlag": "false", + "lsaArrivalHoldInterval": "500", + "lsaArrivalInterval": null, + "lsaArrivalMaxInterval": "1000", + "lsaArrivalStartInterval": "500", + "lsaOriginateHoldInterval": "1000", + "lsaOriginateInterval": "2", + "lsaOriginateIntervalFlag": "false", + "lsaOriginateMaxInterval": "5000", + "lsaOriginateStartInterval": "500", + "processId": "2", + "routerId": "2.2.2.2", + "spfScheduleHoldInterval": "1000", + "spfScheduleInterval": null, + "spfScheduleIntervalMillisecond": null, + "spfScheduleIntervalType": "intelligent-timer", + "spfScheduleMaxInterval": "10000", + "spfScheduleStartInterval": "500", + "vrfName": "_public_" + } +updates: + description: commands sent to the device + returned: always + type: list + sample: ["ospf 2"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: False +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF_VRF = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_CREATE_OSPF_VRF = """ + + + + + %s +%s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + + + + +""" +CE_NC_CREATE_ROUTE_ID = """ + %s +""" + +CE_NC_DELETE_OSPF = """ + + + + + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +class OspfVrf(object): + """ + Manages configuration of an ospf instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.ospf = self.module.params['ospf'] + self.route_id = self.module.params['route_id'] + self.vrf = self.module.params['vrf'] + self.description = self.module.params['description'] + self.bandwidth = self.module.params['bandwidth'] + self.lsaalflag = self.module.params['lsaalflag'] + self.lsaainterval = self.module.params['lsaainterval'] + self.lsaamaxinterval = self.module.params['lsaamaxinterval'] + self.lsaastartinterval = self.module.params['lsaastartinterval'] + self.lsaaholdinterval = self.module.params['lsaaholdinterval'] + self.lsaointervalflag = self.module.params['lsaointervalflag'] + self.lsaointerval = self.module.params['lsaointerval'] + self.lsaomaxinterval = self.module.params['lsaomaxinterval'] + self.lsaostartinterval = self.module.params['lsaostartinterval'] + self.lsaoholdinterval = self.module.params['lsaoholdinterval'] + self.spfintervaltype = self.module.params['spfintervaltype'] + self.spfinterval = self.module.params['spfinterval'] + self.spfintervalmi = self.module.params['spfintervalmi'] + self.spfmaxinterval = self.module.params['spfmaxinterval'] + self.spfstartinterval = self.module.params['spfstartinterval'] + self.spfholdinterval = self.module.params['spfholdinterval'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.lsa_arrival_changed = False + self.lsa_originate_changed = False + self.spf_changed = False + self.route_id_changed = False + self.bandwidth_changed = False + self.description_changed = False + self.vrf_changed = False + + def init_module(self): + """" init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def is_valid_ospf_process_id(self): + """check whether the input ospf process id is valid""" + + if not self.ospf.isdigit(): + return False + if int(self.ospf) > 4294967295 or int(self.ospf) < 1: + return False + return True + + def is_valid_ospf_route_id(self): + """check is ipv4 addr is valid""" + + if self.route_id.find('.') != -1: + addr_list = self.route_id.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + def is_valid_vrf_name(self): + """check whether the input ospf vrf name is valid""" + + if len(self.vrf) > 31 or len(self.vrf) < 1: + return False + if self.vrf.find('?') != -1: + return False + if self.vrf.find(' ') != -1: + return False + return True + + def is_valid_description(self): + """check whether the input ospf description is valid""" + + if len(self.description) > 80 or len(self.description) < 1: + return False + if self.description.find('?') != -1: + return False + return True + + def is_valid_bandwidth(self): + """check whether the input ospf bandwidth reference is valid""" + + if not self.bandwidth.isdigit(): + return False + if int(self.bandwidth) > 2147483648 or int(self.bandwidth) < 1: + return False + return True + + def is_valid_lsa_arrival_interval(self): + """check whether the input ospf lsa arrival interval is valid""" + + if self.lsaainterval is None: + return False + if not self.lsaainterval.isdigit(): + return False + if int(self.lsaainterval) > 10000 or int(self.lsaainterval) < 0: + return False + return True + + def isvalidlsamaxarrivalinterval(self): + """check whether the input ospf lsa max arrival interval is valid""" + + if not self.lsaamaxinterval.isdigit(): + return False + if int(self.lsaamaxinterval) > 10000 or int(self.lsaamaxinterval) < 1: + return False + return True + + def isvalidlsastartarrivalinterval(self): + """check whether the input ospf lsa start arrival interval is valid""" + + if not self.lsaastartinterval.isdigit(): + return False + if int(self.lsaastartinterval) > 1000 or int(self.lsaastartinterval) < 0: + return False + return True + + def isvalidlsaholdarrivalinterval(self): + """check whether the input ospf lsa hold arrival interval is valid""" + + if not self.lsaaholdinterval.isdigit(): + return False + if int(self.lsaaholdinterval) > 5000 or int(self.lsaaholdinterval) < 0: + return False + return True + + def is_valid_lsa_originate_interval(self): + """check whether the input ospf lsa originate interval is valid""" + + if not self.lsaointerval.isdigit(): + return False + if int(self.lsaointerval) > 10 or int(self.lsaointerval) < 0: + return False + return True + + def isvalidlsaoriginatemaxinterval(self): + """check whether the input ospf lsa originate max interval is valid""" + + if not self.lsaomaxinterval.isdigit(): + return False + if int(self.lsaomaxinterval) > 10000 or int(self.lsaomaxinterval) < 1: + return False + return True + + def isvalidlsaostartinterval(self): + """check whether the input ospf lsa originate start interval is valid""" + + if not self.lsaostartinterval.isdigit(): + return False + if int(self.lsaostartinterval) > 1000 or int(self.lsaostartinterval) < 0: + return False + return True + + def isvalidlsaoholdinterval(self): + """check whether the input ospf lsa originate hold interval is valid""" + + if not self.lsaoholdinterval.isdigit(): + return False + if int(self.lsaoholdinterval) > 5000 or int(self.lsaoholdinterval) < 1: + return False + return True + + def is_valid_spf_interval(self): + """check whether the input ospf spf interval is valid""" + + if not self.spfinterval.isdigit(): + return False + if int(self.spfinterval) > 10 or int(self.spfinterval) < 1: + return False + return True + + def is_valid_spf_milli_interval(self): + """check whether the input ospf spf millisecond level interval is valid""" + + if not self.spfintervalmi.isdigit(): + return False + if int(self.spfintervalmi) > 10000 or int(self.spfintervalmi) < 1: + return False + return True + + def is_valid_spf_max_interval(self): + """check whether the input ospf spf intelligent timer max interval is valid""" + + if not self.spfmaxinterval.isdigit(): + return False + if int(self.spfmaxinterval) > 20000 or int(self.spfmaxinterval) < 1: + return False + return True + + def is_valid_spf_start_interval(self): + """check whether the input ospf spf intelligent timer start interval is valid""" + + if not self.spfstartinterval.isdigit(): + return False + if int(self.spfstartinterval) > 1000 or int(self.spfstartinterval) < 1: + return False + return True + + def is_valid_spf_hold_interval(self): + """check whether the input ospf spf intelligent timer hold interval is valid""" + + if not self.spfholdinterval.isdigit(): + return False + if int(self.spfholdinterval) > 5000 or int(self.spfholdinterval) < 1: + return False + return True + + def is_route_id_exist(self): + """is route id exist""" + + if not self.ospf_info: + return False + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] != self.ospf: + continue + if ospf_site["routerId"] == self.route_id: + return True + else: + continue + return False + + def get_exist_ospf_id(self): + """get exist ospf process id""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["processId"] + else: + continue + return None + + def get_exist_route(self): + """get exist route id""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["routerId"] + else: + continue + return None + + def get_exist_vrf(self): + """get exist vrf""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["vrfName"] + else: + continue + return None + + def get_exist_bandwidth(self): + """get exist bandwidth""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["bandwidthReference"] + else: + continue + return None + + def get_exist_lsa_a_interval(self): + """get exist lsa arrival interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalInterval"] + else: + continue + return None + + def get_exist_lsa_a_interval_flag(self): + """get exist lsa arrival interval flag""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalFlag"] + else: + continue + return None + + def get_exist_lsa_a_max_interval(self): + """get exist lsa arrival max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalMaxInterval"] + else: + continue + return None + + def get_exist_lsa_a_start_interval(self): + """get exist lsa arrival start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalStartInterval"] + else: + continue + return None + + def get_exist_lsa_a_hold_interval(self): + """get exist lsa arrival hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalHoldInterval"] + else: + continue + return None + + def getexistlsaointerval(self): + """get exist lsa originate interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateInterval"] + else: + continue + return None + + def getexistlsaointerval_flag(self): + """get exist lsa originate interval flag""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateIntervalFlag"] + else: + continue + return None + + def getexistlsaomaxinterval(self): + """get exist lsa originate max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateMaxInterval"] + else: + continue + return None + + def getexistlsaostartinterval(self): + """get exist lsa originate start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateStartInterval"] + else: + continue + return None + + def getexistlsaoholdinterval(self): + """get exist lsa originate hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateHoldInterval"] + else: + continue + return None + + def get_exist_spf_interval(self): + """get exist spf second level timer interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleInterval"] + else: + continue + return None + + def get_exist_spf_milli_interval(self): + """get exist spf millisecond level timer interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleIntervalMillisecond"] + else: + continue + return None + + def get_exist_spf_max_interval(self): + """get exist spf max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleMaxInterval"] + else: + continue + return None + + def get_exist_spf_start_interval(self): + """get exist spf start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleStartInterval"] + else: + continue + return None + + def get_exist_spf_hold_interval(self): + """get exist spf hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleHoldInterval"] + else: + continue + return None + + def get_exist_spf_interval_type(self): + """get exist spf hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleIntervalType"] + else: + continue + return None + + def is_ospf_exist(self): + """is ospf exist""" + + if not self.ospf_info: + return False + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return True + else: + continue + return False + + def get_exist_description(self): + """is description exist""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["description"] + else: + continue + return None + + def check_params(self): + """Check all input params""" + + if self.ospf == '': + self.module.fail_json( + msg='Error: The ospf process id should not be null.') + if self.ospf: + if not self.is_valid_ospf_process_id(): + self.module.fail_json( + msg='Error: The ospf process id should between 1 - 4294967295.') + if self.route_id == '': + self.module.fail_json( + msg='Error: The ospf route id length should not be null.') + if self.route_id: + if not self.is_valid_ospf_route_id(): + self.module.fail_json( + msg='Error: The ospf route id length should between 0 - 20,i.e.10.1.1.1.') + if self.vrf == '': + self.module.fail_json( + msg='Error: The ospf vpn instance length should not be null.') + if self.vrf: + if not self.is_valid_vrf_name(): + self.module.fail_json( + msg='Error: The ospf vpn instance length should between 0 - 31,but can not contain " " or "?".') + if self.description == '': + self.module.fail_json( + msg='Error: The ospf description should not be null.') + if self.description: + if not self.is_valid_description(): + self.module.fail_json( + msg='Error: The ospf description length should between 1 - 80,but can not contain "?".') + if self.bandwidth == '': + self.module.fail_json( + msg='Error: The ospf bandwidth reference should not be null.') + if self.bandwidth: + if not self.is_valid_bandwidth(): + self.module.fail_json( + msg='Error: The ospf bandwidth reference should between 1 - 2147483648.') + if self.lsaalflag is True: + if not self.is_valid_lsa_arrival_interval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival interval should between 0 - 10000.') + if self.lsaamaxinterval or self.lsaastartinterval or self.lsaaholdinterval: + self.module.fail_json( + msg='Error: Non-Intelligent Timer and Intelligent Timer Interval of ' + 'lsa-arrival-interval can not configured at the same time.') + if self.lsaalflag is False: + if self.lsaainterval: + self.module.fail_json( + msg='Error: The parameter of lsa arrival interval command is invalid, ' + 'because LSA arrival interval can not be config when the LSA arrival flag is not set.') + if self.lsaamaxinterval == '' or self.lsaastartinterval == '' or self.lsaaholdinterval == '': + self.module.fail_json( + msg='Error: The ospf lsa arrival intervals should not be null.') + if self.lsaamaxinterval: + if not self.isvalidlsamaxarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival max interval should between 1 - 10000.') + if self.lsaastartinterval: + if not self.isvalidlsastartarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival start interval should between 1 - 1000.') + if self.lsaaholdinterval: + if not self.isvalidlsaholdarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival hold interval should between 1 - 5000.') + if self.lsaointervalflag is True: + if self.lsaointerval or self.lsaomaxinterval \ + or self.lsaostartinterval or self.lsaoholdinterval: + self.module.fail_json( + msg='Error: Interval for other-type and Instantly Flag ' + 'of lsa-originate-interval can not configured at the same time.') + if self.lsaointerval == '': + self.module.fail_json( + msg='Error: The ospf lsa originate interval should should not be null.') + if self.lsaointerval: + if not self.is_valid_lsa_originate_interval(): + self.module.fail_json( + msg='Error: The ospf lsa originate interval should between 0 - 10 s.') + if self.lsaomaxinterval == '' or self.lsaostartinterval == '' or self.lsaoholdinterval == '': + self.module.fail_json( + msg='Error: The ospf lsa originate intelligent intervals should should not be null.') + if self.lsaomaxinterval: + if not self.isvalidlsaoriginatemaxinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate max interval should between 1 - 10000 ms.') + if self.lsaostartinterval: + if not self.isvalidlsaostartinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate start interval should between 0 - 1000 ms.') + if self.lsaoholdinterval: + if not self.isvalidlsaoholdinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate hold interval should between 1 - 5000 ms.') + if self.spfintervaltype == '': + self.module.fail_json( + msg='Error: The ospf spf interval type should should not be null.') + if self.spfintervaltype == 'intelligent-timer': + if self.spfinterval is not None or self.spfintervalmi is not None: + self.module.fail_json( + msg='Error: Interval second and interval millisecond ' + 'of spf-schedule-interval can not configured if use intelligent timer.') + if self.spfmaxinterval == '' or self.spfstartinterval == '' or self.spfholdinterval == '': + self.module.fail_json( + msg='Error: The ospf spf intelligent timer intervals should should not be null.') + if self.spfmaxinterval and not self.is_valid_spf_max_interval(): + self.module.fail_json( + msg='Error: The ospf spf max interval of intelligent timer should between 1 - 20000 ms.') + if self.spfstartinterval and not self.is_valid_spf_start_interval(): + self.module.fail_json( + msg='Error: The ospf spf start interval of intelligent timer should between 1 - 1000 ms.') + if self.spfholdinterval and not self.is_valid_spf_hold_interval(): + self.module.fail_json( + msg='Error: The ospf spf hold interval of intelligent timer should between 1 - 5000 ms.') + if self.spfintervaltype == 'timer': + if self.spfintervalmi is not None: + self.module.fail_json( + msg='Error: Interval second and interval millisecond ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfmaxinterval or self.spfstartinterval or self.spfholdinterval: + self.module.fail_json( + msg='Error: Interval second and interval intelligent ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfinterval == '' or self.spfinterval is None: + self.module.fail_json( + msg='Error: The ospf spf timer intervals should should not be null.') + if not self.is_valid_spf_interval(): + self.module.fail_json( + msg='Error: Interval second should between 1 - 10 s.') + if self.spfintervaltype == 'millisecond': + if self.spfinterval is not None: + self.module.fail_json( + msg='Error: Interval millisecond and interval second ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfmaxinterval or self.spfstartinterval or self.spfholdinterval: + self.module.fail_json( + msg='Error: Interval millisecond and interval intelligent ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfintervalmi == '' or self.spfintervalmi is None: + self.module.fail_json( + msg='Error: The ospf spf millisecond intervals should should not be null.') + if not self.is_valid_spf_milli_interval(): + self.module.fail_json( + msg='Error: Interval millisecond should between 1 - 10000 ms.') + + def get_ospf_info(self): + """ get the detail information of ospf """ + + self.ospf_info["ospfsite"] = list() + + getxmlstr = CE_NC_GET_OSPF_VRF + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # get the vpn address family and RD text + ospf_sites = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite") + if ospf_sites: + for ospf_site in ospf_sites: + ospf_ele_info = dict() + for ospf_site_ele in ospf_site: + if ospf_site_ele.tag in ["processId", "routerId", "vrfName", "bandwidthReference", + "description", "lsaArrivalInterval", "lsaArrivalMaxInterval", + "lsaArrivalStartInterval", "lsaArrivalHoldInterval", "lsaArrivalFlag", + "lsaOriginateInterval", "lsaOriginateMaxInterval", + "lsaOriginateStartInterval", "lsaOriginateHoldInterval", + "lsaOriginateIntervalFlag", "spfScheduleInterval", + "spfScheduleIntervalMillisecond", "spfScheduleMaxInterval", + "spfScheduleStartInterval", "spfScheduleHoldInterval", + "spfScheduleIntervalType"]: + ospf_ele_info[ + ospf_site_ele.tag] = ospf_site_ele.text + if ospf_ele_info["processId"] == self.ospf: + self.ospf_info["ospfsite"].append(ospf_ele_info) + + def get_proposed(self): + """get proposed info""" + + self.proposed["process_id"] = self.ospf + self.proposed["route_id"] = self.route_id + self.proposed["vrf"] = self.vrf + self.proposed["description"] = self.description + self.proposed["bandwidth"] = self.bandwidth + self.proposed["lsaalflag"] = self.lsaalflag + self.proposed["lsaainterval"] = self.lsaainterval + self.proposed["lsaamaxinterval"] = self.lsaamaxinterval + self.proposed["lsaastartinterval"] = self.lsaastartinterval + self.proposed["lsaaholdinterval"] = self.lsaaholdinterval + self.proposed["lsaointervalflag"] = self.lsaointervalflag + self.proposed["lsaointerval"] = self.lsaointerval + self.proposed["lsaomaxinterval"] = self.lsaomaxinterval + self.proposed["lsaostartinterval"] = self.lsaostartinterval + self.proposed["lsaoholdinterval"] = self.lsaoholdinterval + self.proposed["spfintervaltype"] = self.spfintervaltype + self.proposed["spfinterval"] = self.spfinterval + self.proposed["spfintervalmi"] = self.spfintervalmi + self.proposed["spfmaxinterval"] = self.spfmaxinterval + self.proposed["spfstartinterval"] = self.spfstartinterval + self.proposed["spfholdinterval"] = self.spfholdinterval + + def operate_ospf_info(self): + """operate ospf info""" + + config_route_id_xml = '' + vrf = self.get_exist_vrf() + if vrf is None: + vrf = '_public_' + description = self.get_exist_description() + if description is None: + description = '' + bandwidth_reference = self.get_exist_bandwidth() + if bandwidth_reference is None: + bandwidth_reference = '100' + lsa_in_interval = self.get_exist_lsa_a_interval() + if lsa_in_interval is None: + lsa_in_interval = '' + lsa_arrival_max_interval = self.get_exist_lsa_a_max_interval() + if lsa_arrival_max_interval is None: + lsa_arrival_max_interval = '1000' + lsa_arrival_start_interval = self.get_exist_lsa_a_start_interval() + if lsa_arrival_start_interval is None: + lsa_arrival_start_interval = '500' + lsa_arrival_hold_interval = self.get_exist_lsa_a_hold_interval() + if lsa_arrival_hold_interval is None: + lsa_arrival_hold_interval = '500' + lsa_originate_interval = self.getexistlsaointerval() + if lsa_originate_interval is None: + lsa_originate_interval = '5' + lsa_originate_max_interval = self.getexistlsaomaxinterval() + if lsa_originate_max_interval is None: + lsa_originate_max_interval = '5000' + lsa_originate_start_interval = self.getexistlsaostartinterval() + if lsa_originate_start_interval is None: + lsa_originate_start_interval = '500' + lsa_originate_hold_interval = self.getexistlsaoholdinterval() + if lsa_originate_hold_interval is None: + lsa_originate_hold_interval = '1000' + spf_interval = self.get_exist_spf_interval() + if spf_interval is None: + spf_interval = '' + spf_interval_milli = self.get_exist_spf_milli_interval() + if spf_interval_milli is None: + spf_interval_milli = '' + spf_max_interval = self.get_exist_spf_max_interval() + if spf_max_interval is None: + spf_max_interval = '5000' + spf_start_interval = self.get_exist_spf_start_interval() + if spf_start_interval is None: + spf_start_interval = '50' + spf_hold_interval = self.get_exist_spf_hold_interval() + if spf_hold_interval is None: + spf_hold_interval = '200' + + if self.route_id: + if self.state == 'present': + if self.route_id != self.get_exist_route(): + self.route_id_changed = True + config_route_id_xml = CE_NC_CREATE_ROUTE_ID % self.route_id + else: + if self.route_id != self.get_exist_route(): + self.module.fail_json( + msg='Error: The route id %s is not exist.' % self.route_id) + self.route_id_changed = True + configxmlstr = CE_NC_DELETE_OSPF % ( + self.ospf, self.get_exist_route(), self.get_exist_vrf()) + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + self.changed = True + return + if self.vrf != '_public_': + if self.state == 'present': + if self.vrf != self.get_exist_vrf(): + self.vrf_changed = True + vrf = self.vrf + else: + if self.vrf != self.get_exist_vrf(): + self.module.fail_json( + msg='Error: The vrf %s is not exist.' % self.vrf) + self.vrf_changed = True + configxmlstr = CE_NC_DELETE_OSPF % ( + self.ospf, self.get_exist_route(), self.get_exist_vrf()) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + self.changed = True + return + if self.bandwidth: + if self.state == 'present': + if self.bandwidth != self.get_exist_bandwidth(): + self.bandwidth_changed = True + bandwidth_reference = self.bandwidth + else: + if self.bandwidth != self.get_exist_bandwidth(): + self.module.fail_json( + msg='Error: The bandwidth %s is not exist.' % self.bandwidth) + if self.get_exist_bandwidth() != '100': + self.bandwidth_changed = True + bandwidth_reference = '100' + if self.description: + if self.state == 'present': + if self.description != self.get_exist_description(): + self.description_changed = True + description = self.description + else: + if self.description != self.get_exist_description(): + self.module.fail_json( + msg='Error: The description %s is not exist.' % self.description) + self.description_changed = True + description = '' + + if self.lsaalflag is False: + lsa_in_interval = '' + if self.state == 'present': + if self.lsaamaxinterval: + if self.lsaamaxinterval != self.get_exist_lsa_a_max_interval(): + self.lsa_arrival_changed = True + lsa_arrival_max_interval = self.lsaamaxinterval + if self.lsaastartinterval: + if self.lsaastartinterval != self.get_exist_lsa_a_start_interval(): + self.lsa_arrival_changed = True + lsa_arrival_start_interval = self.lsaastartinterval + if self.lsaaholdinterval: + if self.lsaaholdinterval != self.get_exist_lsa_a_hold_interval(): + self.lsa_arrival_changed = True + lsa_arrival_hold_interval = self.lsaaholdinterval + else: + if self.lsaamaxinterval: + if self.lsaamaxinterval != self.get_exist_lsa_a_max_interval(): + self.module.fail_json( + msg='Error: The lsaamaxinterval %s is not exist.' % self.lsaamaxinterval) + if self.get_exist_lsa_a_max_interval() != '1000': + lsa_arrival_max_interval = '1000' + self.lsa_arrival_changed = True + if self.lsaastartinterval: + if self.lsaastartinterval != self.get_exist_lsa_a_start_interval(): + self.module.fail_json( + msg='Error: The lsaastartinterval %s is not exist.' % self.lsaastartinterval) + if self.get_exist_lsa_a_start_interval() != '500': + lsa_arrival_start_interval = '500' + self.lsa_arrival_changed = True + if self.lsaaholdinterval: + if self.lsaaholdinterval != self.get_exist_lsa_a_hold_interval(): + self.module.fail_json( + msg='Error: The lsaaholdinterval %s is not exist.' % self.lsaaholdinterval) + if self.get_exist_lsa_a_hold_interval() != '500': + lsa_arrival_hold_interval = '500' + self.lsa_arrival_changed = True + else: + if self.state == 'present': + lsaalflag = "false" + if self.lsaalflag is True: + lsaalflag = "true" + if lsaalflag != self.get_exist_lsa_a_interval_flag(): + self.lsa_arrival_changed = True + if self.lsaainterval is None: + self.module.fail_json( + msg='Error: The lsaainterval is not supplied.') + else: + lsa_in_interval = self.lsaainterval + else: + if self.lsaainterval: + if self.lsaainterval != self.get_exist_lsa_a_interval(): + self.lsa_arrival_changed = True + lsa_in_interval = self.lsaainterval + else: + if self.lsaainterval: + if self.lsaainterval != self.get_exist_lsa_a_interval(): + self.module.fail_json( + msg='Error: The lsaainterval %s is not exist.' % self.lsaainterval) + self.lsaalflag = False + lsa_in_interval = '' + self.lsa_arrival_changed = True + + if self.lsaointervalflag is False: + if self.state == 'present': + if self.lsaomaxinterval: + if self.lsaomaxinterval != self.getexistlsaomaxinterval(): + self.lsa_originate_changed = True + lsa_originate_max_interval = self.lsaomaxinterval + if self.lsaostartinterval: + if self.lsaostartinterval != self.getexistlsaostartinterval(): + self.lsa_originate_changed = True + lsa_originate_start_interval = self.lsaostartinterval + if self.lsaoholdinterval: + if self.lsaoholdinterval != self.getexistlsaoholdinterval(): + self.lsa_originate_changed = True + lsa_originate_hold_interval = self.lsaoholdinterval + if self.lsaointerval: + if self.lsaointerval != self.getexistlsaointerval(): + self.lsa_originate_changed = True + lsa_originate_interval = self.lsaointerval + else: + if self.lsaomaxinterval: + if self.lsaomaxinterval != self.getexistlsaomaxinterval(): + self.module.fail_json( + msg='Error: The lsaomaxinterval %s is not exist.' % self.lsaomaxinterval) + if self.getexistlsaomaxinterval() != '5000': + lsa_originate_max_interval = '5000' + self.lsa_originate_changed = True + if self.lsaostartinterval: + if self.lsaostartinterval != self.getexistlsaostartinterval(): + self.module.fail_json( + msg='Error: The lsaostartinterval %s is not exist.' % self.lsaostartinterval) + if self.getexistlsaostartinterval() != '500': + lsa_originate_start_interval = '500' + self.lsa_originate_changed = True + if self.lsaoholdinterval: + if self.lsaoholdinterval != self.getexistlsaoholdinterval(): + self.module.fail_json( + msg='Error: The lsaoholdinterval %s is not exist.' % self.lsaoholdinterval) + if self.getexistlsaoholdinterval() != '1000': + lsa_originate_hold_interval = '1000' + self.lsa_originate_changed = True + if self.lsaointerval: + if self.lsaointerval != self.getexistlsaointerval(): + self.module.fail_json( + msg='Error: The lsaointerval %s is not exist.' % self.lsaointerval) + if self.getexistlsaointerval() != '5': + lsa_originate_interval = '5' + self.lsa_originate_changed = True + else: + if self.state == 'present': + if self.getexistlsaointerval_flag() != 'true': + self.lsa_originate_changed = True + lsa_originate_interval = '5' + lsa_originate_max_interval = '5000' + lsa_originate_start_interval = '500' + lsa_originate_hold_interval = '1000' + else: + if self.getexistlsaointerval_flag() == 'true': + self.lsaointervalflag = False + self.lsa_originate_changed = True + if self.spfintervaltype != self.get_exist_spf_interval_type(): + self.spf_changed = True + if self.spfintervaltype == 'timer': + if self.spfinterval: + if self.state == 'present': + if self.spfinterval != self.get_exist_spf_interval(): + self.spf_changed = True + spf_interval = self.spfinterval + spf_interval_milli = '' + else: + if self.spfinterval != self.get_exist_spf_interval(): + self.module.fail_json( + msg='Error: The spfinterval %s is not exist.' % self.spfinterval) + self.spfintervaltype = 'intelligent-timer' + spf_interval = '' + self.spf_changed = True + if self.spfintervaltype == 'millisecond': + if self.spfintervalmi: + if self.state == 'present': + if self.spfintervalmi != self.get_exist_spf_milli_interval(): + self.spf_changed = True + spf_interval_milli = self.spfintervalmi + spf_interval = '' + else: + if self.spfintervalmi != self.get_exist_spf_milli_interval(): + self.module.fail_json( + msg='Error: The spfintervalmi %s is not exist.' % self.spfintervalmi) + self.spfintervaltype = 'intelligent-timer' + spf_interval_milli = '' + self.spf_changed = True + if self.spfintervaltype == 'intelligent-timer': + spf_interval = '' + spf_interval_milli = '' + if self.spfmaxinterval: + if self.state == 'present': + if self.spfmaxinterval != self.get_exist_spf_max_interval(): + self.spf_changed = True + spf_max_interval = self.spfmaxinterval + else: + if self.spfmaxinterval != self.get_exist_spf_max_interval(): + self.module.fail_json( + msg='Error: The spfmaxinterval %s is not exist.' % self.spfmaxinterval) + if self.get_exist_spf_max_interval() != '5000': + self.spf_changed = True + spf_max_interval = '5000' + if self.spfstartinterval: + if self.state == 'present': + if self.spfstartinterval != self.get_exist_spf_start_interval(): + self.spf_changed = True + spf_start_interval = self.spfstartinterval + else: + if self.spfstartinterval != self.get_exist_spf_start_interval(): + self.module.fail_json( + msg='Error: The spfstartinterval %s is not exist.' % self.spfstartinterval) + if self.get_exist_spf_start_interval() != '50': + self.spf_changed = True + spf_start_interval = '50' + if self.spfholdinterval: + if self.state == 'present': + if self.spfholdinterval != self.get_exist_spf_hold_interval(): + self.spf_changed = True + spf_hold_interval = self.spfholdinterval + else: + if self.spfholdinterval != self.get_exist_spf_hold_interval(): + self.module.fail_json( + msg='Error: The spfholdinterval %s is not exist.' % self.spfholdinterval) + if self.get_exist_spf_hold_interval() != '200': + self.spf_changed = True + spf_hold_interval = '200' + + if not self.description_changed and not self.vrf_changed and not self.lsa_arrival_changed \ + and not self.lsa_originate_changed and not self.spf_changed \ + and not self.route_id_changed and not self.bandwidth_changed: + self.changed = False + return + else: + self.changed = True + lsaointervalflag = "false" + lsaalflag = "false" + if self.lsaointervalflag is True: + lsaointervalflag = "true" + if self.lsaalflag is True: + lsaalflag = "true" + configxmlstr = CE_NC_CREATE_OSPF_VRF % ( + self.ospf, config_route_id_xml, vrf, + description, bandwidth_reference, lsaalflag, + lsa_in_interval, lsa_arrival_max_interval, lsa_arrival_start_interval, + lsa_arrival_hold_interval, lsaointervalflag, lsa_originate_interval, + lsa_originate_max_interval, lsa_originate_start_interval, lsa_originate_hold_interval, + self.spfintervaltype, spf_interval, spf_interval_milli, + spf_max_interval, spf_start_interval, spf_hold_interval) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + + def get_existing(self): + """get existing info""" + + self.get_ospf_info() + self.existing['ospf_info'] = self.ospf_info["ospfsite"] + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + + if self.state == 'present': + if self.vrf_changed: + if self.vrf != '_public_': + if self.route_id_changed: + self.updates_cmd.append( + 'ospf %s router-id %s vpn-instance %s' % (self.ospf, self.route_id, self.vrf)) + else: + self.updates_cmd.append( + 'ospf %s vpn-instance %s ' % (self.ospf, self.vrf)) + else: + if self.route_id_changed: + self.updates_cmd.append( + 'ospf %s router-id %s' % (self.ospf, self.route_id)) + else: + if self.route_id_changed: + if self.vrf != '_public_': + self.updates_cmd.append( + 'ospf %s router-id %s vpn-instance %s' % (self.ospf, self.route_id, self.get_exist_vrf())) + else: + self.updates_cmd.append( + 'ospf %s router-id %s' % (self.ospf, self.route_id)) + else: + if self.route_id_changed: + self.updates_cmd.append('undo ospf %s' % self.ospf) + return + + self.updates_cmd.append('ospf %s' % self.ospf) + + if self.description: + if self.state == 'present': + if self.description_changed: + self.updates_cmd.append( + 'description %s' % self.description) + else: + if self.description_changed: + self.updates_cmd.append('undo description') + if self.bandwidth_changed: + if self.state == 'present': + if self.get_exist_bandwidth() != '100': + self.updates_cmd.append( + 'bandwidth-reference %s' % (self.get_exist_bandwidth())) + else: + self.updates_cmd.append('undo bandwidth-reference') + if self.lsaalflag is True: + if self.lsa_arrival_changed: + if self.state == 'present': + self.updates_cmd.append( + 'lsa-arrival-interval %s' % (self.get_exist_lsa_a_interval())) + else: + self.updates_cmd.append( + 'undo lsa-arrival-interval') + + if self.lsaalflag is False: + if self.lsa_arrival_changed: + if self.state == 'present': + if self.get_exist_lsa_a_max_interval() != '1000' \ + or self.get_exist_lsa_a_start_interval() != '500'\ + or self.get_exist_lsa_a_hold_interval() != '500': + self.updates_cmd.append('lsa-arrival-interval intelligent-timer %s %s %s' + % (self.get_exist_lsa_a_max_interval(), + self.get_exist_lsa_a_start_interval(), + self.get_exist_lsa_a_hold_interval())) + else: + if self.get_exist_lsa_a_max_interval() == '1000' \ + and self.get_exist_lsa_a_start_interval() == '500'\ + and self.get_exist_lsa_a_hold_interval() == '500': + self.updates_cmd.append( + 'undo lsa-arrival-interval') + if self.lsaointervalflag is False: + if self.lsa_originate_changed: + if self.state == 'present': + if self.getexistlsaointerval() != '5' \ + or self.getexistlsaomaxinterval() != '5000' \ + or self.getexistlsaostartinterval() != '500' \ + or self.getexistlsaoholdinterval() != '1000': + self.updates_cmd.append('lsa-originate-interval other-type %s intelligent-timer %s %s %s' + % (self.getexistlsaointerval(), + self.getexistlsaomaxinterval(), + self.getexistlsaostartinterval(), + self.getexistlsaoholdinterval())) + else: + self.updates_cmd.append( + 'undo lsa-originate-interval') + if self.lsaointervalflag is True: + if self.lsa_originate_changed: + if self.state == 'present': + self.updates_cmd.append('lsa-originate-interval 0 ') + else: + self.updates_cmd.append( + 'undo lsa-originate-interval') + if self.spfintervaltype == 'millisecond': + if self.spf_changed: + if self.state == 'present': + self.updates_cmd.append( + 'spf-schedule-interval millisecond %s' % self.get_exist_spf_milli_interval()) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + if self.spfintervaltype == 'timer': + if self.spf_changed: + if self.state == 'present': + self.updates_cmd.append( + 'spf-schedule-interval %s' % self.get_exist_spf_interval()) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + if self.spfintervaltype == 'intelligent-timer': + if self.spf_changed: + if self.state == 'present': + if self.get_exist_spf_max_interval() != '5000' \ + or self.get_exist_spf_start_interval() != '50' \ + or self.get_exist_spf_hold_interval() != '200': + self.updates_cmd.append('spf-schedule-interval intelligent-timer %s %s %s' + % (self.get_exist_spf_max_interval(), + self.get_exist_spf_start_interval(), + self.get_exist_spf_hold_interval())) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + + def get_end_state(self): + """get end state info""" + + self.get_ospf_info() + self.end_state['ospf_info'] = self.ospf_info["ospfsite"] + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_ospf_info() + self.get_end_state() + self.set_update_cmd() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + ospf=dict(required=True, type='str'), + route_id=dict(required=False, type='str'), + vrf=dict(required=False, type='str', default='_public_'), + description=dict(required=False, type='str'), + bandwidth=dict(required=False, type='str'), + lsaalflag=dict(type='bool', default=False), + lsaainterval=dict(required=False, type='str'), + lsaamaxinterval=dict(required=False, type='str'), + lsaastartinterval=dict(required=False, type='str'), + lsaaholdinterval=dict(required=False, type='str'), + lsaointervalflag=dict(type='bool', default=False), + lsaointerval=dict(required=False, type='str'), + lsaomaxinterval=dict(required=False, type='str'), + lsaostartinterval=dict(required=False, type='str'), + lsaoholdinterval=dict(required=False, type='str'), + spfintervaltype=dict(required=False, default='intelligent-timer', + choices=['intelligent-timer', 'timer', 'millisecond']), + spfinterval=dict(required=False, type='str'), + spfintervalmi=dict(required=False, type='str'), + spfmaxinterval=dict(required=False, type='str'), + spfstartinterval=dict(required=False, type='str'), + spfholdinterval=dict(required=False, type='str'), + state=dict(required=False, choices=['present', 'absent'], default='present'), + ) + + argument_spec.update(ce_argument_spec) + module = OspfVrf(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_reboot.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_reboot.py new file mode 100644 index 00000000..0213a072 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_reboot.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_reboot +short_description: Reboot a HUAWEI CloudEngine switches. +description: + - Reboot a HUAWEI CloudEngine switches. +author: Gong Jianjun (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +requirements: ["ncclient"] +options: + confirm: + description: + - Safeguard boolean. Set to true if you're sure you want to reboot. + type: bool + required: true + save_config: + description: + - Flag indicating whether to save the configuration. + required: false + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Reboot module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Reboot the device + community.network.ce_reboot: + confirm: true + save_config: true + provider: "{{ cli }}" +''' + +RETURN = ''' +rebooted: + description: Whether the device was instructed to reboot. + returned: success + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import execute_nc_action, ce_argument_spec + +try: + from ncclient.operations.errors import TimeoutExpiredError + HAS_NCCLIENT = True +except ImportError: + HAS_NCCLIENT = False + +CE_NC_XML_EXECUTE_REBOOT = """ + + + + %s + + + +""" + + +class Reboot(object): + """ Reboot a network device """ + + def __init__(self, **kwargs): + """ __init___ """ + + self.network_module = None + self.netconf = None + self.init_network_module(**kwargs) + + self.confirm = self.network_module.params['confirm'] + self.save_config = self.network_module.params['save_config'] + + def init_network_module(self, **kwargs): + """ init network module """ + + self.network_module = AnsibleModule(**kwargs) + + def netconf_set_action(self, xml_str): + """ netconf execute action """ + + try: + execute_nc_action(self.network_module, xml_str) + except TimeoutExpiredError: + pass + + def work(self): + """ start to work """ + + if not self.confirm: + self.network_module.fail_json( + msg='Error: Confirm must be set to true for this module to work.') + + xml_str = CE_NC_XML_EXECUTE_REBOOT % str(self.save_config).lower() + self.netconf_set_action(xml_str) + + +def main(): + """ main """ + + argument_spec = dict( + confirm=dict(required=True, type='bool'), + save_config=dict(default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = Reboot(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_NCCLIENT: + module.network_module.fail_json(msg='Error: The ncclient library is required.') + + changed = False + rebooted = False + + module.work() + + changed = True + rebooted = True + + results = dict() + results['changed'] = changed + results['rebooted'] = rebooted + + module.network_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_rollback.py new file mode 100644 index 00000000..75d82a3e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_rollback.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_rollback +short_description: Set a checkpoint or rollback to a checkpoint on HUAWEI CloudEngine switches. +description: + - This module offers the ability to set a configuration checkpoint + file or rollback to a configuration checkpoint file on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + commit_id: + description: + - Specifies the label of the configuration rollback point to which system configurations are + expected to roll back. + The value is an integer that the system generates automatically. + label: + description: + - Specifies a user label for a configuration rollback point. + The value is a string of 1 to 256 case-sensitive ASCII characters, spaces not supported. + The value must start with a letter and cannot be presented in a single hyphen (-). + filename: + description: + - Specifies a configuration file for configuration rollback. + The value is a string of 5 to 64 case-sensitive characters in the format of *.zip, *.cfg, or *.dat, + spaces not supported. + last: + description: + - Specifies the number of configuration rollback points. + The value is an integer that ranges from 1 to 80. + oldest: + description: + - Specifies the number of configuration rollback points. + The value is an integer that ranges from 1 to 80. + action: + description: + - The operation of configuration rollback. + required: true + choices: ['rollback','clear','set','display','commit'] +''' +EXAMPLES = ''' +- name: Rollback module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + +- name: Ensure commit_id is exist, and specifies the label of the configuration rollback point to + which system configurations are expected to roll back. + community.network.ce_rollback: + commit_id: 1000000748 + action: rollback + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: sometimes + type: dict + sample: {"commit_id": "1000000748", "action": "rollback"} +existing: + description: k/v pairs of existing rollback + returned: sometimes + type: dict + sample: {"commitId": "1000000748", "userLabel": "abc"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["rollback configuration to file a.cfg", + "set configuration commit 1000000783 label ddd", + "clear configuration commit 1000000783 label", + "display configuration commit list"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {"commitId": "1000000748", "userLabel": "abc"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, exec_command, run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList + + +class RollBack(object): + """ + Manages rolls back the system from the current configuration state to a historical configuration state. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + self.commands = list() + # module input info + self.commit_id = self.module.params['commit_id'] + self.label = self.module.params['label'] + self.filename = self.module.params['filename'] + self.last = self.module.params['last'] + self.oldest = self.module.params['oldest'] + self.action = self.module.params['action'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # configuration rollback points info + self.rollback_info = None + self.init_module() + + def init_module(self): + """ init module """ + + required_if = [('action', 'set', ['commit_id', 'label']), ('action', 'commit', ['label'])] + mutually_exclusive = None + required_one_of = None + if self.action == "rollback": + required_one_of = [['commit_id', 'label', 'filename', 'last']] + elif self.action == "clear": + required_one_of = [['commit_id', 'oldest']] + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True, required_if=required_if, mutually_exclusive=mutually_exclusive, required_one_of=required_one_of) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + self.commands.append("return") + self.commands.append("mmi-mode enable") + + if self.action == "commit": + self.commands.append("sys") + + self.commands.append(command) + self.updates_cmd.append(command) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + run_commands(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_rollback_dict(self): + """ get rollback attributes dict.""" + + rollback_info = dict() + rollback_info["RollBackInfos"] = list() + + flags = list() + exp = "commit list" + flags.append(exp) + cfg_info = self.get_config(flags) + if not cfg_info: + return rollback_info + + cfg_line = cfg_info.split("\n") + for cfg in cfg_line: + if re.findall(r'^\d', cfg): + pre_rollback_info = cfg.split() + rollback_info["RollBackInfos"].append(dict(commitId=pre_rollback_info[1], userLabel=pre_rollback_info[2])) + + return rollback_info + + def get_filename_type(self, filename): + """Gets the type of filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Configuration file name include spaces.') + + iftype = None + + if filename.endswith('.cfg'): + iftype = 'cfg' + elif filename.endswith('.zip'): + iftype = 'zip' + elif filename.endswith('.dat'): + iftype = 'dat' + else: + return None + return iftype.lower() + + def set_config(self): + + if self.action == "rollback": + if self.commit_id: + cmd = "rollback configuration to commit-id %s" % self.commit_id + self.cli_add_command(cmd) + if self.label: + cmd = "rollback configuration to label %s" % self.label + self.cli_add_command(cmd) + if self.filename: + cmd = "rollback configuration to file %s" % self.filename + self.cli_add_command(cmd) + if self.last: + cmd = "rollback configuration last %s" % self.last + self.cli_add_command(cmd) + elif self.action == "set": + if self.commit_id and self.label: + cmd = "set configuration commit %s label %s" % (self.commit_id, self.label) + self.cli_add_command(cmd) + elif self.action == "clear": + if self.commit_id: + cmd = "clear configuration commit %s label" % self.commit_id + self.cli_add_command(cmd) + if self.oldest: + cmd = "clear configuration commit oldest %s" % self.oldest + self.cli_add_command(cmd) + elif self.action == "commit": + if self.label: + cmd = "commit label %s" % self.label + self.cli_add_command(cmd) + + elif self.action == "display": + self.rollback_info = self.get_rollback_dict() + if self.commands: + self.commands.append('return') + self.commands.append('undo mmi-mode enable') + self.cli_load_config(self.commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + # commit_id check + rollback_info = self.rollback_info["RollBackInfos"] + if self.commit_id: + if not self.commit_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of commit_id is invalid.') + + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + info_bool = True + if not info_bool: + self.module.fail_json( + msg='Error: The parameter of commit_id is not exist.') + + if self.action == "clear": + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + if info.get("userLabel") == "-": + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: This commit_id does not have a label.') + + # filename check + if self.filename: + if not self.get_filename_type(self.filename): + self.module.fail_json( + msg='Error: Invalid file name or file name extension ( *.cfg, *.zip, *.dat ).') + # last check + if self.last: + if not self.last.isdigit(): + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not digit.') + if int(self.last) <= 0 or int(self.last) > 80: + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not in the range from 1 to 80.') + + # oldest check + if self.oldest: + if not self.oldest.isdigit(): + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not digit.') + if int(self.oldest) <= 0 or int(self.oldest) > 80: + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not in the range from 1 to 80.') + + # label check + if self.label: + if self.label[0].isdigit(): + self.module.fail_json( + msg='Error: Commit label which should not start with a number.') + if len(self.label.replace(' ', '')) == 1: + if self.label == '-': + self.module.fail_json( + msg='Error: Commit label which should not be "-"') + if len(self.label.replace(' ', '')) < 1 or len(self.label) > 256: + self.module.fail_json( + msg='Error: Label of configuration checkpoints is a string of 1 to 256 characters.') + + if self.action == "rollback": + info_bool = False + for info in rollback_info: + if info.get("userLabel") == self.label: + info_bool = True + if not info_bool: + self.module.fail_json( + msg='Error: The parameter of userLabel is not exist.') + + if self.action == "commit": + info_bool = False + for info in rollback_info: + if info.get("userLabel") == self.label: + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: The parameter of userLabel is existing.') + + if self.action == "set": + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + if info.get("userLabel") != "-": + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: The userLabel of this commitid is present and can be reset after deletion.') + + def get_proposed(self): + """get proposed info""" + + if self.commit_id: + self.proposed["commit_id"] = self.commit_id + if self.label: + self.proposed["label"] = self.label + if self.filename: + self.proposed["filename"] = self.filename + if self.last: + self.proposed["last"] = self.last + if self.oldest: + self.proposed["oldest"] = self.oldest + + def get_existing(self): + """get existing info""" + if not self.rollback_info: + self.existing["RollBackInfos"] = None + else: + self.existing["RollBackInfos"] = self.rollback_info["RollBackInfos"] + + def get_end_state(self): + """get end state info""" + + rollback_info = self.get_rollback_dict() + if not rollback_info: + self.end_state["RollBackInfos"] = None + else: + self.end_state["RollBackInfos"] = rollback_info["RollBackInfos"] + + def work(self): + """worker""" + + self.rollback_info = self.get_rollback_dict() + self.check_params() + self.get_proposed() + + self.set_config() + + self.get_existing() + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + commit_id=dict(required=False), + label=dict(required=False, type='str'), + filename=dict(required=False, type='str'), + last=dict(required=False, type='str'), + oldest=dict(required=False, type='str'), + action=dict(required=False, type='str', choices=[ + 'rollback', 'clear', 'set', 'commit', 'display']), + ) + argument_spec.update(ce_argument_spec) + module = RollBack(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_sflow.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_sflow.py new file mode 100644 index 00000000..166ac0aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_sflow.py @@ -0,0 +1,1169 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_sflow +short_description: Manages sFlow configuration on HUAWEI CloudEngine switches. +description: + - Configure Sampled Flow (sFlow) to monitor traffic on an interface in real time, + detect abnormal traffic, and locate the source of attack traffic, + ensuring stable running of the network. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + agent_ip: + description: + - Specifies the IPv4/IPv6 address of an sFlow agent. + source_ip: + description: + - Specifies the source IPv4/IPv6 address of sFlow packets. + collector_id: + description: + - Specifies the ID of an sFlow collector. This ID is used when you specify + the collector in subsequent sFlow configuration. + choices: ['1', '2'] + collector_ip: + description: + - Specifies the IPv4/IPv6 address of the sFlow collector. + collector_ip_vpn: + description: + - Specifies the name of a VPN instance. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + collector_datagram_size: + description: + - Specifies the maximum length of sFlow packets sent from an sFlow agent to an sFlow collector. + The value is an integer, in bytes. It ranges from 1024 to 8100. The default value is 1400. + collector_udp_port: + description: + - Specifies the UDP destination port number of sFlow packets. + The value is an integer that ranges from 1 to 65535. The default value is 6343. + collector_meth: + description: + - Configures the device to send sFlow packets through service interfaces, + enhancing the sFlow packet forwarding capability. + The enhanced parameter is optional. No matter whether you configure the enhanced mode, + the switch determines to send sFlow packets through service cards or management port + based on the routing information on the collector. + When the value is meth, the device forwards sFlow packets at the control plane. + When the value is enhanced, the device forwards sFlow packets at the forwarding plane to + enhance the sFlow packet forwarding capacity. + choices: ['meth', 'enhanced'] + collector_description: + description: + - Specifies the description of an sFlow collector. + The value is a string of 1 to 255 case-sensitive characters without spaces. + sflow_interface: + description: + - Full name of interface for Flow Sampling or Counter. + It must be a physical interface, Eth-Trunk, or Layer 2 subinterface. + sample_collector: + description: + - Indicates the ID list of the collector. + sample_rate: + description: + - Specifies the flow sampling rate in the format 1/rate. + The value is an integer and ranges from 1 to 4294967295. The default value is 8192. + sample_length: + description: + - Specifies the maximum length of sampled packets. + The value is an integer and ranges from 18 to 512, in bytes. The default value is 128. + sample_direction: + description: + - Enables flow sampling in the inbound or outbound direction. + choices: ['inbound', 'outbound', 'both'] + counter_collector: + description: + - Indicates the ID list of the counter collector. + counter_interval: + description: + - Indicates the counter sampling interval. + The value is an integer that ranges from 10 to 4294967295, in seconds. The default value is 20. + export_route: + description: + - Configures the sFlow packets sent by the switch not to carry routing information. + choices: ['enable', 'disable'] + rate_limit: + description: + - Specifies the rate of sFlow packets sent from a card to the control plane. + The value is an integer that ranges from 100 to 1500, in pps. + type: str + version_added: '0.2.0' + rate_limit_slot: + description: + - Specifies the slot where the rate of output sFlow packets is limited. + If this parameter is not specified, the rate of sFlow packets sent from + all cards to the control plane is limited. + The value is an integer or a string of characters. + type: str + version_added: '0.2.0' + forward_enp_slot: + description: + - Enable the Embedded Network Processor (ENP) chip function. + The switch uses the ENP chip to perform sFlow sampling, + and the maximum sFlow sampling interval is 65535. + If you set the sampling interval to be larger than 65535, + the switch automatically restores it to 65535. + The value is an integer or 'all'. + type: str + version_added: '0.2.0' + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +--- + +- name: Sflow module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Configuring sFlow Agent + community.network.ce_sflow: + agent_ip: 6.6.6.6 + provider: '{{ cli }}' + + - name: Configuring sFlow Collector + community.network.ce_sflow: + collector_id: 1 + collector_ip: 7.7.7.7 + collector_ip_vpn: vpn1 + collector_description: Collector1 + provider: '{{ cli }}' + + - name: Configure flow sampling. + community.network.ce_sflow: + sflow_interface: 10GE2/0/2 + sample_collector: 1 + sample_direction: inbound + provider: '{{ cli }}' + + - name: Configure counter sampling. + community.network.ce_sflow: + sflow_interface: 10GE2/0/2 + counter_collector: 1 + counter_interval: 1000 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"agent_ip": "6.6.6.6", "state": "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"agent": {}} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"agent": {"family": "ipv4", "ipv4Addr": "1.2.3.4", "ipv6Addr": null}} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["sflow agent ip 6.6.6.6"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +CE_NC_GET_SFLOW = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %s + + + + + + + + + %s + + + + + + + + + + + +""" + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def get_ip_version(address): + """get ip version fast""" + + if not address: + return None + + if address.count(':') >= 2 and address.count(":") <= 7: + return "ipv6" + elif address.count('.') == 3: + return "ipv4" + else: + return None + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Sflow(object): + """Manages sFlow""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.agent_ip = self.module.params['agent_ip'] + self.agent_version = None + self.source_ip = self.module.params['source_ip'] + self.source_version = None + self.export_route = self.module.params['export_route'] + self.rate_limit = self.module.params['rate_limit'] + self.rate_limit_slot = self.module.params['rate_limit_slot'] + self.forward_enp_slot = self.module.params['forward_enp_slot'] + self.collector_id = self.module.params['collector_id'] + self.collector_ip = self.module.params['collector_ip'] + self.collector_version = None + self.collector_ip_vpn = self.module.params['collector_ip_vpn'] + self.collector_datagram_size = self.module.params['collector_datagram_size'] + self.collector_udp_port = self.module.params['collector_udp_port'] + self.collector_meth = self.module.params['collector_meth'] + self.collector_description = self.module.params['collector_description'] + self.sflow_interface = self.module.params['sflow_interface'] + self.sample_collector = self.module.params['sample_collector'] or list() + self.sample_rate = self.module.params['sample_rate'] + self.sample_length = self.module.params['sample_length'] + self.sample_direction = self.module.params['sample_direction'] + self.counter_collector = self.module.params['counter_collector'] or list() + self.counter_interval = self.module.params['counter_interval'] + self.state = self.module.params['state'] + + # state + self.config = "" # current config + self.sflow_dict = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [("collector_id", "collector_ip")] + self.module = AnsibleModule( + argument_spec=self.spec, required_together=required_together, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def netconf_set_config(self, xml_str, xml_name): + """netconf set config""" + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_sflow_dict(self): + """ sflow config dict""" + + sflow_dict = dict(source=list(), agent=dict(), collector=list(), + sampling=dict(), counter=dict(), export=dict()) + conf_str = CE_NC_GET_SFLOW % ( + self.sflow_interface, self.sflow_interface) + + if not self.collector_meth: + conf_str = conf_str.replace("", "") + + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return sflow_dict + + xml_str = rcv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get source info + srcs = root.findall("sflow/sources/source") + if srcs: + for src in srcs: + attrs = dict() + for attr in src: + if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]: + attrs[attr.tag] = attr.text + sflow_dict["source"].append(attrs) + + # get agent info + agent = root.find("sflow/agents/agent") + if agent: + for attr in agent: + if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]: + sflow_dict["agent"][attr.tag] = attr.text + + # get collector info + collectors = root.findall("sflow/collectors/collector") + if collectors: + for collector in collectors: + attrs = dict() + for attr in collector: + if attr.tag in ["collectorID", "family", "ipv4Addr", "ipv6Addr", + "vrfName", "datagramSize", "port", "description", "meth"]: + attrs[attr.tag] = attr.text + sflow_dict["collector"].append(attrs) + + # get sampling info + sample = root.find("sflow/samplings/sampling") + if sample: + for attr in sample: + if attr.tag in ["ifName", "collectorID", "direction", "length", "rate"]: + sflow_dict["sampling"][attr.tag] = attr.text + + # get counter info + counter = root.find("sflow/counters/counter") + if counter: + for attr in counter: + if attr.tag in ["ifName", "collectorID", "interval"]: + sflow_dict["counter"][attr.tag] = attr.text + + # get export info + export = root.find("sflow/exports/export") + if export: + for attr in export: + if attr.tag == "ExportRoute": + sflow_dict["export"][attr.tag] = attr.text + + return sflow_dict + + def config_agent(self): + """configures sFlow agent""" + + xml_str = '' + if not self.agent_ip: + return xml_str + + self.agent_version = get_ip_version(self.agent_ip) + if not self.agent_version: + self.module.fail_json(msg="Error: agent_ip is invalid.") + + if self.state == "present": + if self.agent_ip != self.sflow_dict["agent"].get("ipv4Addr") \ + and self.agent_ip != self.sflow_dict["agent"].get("ipv6Addr"): + xml_str += '' + xml_str += '%s' % self.agent_version + if self.agent_version == "ipv4": + xml_str += '%s' % self.agent_ip + self.updates_cmd.append("sflow agent ip %s" % self.agent_ip) + else: + xml_str += '%s' % self.agent_ip + self.updates_cmd.append("sflow agent ipv6 %s" % self.agent_ip) + xml_str += '' + + else: + if self.agent_ip == self.sflow_dict["agent"].get("ipv4Addr") \ + or self.agent_ip == self.sflow_dict["agent"].get("ipv6Addr"): + xml_str += '' + self.updates_cmd.append("undo sflow agent") + + return xml_str + + def config_source(self): + """configures the source IP address for sFlow packets""" + + xml_str = '' + if not self.source_ip: + return xml_str + + self.source_version = get_ip_version(self.source_ip) + if not self.source_version: + self.module.fail_json(msg="Error: source_ip is invalid.") + + src_dict = dict() + for src in self.sflow_dict["source"]: + if src.get("family") == self.source_version: + src_dict = src + break + + if self.state == "present": + if self.source_ip != src_dict.get("ipv4Addr") \ + and self.source_ip != src_dict.get("ipv6Addr"): + xml_str += '' + xml_str += '%s' % self.source_version + if self.source_version == "ipv4": + xml_str += '%s' % self.source_ip + self.updates_cmd.append("sflow source ip %s" % self.source_ip) + else: + xml_str += '%s' % self.source_ip + self.updates_cmd.append( + "sflow source ipv6 %s" % self.source_ip) + xml_str += '' + else: + if self.source_ip == src_dict.get("ipv4Addr"): + xml_str += 'ipv4' + self.updates_cmd.append("undo sflow source ip %s" % self.source_ip) + elif self.source_ip == src_dict.get("ipv6Addr"): + xml_str += 'ipv6' + self.updates_cmd.append("undo sflow source ipv6 %s" % self.source_ip) + + return xml_str + + def config_collector(self): + """creates an sFlow collector and sets or modifies optional parameters for the sFlow collector""" + + xml_str = '' + if not self.collector_id: + return xml_str + + if self.state == "present" and not self.collector_ip: + return xml_str + + if self.collector_ip: + self.collector_version = get_ip_version(self.collector_ip) + if not self.collector_version: + self.module.fail_json(msg="Error: collector_ip is invalid.") + + # get collector dict + exist_dict = dict() + for collector in self.sflow_dict["collector"]: + if collector.get("collectorID") == self.collector_id: + exist_dict = collector + break + + change = False + if self.state == "present": + if not exist_dict: + change = True + elif self.collector_version != exist_dict.get("family"): + change = True + elif self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"): + change = True + elif self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"): + change = True + elif self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"): + change = True + elif not self.collector_ip_vpn and exist_dict.get("vrfName") != "_public_": + change = True + elif self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"): + change = True + elif not self.collector_udp_port and exist_dict.get("port") != "6343": + change = True + elif self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"): + change = True + elif not self.collector_datagram_size and exist_dict.get("datagramSize") != "1400": + change = True + elif self.collector_meth and self.collector_meth != exist_dict.get("meth"): + change = True + elif not self.collector_meth and exist_dict.get("meth") and exist_dict.get("meth") != "meth": + change = True + elif self.collector_description and self.collector_description != exist_dict.get("description"): + change = True + elif not self.collector_description and exist_dict.get("description"): + change = True + else: + pass + else: # absent + # collector not exist + if not exist_dict: + return xml_str + if self.collector_version and self.collector_version != exist_dict.get("family"): + return xml_str + if self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"): + return xml_str + if self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"): + return xml_str + if self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"): + return xml_str + if self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"): + return xml_str + if self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"): + return xml_str + if self.collector_meth and self.collector_meth != exist_dict.get("meth"): + return xml_str + if self.collector_description and self.collector_description != exist_dict.get("description"): + return xml_str + change = True + + if not change: + return xml_str + + # update or delete + if self.state == "absent": + xml_str += '%s' % self.collector_id + self.updates_cmd.append("undo collector %s" % self.collector_id) + else: + xml_str += '%s' % self.collector_id + cmd = "sflow collector %s" % self.collector_id + xml_str += '%s' % self.collector_version + if self.collector_version == "ipv4": + cmd += " ip %s" % self.collector_ip + xml_str += '%s' % self.collector_ip + else: + cmd += " ipv6 %s" % self.collector_ip + xml_str += '%s' % self.collector_ip + if self.collector_ip_vpn: + cmd += " vpn-instance %s" % self.collector_ip_vpn + xml_str += '%s' % self.collector_ip_vpn + if self.collector_datagram_size: + cmd += " length %s" % self.collector_datagram_size + xml_str += '%s' % self.collector_datagram_size + if self.collector_udp_port: + cmd += " udp-port %s" % self.collector_udp_port + xml_str += '%s' % self.collector_udp_port + if self.collector_description: + cmd += " description %s" % self.collector_description + xml_str += '%s' % self.collector_description + else: + xml_str += '' + if self.collector_meth: + if self.collector_meth == "enhanced": + cmd += " enhanced" + xml_str += '%s' % self.collector_meth + self.updates_cmd.append(cmd) + + xml_str += "" + + return xml_str + + def config_sampling(self): + """configure sflow sampling on an interface""" + + xml_str = '' + if not self.sflow_interface: + return xml_str + + if not self.sflow_dict["sampling"] and self.state == "absent": + return xml_str + + self.updates_cmd.append("interface %s" % self.sflow_interface) + if self.state == "present": + xml_str += '%s' % self.sflow_interface + else: + xml_str += '%s' % self.sflow_interface + + # sample_collector + if self.sample_collector: + if self.sflow_dict["sampling"].get("collectorID") \ + and self.sflow_dict["sampling"].get("collectorID") != "invalid": + existing = self.sflow_dict["sampling"].get("collectorID").split(',') + else: + existing = list() + + if self.state == "present": + diff = list(set(self.sample_collector) - set(existing)) + if diff: + self.updates_cmd.append( + "sflow sampling collector %s" % ' '.join(diff)) + new_set = list(self.sample_collector + existing) + xml_str += '%s' % ','.join(list(set(new_set))) + else: + same = list(set(self.sample_collector) & set(existing)) + if same: + self.updates_cmd.append( + "undo sflow sampling collector %s" % ' '.join(same)) + xml_str += '%s' % ','.join(list(set(same))) + + # sample_rate + if self.sample_rate: + exist = bool(self.sample_rate == self.sflow_dict["sampling"].get("rate")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow sampling rate %s" % self.sample_rate) + xml_str += '%s' % self.sample_rate + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow sampling rate %s" % self.sample_rate) + xml_str += '%s' % self.sample_rate + + # sample_length + if self.sample_length: + exist = bool(self.sample_length == self.sflow_dict["sampling"].get("length")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow sampling length %s" % self.sample_length) + xml_str += '%s' % self.sample_length + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow sampling length %s" % self.sample_length) + xml_str += '%s' % self.sample_length + + # sample_direction + if self.sample_direction: + direction = list() + if self.sample_direction == "both": + direction = ["inbound", "outbound"] + else: + direction.append(self.sample_direction) + existing = list() + if self.sflow_dict["sampling"].get("direction"): + if self.sflow_dict["sampling"].get("direction") == "both": + existing = ["inbound", "outbound"] + else: + existing.append( + self.sflow_dict["sampling"].get("direction")) + + if self.state == "present": + diff = list(set(direction) - set(existing)) + if diff: + new_set = list(set(direction + existing)) + self.updates_cmd.append( + "sflow sampling %s" % ' '.join(diff)) + if len(new_set) > 1: + new_dir = "both" + else: + new_dir = new_set[0] + xml_str += '%s' % new_dir + else: + same = list(set(existing) & set(direction)) + if same: + self.updates_cmd.append("undo sflow sampling %s" % ' '.join(same)) + if len(same) > 1: + del_dir = "both" + else: + del_dir = same[0] + xml_str += '%s' % del_dir + + if xml_str.endswith(""): + self.updates_cmd.pop() + return "" + + xml_str += '' + + return xml_str + + def config_counter(self): + """configures sflow counter on an interface""" + + xml_str = '' + if not self.sflow_interface: + return xml_str + + if not self.sflow_dict["counter"] and self.state == "absent": + return xml_str + + self.updates_cmd.append("interface %s" % self.sflow_interface) + if self.state == "present": + xml_str += '%s' % self.sflow_interface + else: + xml_str += '%s' % self.sflow_interface + + # counter_collector + if self.counter_collector: + if self.sflow_dict["counter"].get("collectorID") \ + and self.sflow_dict["counter"].get("collectorID") != "invalid": + existing = self.sflow_dict["counter"].get("collectorID").split(',') + else: + existing = list() + + if self.state == "present": + diff = list(set(self.counter_collector) - set(existing)) + if diff: + self.updates_cmd.append("sflow counter collector %s" % ' '.join(diff)) + new_set = list(self.counter_collector + existing) + xml_str += '%s' % ','.join(list(set(new_set))) + else: + same = list(set(self.counter_collector) & set(existing)) + if same: + self.updates_cmd.append( + "undo sflow counter collector %s" % ' '.join(same)) + xml_str += '%s' % ','.join(list(set(same))) + + # counter_interval + if self.counter_interval: + exist = bool(self.counter_interval == self.sflow_dict["counter"].get("interval")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow counter interval %s" % self.counter_interval) + xml_str += '%s' % self.counter_interval + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow counter interval %s" % self.counter_interval) + xml_str += '%s' % self.counter_interval + + if xml_str.endswith(""): + self.updates_cmd.pop() + return "" + + xml_str += '' + + return xml_str + + def config_export(self): + """configure sflow export""" + + xml_str = '' + if not self.export_route: + return xml_str + + if self.export_route == "enable": + if self.sflow_dict["export"] and self.sflow_dict["export"].get("ExportRoute") == "disable": + xml_str = 'disable' + self.updates_cmd.append("undo sflow export extended-route-data disable") + else: # disable + if not self.sflow_dict["export"] or self.sflow_dict["export"].get("ExportRoute") != "disable": + xml_str = 'disable' + self.updates_cmd.append("sflow export extended-route-data disable") + + return xml_str + + def netconf_load_config(self, xml_str): + """load sflow config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + self.netconf_set_config(xml_cfg, "SET_SFLOW") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check agent_ip + if self.agent_ip: + self.agent_ip = self.agent_ip.upper() + if not check_ip_addr(self.agent_ip): + self.module.fail_json(msg="Error: agent_ip is invalid.") + + # check source_ip + if self.source_ip: + self.source_ip = self.source_ip.upper() + if not check_ip_addr(self.source_ip): + self.module.fail_json(msg="Error: source_ip is invalid.") + + # check collector + if self.collector_id: + # check collector_ip and collector_ip_vpn + if self.collector_ip: + self.collector_ip = self.collector_ip.upper() + if not check_ip_addr(self.collector_ip): + self.module.fail_json( + msg="Error: collector_ip is invalid.") + if self.collector_ip_vpn and not is_valid_ip_vpn(self.collector_ip_vpn): + self.module.fail_json( + msg="Error: collector_ip_vpn is invalid.") + + # check collector_datagram_size ranges from 1024 to 8100 + if self.collector_datagram_size: + if not self.collector_datagram_size.isdigit(): + self.module.fail_json( + msg="Error: collector_datagram_size is not digit.") + if int(self.collector_datagram_size) < 1024 or int(self.collector_datagram_size) > 8100: + self.module.fail_json( + msg="Error: collector_datagram_size is not ranges from 1024 to 8100.") + + # check collector_udp_port ranges from 1 to 65535 + if self.collector_udp_port: + if not self.collector_udp_port.isdigit(): + self.module.fail_json( + msg="Error: collector_udp_port is not digit.") + if int(self.collector_udp_port) < 1 or int(self.collector_udp_port) > 65535: + self.module.fail_json( + msg="Error: collector_udp_port is not ranges from 1 to 65535.") + + # check collector_description 1 to 255 case-sensitive characters + if self.collector_description: + if self.collector_description.count(" "): + self.module.fail_json( + msg="Error: collector_description should without spaces.") + if len(self.collector_description) < 1 or len(self.collector_description) > 255: + self.module.fail_json( + msg="Error: collector_description is not ranges from 1 to 255.") + + # check sflow_interface + if self.sflow_interface: + intf_type = get_interface_type(self.sflow_interface) + if not intf_type: + self.module.fail_json(msg="Error: intf_type is invalid.") + if intf_type not in ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'eth-trunk']: + self.module.fail_json( + msg="Error: interface %s is not support sFlow." % self.sflow_interface) + + # check sample_collector + if self.sample_collector: + self.sample_collector.sort() + if self.sample_collector not in [["1"], ["2"], ["1", "2"]]: + self.module.fail_json( + msg="Error: sample_collector is invalid.") + + # check sample_rate ranges from 1 to 4294967295 + if self.sample_rate: + if not self.sample_rate.isdigit(): + self.module.fail_json( + msg="Error: sample_rate is not digit.") + if int(self.sample_rate) < 1 or int(self.sample_rate) > 4294967295: + self.module.fail_json( + msg="Error: sample_rate is not ranges from 1 to 4294967295.") + + # check sample_length ranges from 18 to 512 + if self.sample_length: + if not self.sample_length.isdigit(): + self.module.fail_json( + msg="Error: sample_rate is not digit.") + if int(self.sample_length) < 18 or int(self.sample_length) > 512: + self.module.fail_json( + msg="Error: sample_length is not ranges from 18 to 512.") + + # check counter_collector + if self.counter_collector: + self.counter_collector.sort() + if self.counter_collector not in [["1"], ["2"], ["1", "2"]]: + self.module.fail_json( + msg="Error: counter_collector is invalid.") + + # counter_interval ranges from 10 to 4294967295 + if self.counter_interval: + if not self.counter_interval.isdigit(): + self.module.fail_json( + msg="Error: counter_interval is not digit.") + if int(self.counter_interval) < 10 or int(self.counter_interval) > 4294967295: + self.module.fail_json( + msg="Error: sample_length is not ranges from 10 to 4294967295.") + + if self.rate_limit or self.rate_limit_slot or self.forward_enp_slot: + self.module.fail_json(msg="Error: The following parameters cannot be configured" + "because XML mode is not supported:rate_limit,rate_limit_slot,forward_enp_slot.") + + def get_proposed(self): + """get proposed info""" + + # base config + if self.agent_ip: + self.proposed["agent_ip"] = self.agent_ip + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.export_route: + self.proposed["export_route"] = self.export_route + if self.rate_limit: + self.proposed["rate_limit"] = self.rate_limit + self.proposed["rate_limit_slot"] = self.rate_limit_slot + if self.forward_enp_slot: + self.proposed["forward_enp_slot"] = self.forward_enp_slot + if self.collector_id: + self.proposed["collector_id"] = self.collector_id + if self.collector_ip: + self.proposed["collector_ip"] = self.collector_ip + self.proposed["collector_ip_vpn"] = self.collector_ip_vpn + if self.collector_datagram_size: + self.proposed[ + "collector_datagram_size"] = self.collector_datagram_size + if self.collector_udp_port: + self.proposed["collector_udp_port"] = self.collector_udp_port + if self.collector_meth: + self.proposed["collector_meth"] = self.collector_meth + if self.collector_description: + self.proposed[ + "collector_description"] = self.collector_description + + # sample and counter config + if self.sflow_interface: + self.proposed["sflow_interface"] = self.sflow_interface + if self.sample_collector: + self.proposed["sample_collector"] = self.sample_collector + if self.sample_rate: + self.proposed["sample_rate"] = self.sample_rate + if self.sample_length: + self.proposed["sample_length"] = self.sample_length + if self.sample_direction: + self.proposed["sample_direction"] = self.sample_direction + if self.counter_collector: + self.proposed["counter_collector"] = self.counter_collector + if self.counter_interval: + self.proposed["counter_interval"] = self.counter_interval + + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.sflow_dict: + return + + if self.agent_ip: + self.existing["agent"] = self.sflow_dict["agent"] + if self.source_ip: + self.existing["source"] = self.sflow_dict["source"] + if self.collector_id: + self.existing["collector"] = self.sflow_dict["collector"] + if self.export_route: + self.existing["export"] = self.sflow_dict["export"] + + if self.sflow_interface: + self.existing["sampling"] = self.sflow_dict["sampling"] + self.existing["counter"] = self.sflow_dict["counter"] + + def get_end_state(self): + """get end state info""" + + sflow_dict = self.get_sflow_dict() + if not sflow_dict: + return + + if self.agent_ip: + self.end_state["agent"] = sflow_dict["agent"] + if self.source_ip: + self.end_state["source"] = sflow_dict["source"] + if self.collector_id: + self.end_state["collector"] = sflow_dict["collector"] + if self.export_route: + self.end_state["export"] = sflow_dict["export"] + + if self.sflow_interface: + self.end_state["sampling"] = sflow_dict["sampling"] + self.end_state["counter"] = sflow_dict["counter"] + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.sflow_dict = self.get_sflow_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.export_route: + xml_str += self.config_export() + if self.agent_ip: + xml_str += self.config_agent() + if self.source_ip: + xml_str += self.config_source() + + if self.state == "present": + if self.collector_id and self.collector_ip: + xml_str += self.config_collector() + if self.sflow_interface: + xml_str += self.config_sampling() + xml_str += self.config_counter() + else: + if self.sflow_interface: + xml_str += self.config_sampling() + xml_str += self.config_counter() + if self.collector_id: + xml_str += self.config_collector() + + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + agent_ip=dict(required=False, type='str'), + source_ip=dict(required=False, type='str'), + export_route=dict(required=False, type='str', + choices=['enable', 'disable']), + rate_limit=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + rate_limit_slot=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + forward_enp_slot=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + collector_id=dict(required=False, type='str', choices=['1', '2']), + collector_ip=dict(required=False, type='str'), + collector_ip_vpn=dict(required=False, type='str'), + collector_datagram_size=dict(required=False, type='str'), + collector_udp_port=dict(required=False, type='str'), + collector_meth=dict(required=False, type='str', + choices=['meth', 'enhanced']), + collector_description=dict(required=False, type='str'), + sflow_interface=dict(required=False, type='str'), + sample_collector=dict(required=False, type='list'), + sample_rate=dict(required=False, type='str'), + sample_length=dict(required=False, type='str'), + sample_direction=dict(required=False, type='str', + choices=['inbound', 'outbound', 'both']), + counter_collector=dict(required=False, type='list'), + counter_interval=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = Sflow(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_community.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_community.py new file mode 100644 index 00000000..08704ba9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_community.py @@ -0,0 +1,975 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_community +short_description: Manages SNMP community configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP community configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_number: + description: + - Access control list number. + community_name: + description: + - Unique name to identify the community. + access_right: + description: + - Access right read or write. + choices: ['read','write'] + community_mib_view: + description: + - Mib view name. + group_name: + description: + - Unique name to identify the SNMPv3 group. + security_level: + description: + - Security level indicating whether to use authentication and encryption. + choices: ['noAuthNoPriv', 'authentication', 'privacy'] + read_view: + description: + - Mib view name for read. + write_view: + description: + - Mib view name for write. + notify_view: + description: + - Mib view name for notification. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp community test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP community" + community.network.ce_snmp_community: + state: present + community_name: Wdz123456789 + access_right: write + provider: "{{ cli }}" + + - name: "Undo SNMP community" + community.network.ce_snmp_community: + state: absent + community_name: Wdz123456789 + access_right: write + provider: "{{ cli }}" + + - name: "Config SNMP group" + community.network.ce_snmp_community: + state: present + group_name: wdz_group + security_level: noAuthNoPriv + acl_number: 2000 + provider: "{{ cli }}" + + - name: "Undo SNMP group" + community.network.ce_snmp_community: + state: absent + group_name: wdz_group + security_level: noAuthNoPriv + acl_number: 2000 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_number": "2000", "group_name": "wdz_group", + "security_level": "noAuthNoPriv", "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp v3 group": {"snmp_group": ["wdz_group", "noAuthNoPriv", "2000"]}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent group v3 wdz_group noauthentication acl 2000"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get snmp community +CE_GET_SNMP_COMMUNITY_HEADER = """ + + + + + + +""" +CE_GET_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# merge snmp community +CE_MERGE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_MERGE_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# create snmp community +CE_CREATE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_CREATE_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# delete snmp community +CE_DELETE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_DELETE_SNMP_COMMUNITY_TAIL = """ + + + + +""" + +# get snmp v3 group +CE_GET_SNMP_V3_GROUP_HEADER = """ + + + + + + +""" +CE_GET_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# merge snmp v3 group +CE_MERGE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_MERGE_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# create snmp v3 group +CE_CREATE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_CREATE_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# delete snmp v3 group +CE_DELETE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_DELETE_SNMP_V3_GROUP_TAIL = """ + + + + +""" + + +class SnmpCommunity(object): + """ Manages SNMP community configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure through netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure through netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_snmp_community_args(self, **kwargs): + """ Check snmp community args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + result["community_info"] = [] + state = module.params['state'] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + if community_name and access_right: + if len(community_name) > 32 or len(community_name) == 0: + module.fail_json( + msg='Error: The len of community_name %s is out of [1 - 32].' % community_name) + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The len of acl_number %s is out of [1 - 32] or is invalid.' % acl_number) + + if community_mib_view: + if len(community_mib_view) > 32 or len(community_mib_view) == 0: + module.fail_json( + msg='Error: The len of community_mib_view %s is out of [1 - 32].' % community_mib_view) + + conf_str = CE_GET_SNMP_COMMUNITY_HEADER + if acl_number: + conf_str += "" + if community_mib_view: + conf_str += "" + + conf_str += CE_GET_SNMP_COMMUNITY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + community_info = root.findall("snmp/communitys/community") + if community_info: + for tmp in community_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["communityName", "accessRight", "aclNumber", "mibViewName"]: + tmp_dict[site.tag] = site.text + + result["community_info"].append(tmp_dict) + + if result["community_info"]: + community_name_list = list() + for tmp in result["community_info"]: + if "communityName" in tmp.keys(): + community_name_list.append(tmp["communityName"]) + + if community_name not in community_name_list: + need_cfg = True + else: + need_cfg_bool = True + + for tmp in result["community_info"]: + if tmp["communityName"] == community_name: + + cfg_bool_list = list() + + if access_right: + if "accessRight" in tmp.keys(): + need_cfg_access = False + if tmp["accessRight"] != access_right: + need_cfg_access = True + else: + need_cfg_access = True + + cfg_bool_list.append(need_cfg_access) + + if acl_number: + if "aclNumber" in tmp.keys(): + need_cfg_acl = False + if tmp["aclNumber"] != acl_number: + need_cfg_acl = True + else: + need_cfg_acl = True + + cfg_bool_list.append(need_cfg_acl) + + if community_mib_view: + if "mibViewName" in tmp.keys(): + need_cfg_mib = False + if tmp["mibViewName"] != community_mib_view: + need_cfg_mib = True + else: + need_cfg_mib = True + cfg_bool_list.append(need_cfg_mib) + + if True not in cfg_bool_list: + need_cfg_bool = False + + if state == "present": + if not need_cfg_bool: + need_cfg = False + else: + need_cfg = True + else: + if not need_cfg_bool: + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def check_snmp_v3_group_args(self, **kwargs): + """ Check snmp v3 group args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + result["group_info"] = [] + state = module.params['state'] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + community_name = module.params['community_name'] + access_right = module.params['access_right'] + + if group_name and security_level: + + if community_name and access_right: + module.fail_json( + msg='Error: Community is used for v1/v2c, group_name is used for v3, do not ' + 'input at the same time.') + + if len(group_name) > 32 or len(group_name) == 0: + module.fail_json( + msg='Error: The len of group_name %s is out of [1 - 32].' % group_name) + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The len of acl_number %s is out of [1 - 32] or is invalid.' % acl_number) + + if read_view: + if len(read_view) > 32 or len(read_view) < 1: + module.fail_json( + msg='Error: The len of read_view %s is out of [1 - 32].' % read_view) + + if write_view: + if len(write_view) > 32 or len(write_view) < 1: + module.fail_json( + msg='Error: The len of write_view %s is out of [1 - 32].' % write_view) + + if notify_view: + if len(notify_view) > 32 or len(notify_view) < 1: + module.fail_json( + msg='Error: The len of notify_view %s is out of [1 - 32].' % notify_view) + + conf_str = CE_GET_SNMP_V3_GROUP_HEADER + if acl_number: + conf_str += "" + if read_view: + conf_str += "" + if write_view: + conf_str += "" + if notify_view: + conf_str += "" + + conf_str += CE_GET_SNMP_V3_GROUP_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + group_info = root.findall("snmp/snmpv3Groups/snmpv3Group") + if group_info: + for tmp in group_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["groupName", "securityLevel", "readViewName", "writeViewName", + "notifyViewName", "aclNumber"]: + tmp_dict[site.tag] = site.text + + result["group_info"].append(tmp_dict) + + if result["group_info"]: + group_name_list = list() + + for tmp in result["group_info"]: + if "groupName" in tmp.keys(): + group_name_list.append(tmp["groupName"]) + if group_name not in group_name_list: + if state == "present": + need_cfg = True + else: + need_cfg = False + else: + need_cfg_bool = True + for tmp in result["group_info"]: + if tmp["groupName"] == group_name: + + cfg_bool_list = list() + + if security_level: + if "securityLevel" in tmp.keys(): + need_cfg_group = False + if tmp["securityLevel"] != security_level: + need_cfg_group = True + else: + need_cfg_group = True + + cfg_bool_list.append(need_cfg_group) + + if acl_number: + if "aclNumber" in tmp.keys(): + need_cfg_acl = False + if tmp["aclNumber"] != acl_number: + need_cfg_acl = True + else: + need_cfg_acl = True + + cfg_bool_list.append(need_cfg_acl) + + if read_view: + if "readViewName" in tmp.keys(): + need_cfg_read = False + if tmp["readViewName"] != read_view: + need_cfg_read = True + else: + need_cfg_read = True + cfg_bool_list.append(need_cfg_read) + + if write_view: + if "writeViewName" in tmp.keys(): + need_cfg_write = False + if tmp["writeViewName"] != write_view: + need_cfg_write = True + else: + need_cfg_write = True + cfg_bool_list.append(need_cfg_write) + + if notify_view: + if "notifyViewName" in tmp.keys(): + need_cfg_notify = False + if tmp["notifyViewName"] != notify_view: + need_cfg_notify = True + else: + need_cfg_notify = True + cfg_bool_list.append(need_cfg_notify) + + if True not in cfg_bool_list: + need_cfg_bool = False + + if state == "present": + if not need_cfg_bool: + need_cfg = False + else: + need_cfg = True + else: + if not need_cfg_bool: + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def merge_snmp_community(self, **kwargs): + """ Merge snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_MERGE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_MERGE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp community failed.') + + community_safe_name = "******" + + cmd = "snmp-agent community %s %s" % (access_right, community_safe_name) + + if acl_number: + cmd += " acl %s" % acl_number + if community_mib_view: + cmd += " mib-view %s" % community_mib_view + + return cmd + + def create_snmp_community(self, **kwargs): + """ Create snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_CREATE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_CREATE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp community failed.') + + community_safe_name = "******" + + cmd = "snmp-agent community %s %s" % (access_right, community_safe_name) + + if acl_number: + cmd += " acl %s" % acl_number + if community_mib_view: + cmd += " mib-view %s" % community_mib_view + + return cmd + + def delete_snmp_community(self, **kwargs): + """ Delete snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_DELETE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_DELETE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp community failed.') + + community_safe_name = "******" + cmd = "undo snmp-agent community %s %s" % ( + access_right, community_safe_name) + + return cmd + + def merge_snmp_v3_group(self, **kwargs): + """ Merge snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_MERGE_SNMP_V3_GROUP_HEADER % (group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_MERGE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "snmp-agent group v3 %s %s" % (group_name, security_level_cli) + + if read_view: + cmd += " read-view %s" % read_view + if write_view: + cmd += " write-view %s" % write_view + if notify_view: + cmd += " notify-view %s" % notify_view + if acl_number: + cmd += " acl %s" % acl_number + + return cmd + + def create_snmp_v3_group(self, **kwargs): + """ Create snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_CREATE_SNMP_V3_GROUP_HEADER % ( + group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_CREATE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "snmp-agent group v3 %s %s" % (group_name, security_level_cli) + + if read_view: + cmd += " read-view %s" % read_view + if write_view: + cmd += " write-view %s" % write_view + if notify_view: + cmd += " notify-view %s" % notify_view + if acl_number: + cmd += " acl %s" % acl_number + + return cmd + + def delete_snmp_v3_group(self, **kwargs): + """ Delete snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_DELETE_SNMP_V3_GROUP_HEADER % ( + group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_DELETE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "undo snmp-agent group v3 %s %s" % ( + group_name, security_level_cli) + + return cmd + + +def main(): + """ main function """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_number=dict(type='str'), + community_name=dict(type='str', no_log=True), + access_right=dict(choices=['read', 'write']), + community_mib_view=dict(type='str'), + group_name=dict(type='str'), + security_level=dict( + choices=['noAuthNoPriv', 'authentication', 'privacy']), + read_view=dict(type='str'), + write_view=dict(type='str'), + notify_view=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + required_together = [("community_name", "access_right"), ("security_level", "group_name")] + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + supports_check_mode=True + ) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + acl_number = module.params['acl_number'] + community_name = module.params['community_name'] + community_mib_view = module.params['community_mib_view'] + access_right = module.params['access_right'] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + snmp_community_obj = SnmpCommunity() + + if not snmp_community_obj: + module.fail_json(msg='Error: Init module failed.') + + snmp_community_rst = snmp_community_obj.check_snmp_community_args( + module=module) + snmp_v3_group_rst = snmp_community_obj.check_snmp_v3_group_args( + module=module) + + # get proposed + proposed["state"] = state + if acl_number: + proposed["acl_number"] = acl_number + if community_name: + proposed["community_name"] = community_name + if community_mib_view: + proposed["community_mib_view"] = community_mib_view + if access_right: + proposed["access_right"] = access_right + if group_name: + proposed["group_name"] = group_name + if security_level: + proposed["security_level"] = security_level + if read_view: + proposed["read_view"] = read_view + if write_view: + proposed["write_view"] = write_view + if notify_view: + proposed["notify_view"] = notify_view + + # state exist snmp community config + exist_tmp = dict() + for item in snmp_community_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_community_rst[item] + + if exist_tmp: + existing["snmp community"] = exist_tmp + # state exist snmp v3 group config + exist_tmp = dict() + for item in snmp_v3_group_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_group_rst[item] + + if exist_tmp: + existing["snmp v3 group"] = exist_tmp + + if state == "present": + if snmp_community_rst["need_cfg"]: + if len(snmp_community_rst["community_info"]) != 0: + cmd = snmp_community_obj.merge_snmp_community(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_community_obj.create_snmp_community(module=module) + changed = True + updates.append(cmd) + + if snmp_v3_group_rst["need_cfg"]: + if len(snmp_v3_group_rst["group_info"]): + cmd = snmp_community_obj.merge_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_community_obj.create_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + + else: + if snmp_community_rst["need_cfg"]: + cmd = snmp_community_obj.delete_snmp_community(module=module) + changed = True + updates.append(cmd) + if snmp_v3_group_rst["need_cfg"]: + cmd = snmp_community_obj.delete_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + + # state end snmp community config + snmp_community_rst = snmp_community_obj.check_snmp_community_args( + module=module) + end_tmp = dict() + for item in snmp_community_rst: + if item != "need_cfg": + end_tmp[item] = snmp_community_rst[item] + end_tmp[item] = snmp_community_rst[item] + if end_tmp: + end_state["snmp community"] = end_tmp + # state end snmp v3 group config + snmp_v3_group_rst = snmp_community_obj.check_snmp_v3_group_args( + module=module) + end_tmp = dict() + for item in snmp_v3_group_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_group_rst[item] + if end_tmp: + end_state["snmp v3 group"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_contact.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_contact.py new file mode 100644 index 00000000..873a2aa6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_contact.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_contact +short_description: Manages SNMP contact configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP contact configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + contact: + description: + - Contact information. + required: true + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp contact test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP contact" + community.network.ce_snmp_contact: + state: present + contact: call Operator at 010-99999999 + provider: "{{ cli }}" + + - name: "Undo SNMP contact" + community.network.ce_snmp_contact: + state: absent + contact: call Operator at 010-99999999 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"contact": "call Operator at 010-99999999", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"contact": "call Operator at 010-99999999"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent sys-info contact call Operator at 010-99999999"] +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +class SnmpContact(object): + """ Manages SNMP contact configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + + # module args + self.state = self.module.params['state'] + self.contact = self.module.params['contact'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check invalid args """ + + if self.contact: + if len(self.contact) > 255 or len(self.contact) < 1: + self.module.fail_json( + msg='Error: The len of contact %s is out of [1 - 255].' % self.contact) + else: + self.module.fail_json( + msg='Error: The len of contact is 0.') + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.contact: + self.proposed["contact"] = self.contact + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"contact ") + if len(temp_data) > 1: + self.cur_cfg["contact"] = temp_data[1] + self.existing["contact"] = temp_data[1] + + def get_end_state(self): + """ Get end state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"contact ") + if len(temp_data) > 1: + self.end_state["contact"] = temp_data[1] + + def cli_load_config(self, commands): + """ Load configure by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get configure by cli """ + + regular = "| include snmp | include contact" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_config(self): + """ Set configure by cli """ + + cmd = "snmp-agent sys-info contact %s" % self.contact + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config(self): + """ Undo configure by cli """ + + cmd = "undo snmp-agent sys-info contact" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Main work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if "contact" in self.cur_cfg.keys() and self.contact == self.cur_cfg["contact"]: + pass + else: + self.set_config() + else: + if "contact" in self.cur_cfg.keys() and self.contact == self.cur_cfg["contact"]: + self.undo_config() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + contact=dict(type='str', required=True) + ) + + argument_spec.update(ce_argument_spec) + module = SnmpContact(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_location.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_location.py new file mode 100644 index 00000000..ad19f568 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_location.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_location +short_description: Manages SNMP location configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP location configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + location: + description: + - Location information. + required: true + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp location test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP location" + community.network.ce_snmp_location: + state: present + location: nanjing China + provider: "{{ cli }}" + + - name: "Remove SNMP location" + community.network.ce_snmp_location: + state: absent + location: nanjing China + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"location": "nanjing China", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"location": "nanjing China"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent sys-info location nanjing China"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +class SnmpLocation(object): + """ Manages SNMP location configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + + # module args + self.state = self.module.params['state'] + self.location = self.module.params['location'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check invalid args """ + + if self.location: + if len(self.location) > 255 or len(self.location) < 1: + self.module.fail_json( + msg='Error: The len of location %s is out of [1 - 255].' % self.location) + else: + self.module.fail_json( + msg='Error: The len of location is 0.') + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.location: + self.proposed["location"] = self.location + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"location ") + if len(temp_data) > 1: + self.cur_cfg["location"] = temp_data[1] + self.existing["location"] = temp_data[1] + + def get_end_state(self): + """ Get end state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"location ") + if len(temp_data) > 1: + self.end_state["location"] = temp_data[1] + + def cli_load_config(self, commands): + """ Load config by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get config by cli """ + + regular = "| include snmp | include location" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_config(self): + """ Set configure by cli """ + + cmd = "snmp-agent sys-info location %s" % self.location + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config(self): + """ Undo configure by cli """ + + cmd = "undo snmp-agent sys-info location" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Main work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if "location" in self.cur_cfg.keys() and self.location == self.cur_cfg["location"]: + pass + else: + self.set_config() + else: + if "location" in self.cur_cfg.keys() and self.location == self.cur_cfg["location"]: + self.undo_config() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + location=dict(type='str', required=True) + ) + + argument_spec.update(ce_argument_spec) + module = SnmpLocation(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_target_host.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_target_host.py new file mode 100644 index 00000000..f1f52087 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_target_host.py @@ -0,0 +1,940 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_target_host +short_description: Manages SNMP target host configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP target host configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + version: + description: + - Version(s) Supported by SNMP Engine. + choices: ['none', 'v1', 'v2c', 'v3', 'v1v2c', 'v1v3', 'v2cv3', 'all'] + connect_port: + description: + - Udp port used by SNMP agent to connect the Network management. + host_name: + description: + - Unique name to identify target host entry. + address: + description: + - Network Address. + notify_type: + description: + - To configure notify type as trap or inform. + choices: ['trap','inform'] + vpn_name: + description: + - VPN instance Name. + recv_port: + description: + - UDP Port number used by network management to receive alarm messages. + security_model: + description: + - Security Model. + choices: ['v1','v2c', 'v3'] + security_name: + description: + - Security Name. + security_name_v3: + description: + - Security Name V3. + security_level: + description: + - Security level indicating whether to use authentication and encryption. + choices: ['noAuthNoPriv','authentication', 'privacy'] + is_public_net: + description: + - To enable or disable Public Net-manager for target Host. + default: no_use + choices: ['no_use','true','false'] + interface_name: + description: + - Name of the interface to send the trap message. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp target host test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP version" + community.network.ce_snmp_target_host: + state: present + version: v2cv3 + provider: "{{ cli }}" + + - name: "Config SNMP target host" + community.network.ce_snmp_target_host: + state: present + host_name: test1 + address: 1.1.1.1 + notify_type: trap + vpn_name: js + security_model: v2c + security_name: wdz + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"address": "10.135.182.158", "host_name": "test2", + "notify_type": "trap", "security_level": "authentication", + "security_model": "v3", "security_name_v3": "wdz", + "state": "present", "vpn_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"target host info": [{"address": "10.135.182.158", "domain": "snmpUDPDomain", + "nmsName": "test2", "notifyType": "trap", + "securityLevel": "authentication", "securityModel": "v3", + "securityNameV3": "wdz", "vpnInstanceName": "js"}]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent target-host host-name test2 trap address udp-domain 10.135.182.158 vpn-instance js params securityname wdz v3 authentication"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, \ + ce_argument_spec, load_config, check_ip_addr + +# get snmp version +CE_GET_SNMP_VERSION = """ + + + + + + + +""" +# merge snmp version +CE_MERGE_SNMP_VERSION = """ + + + + %s + + + +""" + +# get snmp target host +CE_GET_SNMP_TARGET_HOST_HEADER = """ + + + + + +""" +CE_GET_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# merge snmp target host +CE_MERGE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_MERGE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# create snmp target host +CE_CREATE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_CREATE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# delete snmp target host +CE_DELETE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_DELETE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# get snmp listen port +CE_GET_SNMP_PORT = """ + + + + + + + +""" + +# merge snmp listen port +CE_MERGE_SNMP_PORT = """ + + + + %s + + + +""" + + +INTERFACE_TYPE = ['ethernet', 'eth-trunk', 'tunnel', 'null', 'loopback', + 'vlanif', '100ge', '40ge', 'mtunnel', '10ge', 'ge', 'meth', 'vbdif', 'nve'] + + +class SnmpTargetHost(object): + """ Manages SNMP target host configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + required_together = [("address", "notify_type"), ("address", "notify_type")] + required_if = [ + ["security_model", "v1", ["security_name"]], + ["security_model", "v2c", ["security_name"]], + ["security_model", "v3", ["security_name_v3"]] + ] + self.module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + required_if=required_if, + supports_check_mode=True + ) + + # module args + self.state = self.module.params['state'] + self.version = self.module.params['version'] + self.connect_port = self.module.params['connect_port'] + self.host_name = self.module.params['host_name'] + self.domain = "snmpUDPDomain" + self.address = self.module.params['address'] + self.notify_type = self.module.params['notify_type'] + self.vpn_name = self.module.params['vpn_name'] + self.recv_port = self.module.params['recv_port'] + self.security_model = self.module.params['security_model'] + self.security_name = self.module.params['security_name'] + self.security_name_v3 = self.module.params['security_name_v3'] + self.security_level = self.module.params['security_level'] + self.is_public_net = self.module.params['is_public_net'] + self.interface_name = self.module.params['interface_name'] + + # config + self.cur_cli_cfg = dict() + self.cur_netconf_cfg = dict() + self.end_netconf_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def check_cli_args(self): + """ Check invalid cli args """ + + if self.connect_port: + if int(self.connect_port) != 161 and (int(self.connect_port) > 65535 or int(self.connect_port) < 1025): + self.module.fail_json( + msg='Error: The value of connect_port %s is out of [161, 1025 - 65535].' % self.connect_port) + + def check_netconf_args(self, result): + """ Check invalid netconf args """ + + need_cfg = True + same_flag = True + delete_flag = False + result["target_host_info"] = [] + + if self.host_name: + + if len(self.host_name) > 32 or len(self.host_name) < 1: + self.module.fail_json( + msg='Error: The len of host_name is out of [1 - 32].') + + if self.vpn_name and self.is_public_net != 'no_use': + if self.is_public_net == "true": + self.module.fail_json( + msg='Error: Do not support vpn_name and is_public_net at the same time.') + + conf_str = CE_GET_SNMP_TARGET_HOST_HEADER + + if self.domain: + conf_str += "" + + if self.address: + if not check_ip_addr(ipaddr=self.address): + self.module.fail_json( + msg='Error: The host address [%s] is invalid.' % self.address) + conf_str += "
" + + if self.notify_type: + conf_str += "" + + if self.vpn_name: + if len(self.vpn_name) > 31 or len(self.vpn_name) < 1: + self.module.fail_json( + msg='Error: The len of vpn_name is out of [1 - 31].') + conf_str += "" + + if self.recv_port: + if int(self.recv_port) > 65535 or int(self.recv_port) < 0: + self.module.fail_json( + msg='Error: The value of recv_port is out of [0 - 65535].') + conf_str += "" + + if self.security_model: + conf_str += "" + + if self.security_name: + if len(self.security_name) > 32 or len(self.security_name) < 1: + self.module.fail_json( + msg='Error: The len of security_name is out of [1 - 32].') + conf_str += "" + + if self.security_name_v3: + if len(self.security_name_v3) > 32 or len(self.security_name_v3) < 1: + self.module.fail_json( + msg='Error: The len of security_name_v3 is out of [1 - 32].') + conf_str += "" + + if self.security_level: + conf_str += "" + + if self.is_public_net != 'no_use': + conf_str += "" + + if self.interface_name: + if len(self.interface_name) > 63 or len(self.interface_name) < 1: + self.module.fail_json( + msg='Error: The len of interface_name is out of [1 - 63].') + + find_flag = False + for item in INTERFACE_TYPE: + if item in self.interface_name.lower(): + find_flag = True + break + if not find_flag: + self.module.fail_json( + msg='Error: Please input full name of interface_name.') + + conf_str += "" + + conf_str += CE_GET_SNMP_TARGET_HOST_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + if self.state == "present": + same_flag = False + else: + delete_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + target_host_info = root.findall( + "snmp/targetHosts/targetHost") + if target_host_info: + for tmp in target_host_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["nmsName", "domain", "address", "notifyType", "vpnInstanceName", + "portNumber", "securityModel", "securityName", "securityNameV3", + "securityLevel", "isPublicNet", "interface-name"]: + tmp_dict[site.tag] = site.text + + result["target_host_info"].append(tmp_dict) + + if result["target_host_info"]: + for tmp in result["target_host_info"]: + + same_flag = True + + if "nmsName" in tmp.keys(): + if tmp["nmsName"] != self.host_name: + same_flag = False + else: + delete_flag = True + + if "domain" in tmp.keys(): + if tmp["domain"] != self.domain: + same_flag = False + + if "address" in tmp.keys(): + if tmp["address"] != self.address: + same_flag = False + + if "notifyType" in tmp.keys(): + if tmp["notifyType"] != self.notify_type: + same_flag = False + + if "vpnInstanceName" in tmp.keys(): + if tmp["vpnInstanceName"] != self.vpn_name: + same_flag = False + + if "portNumber" in tmp.keys(): + if tmp["portNumber"] != self.recv_port: + same_flag = False + + if "securityModel" in tmp.keys(): + if tmp["securityModel"] != self.security_model: + same_flag = False + + if "securityName" in tmp.keys(): + if tmp["securityName"] != self.security_name: + same_flag = False + + if "securityNameV3" in tmp.keys(): + if tmp["securityNameV3"] != self.security_name_v3: + same_flag = False + + if "securityLevel" in tmp.keys(): + if tmp["securityLevel"] != self.security_level: + same_flag = False + + if "isPublicNet" in tmp.keys(): + if tmp["isPublicNet"] != self.is_public_net: + same_flag = False + + if "interface-name" in tmp.keys(): + if tmp.get("interface-name") is not None: + if tmp["interface-name"].lower() != self.interface_name.lower(): + same_flag = False + else: + same_flag = False + + if same_flag: + break + + if self.state == "present": + need_cfg = True + if same_flag: + need_cfg = False + else: + need_cfg = False + if delete_flag: + need_cfg = True + + result["need_cfg"] = need_cfg + + def cli_load_config(self, commands): + """ Load configure by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_snmp_version(self): + """ Get snmp version """ + + version = None + conf_str = CE_GET_SNMP_VERSION + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + pass + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + version_info = root.find("snmp/engine") + if version_info: + for site in version_info: + if site.tag in ["version"]: + version = site.text + + return version + + def xml_get_connect_port(self): + """ Get connect port by xml """ + tmp_cfg = None + conf_str = CE_GET_SNMP_PORT + recv_xml = self.netconf_get_config(conf_str=conf_str) + if "" in recv_xml: + pass + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + snmp_port_info = root.findall("snmp/systemCfg/snmpListenPort") + + if snmp_port_info: + tmp_cfg = snmp_port_info[0].text + return tmp_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.version: + self.proposed["version"] = self.version + if self.connect_port: + self.proposed["connect_port"] = self.connect_port + if self.host_name: + self.proposed["host_name"] = self.host_name + if self.address: + self.proposed["address"] = self.address + if self.notify_type: + self.proposed["notify_type"] = self.notify_type + if self.vpn_name: + self.proposed["vpn_name"] = self.vpn_name + if self.recv_port: + self.proposed["recv_port"] = self.recv_port + if self.security_model: + self.proposed["security_model"] = self.security_model + if self.security_name: + self.proposed["security_name"] = "******" + if self.security_name_v3: + self.proposed["security_name_v3"] = self.security_name_v3 + if self.security_level: + self.proposed["security_level"] = self.security_level + if self.is_public_net != 'no_use': + self.proposed["is_public_net"] = self.is_public_net + if self.interface_name: + self.proposed["interface_name"] = self.interface_name + + def get_existing(self): + """ Get existing state """ + + if self.version: + version = self.get_snmp_version() + if version: + self.cur_cli_cfg["version"] = version + self.existing["version"] = version + + if self.connect_port: + tmp_cfg = self.xml_get_connect_port() + if tmp_cfg: + self.cur_cli_cfg["connect port"] = tmp_cfg + self.existing["connect port"] = tmp_cfg + + if self.host_name: + self.existing["target host info"] = self.cur_netconf_cfg[ + "target_host_info"] + + def get_end_state(self): + """ Get end state """ + + if self.version: + version = self.get_snmp_version() + if version: + self.end_state["version"] = version + + if self.connect_port: + tmp_cfg = self.xml_get_connect_port() + if tmp_cfg: + self.end_state["connect port"] = tmp_cfg + + if self.host_name: + self.end_state["target host info"] = self.end_netconf_cfg[ + "target_host_info"] + if self.existing == self.end_state: + self.changed = False + self.updates_cmd = list() + + def config_version_cli(self): + """ Config version by cli """ + + if "disable" in self.cur_cli_cfg["version"]: + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + else: + if self.version != self.cur_cli_cfg["version"]: + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config_version_cli(self): + """ Undo config version by cli """ + + if "disable" in self.cur_cli_cfg["version"]: + pass + else: + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + self.cli_load_config(cmds) + self.changed = True + + def config_connect_port_xml(self): + """ Config connect port by xml """ + + if "connect port" in self.cur_cli_cfg.keys(): + if self.cur_cli_cfg["connect port"] == self.connect_port: + pass + else: + cmd = "snmp-agent udp-port %s" % self.connect_port + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + conf_str = CE_MERGE_SNMP_PORT % self.connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + else: + cmd = "snmp-agent udp-port %s" % self.connect_port + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + conf_str = CE_MERGE_SNMP_PORT % self.connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + + def undo_config_connect_port_cli(self): + """ Undo config connect port by cli """ + + if "connect port" in self.cur_cli_cfg.keys(): + if not self.cur_cli_cfg["connect port"]: + pass + else: + cmd = "undo snmp-agent udp-port" + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + connect_port = "161" + conf_str = CE_MERGE_SNMP_PORT % connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + + def merge_snmp_target_host(self): + """ Merge snmp target host operation """ + + conf_str = CE_MERGE_SNMP_TARGET_HOST_HEADER % self.host_name + + if self.domain: + conf_str += "%s" % self.domain + if self.address: + conf_str += "
%s
" % self.address + if self.notify_type: + conf_str += "%s" % self.notify_type + if self.vpn_name: + conf_str += "%s" % self.vpn_name + if self.recv_port: + conf_str += "%s" % self.recv_port + if self.security_model: + conf_str += "%s" % self.security_model + if self.security_name: + conf_str += "%s" % self.security_name + if self.security_name_v3: + conf_str += "%s" % self.security_name_v3 + if self.security_level: + conf_str += "%s" % self.security_level + if self.is_public_net != 'no_use': + conf_str += "%s" % self.is_public_net + if self.interface_name: + conf_str += "%s" % self.interface_name + + conf_str += CE_MERGE_SNMP_TARGET_HOST_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge snmp target host failed.') + + cmd = "snmp-agent target-host host-name %s " % self.host_name + cmd += "%s " % self.notify_type + cmd += "address udp-domain %s " % self.address + + if self.recv_port: + cmd += "udp-port %s " % self.recv_port + if self.interface_name: + cmd += "source %s " % self.interface_name + if self.vpn_name: + cmd += "vpn-instance %s " % self.vpn_name + if self.is_public_net == "true": + cmd += "public-net " + if self.security_model in ["v1", "v2c"] and self.security_name: + cmd += "params securityname %s %s " % ( + "******", self.security_model) + if self.security_model == "v3" and self.security_name_v3: + cmd += "params securityname %s %s " % ( + self.security_name_v3, self.security_model) + if self.security_level and self.security_level in ["authentication", "privacy"]: + cmd += "%s" % self.security_level + + self.changed = True + self.updates_cmd.append(cmd) + + def delete_snmp_target_host(self): + """ Delete snmp target host operation """ + + conf_str = CE_DELETE_SNMP_TARGET_HOST_HEADER % self.host_name + + if self.domain: + conf_str += "%s" % self.domain + if self.address: + conf_str += "
%s
" % self.address + if self.notify_type: + conf_str += "%s" % self.notify_type + if self.vpn_name: + conf_str += "%s" % self.vpn_name + if self.recv_port: + conf_str += "%s" % self.recv_port + if self.security_model: + conf_str += "%s" % self.security_model + if self.security_name: + conf_str += "%s" % self.security_name + if self.security_name_v3: + conf_str += "%s" % self.security_name_v3 + if self.security_level: + conf_str += "%s" % self.security_level + if self.is_public_net != 'no_use': + conf_str += "%s" % self.is_public_net + if self.interface_name: + conf_str += "%s" % self.interface_name + + conf_str += CE_DELETE_SNMP_TARGET_HOST_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete snmp target host failed.') + + if not self.address: + cmd = "undo snmp-agent target-host host-name %s " % self.host_name + else: + if self.notify_type == "trap": + cmd = "undo snmp-agent target-host trap address udp-domain %s " % self.address + else: + cmd = "undo snmp-agent target-host inform address udp-domain %s " % self.address + if self.recv_port: + cmd += "udp-port %s " % self.recv_port + if self.interface_name: + cmd += "source %s " % self.interface_name + if self.vpn_name: + cmd += "vpn-instance %s " % self.vpn_name + if self.is_public_net == "true": + cmd += "public-net " + if self.security_model in ["v1", "v2c"] and self.security_name: + cmd += "params securityname %s" % "******" + if self.security_model == "v3" and self.security_name_v3: + cmd += "params securityname %s" % self.security_name_v3 + + self.changed = True + self.updates_cmd.append(cmd) + + def merge_snmp_version(self): + """ Merge snmp version operation """ + + conf_str = CE_MERGE_SNMP_VERSION % self.version + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge snmp version failed.') + + if self.version == "none": + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + self.updates_cmd.append(cmd) + elif self.version == "v1v2c": + cmd = "snmp-agent sys-info version v1" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v2c" + self.updates_cmd.append(cmd) + elif self.version == "v1v3": + cmd = "snmp-agent sys-info version v1" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v3" + self.updates_cmd.append(cmd) + elif self.version == "v2cv3": + cmd = "snmp-agent sys-info version v2c" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v3" + self.updates_cmd.append(cmd) + else: + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_cli_args() + self.check_netconf_args(self.cur_netconf_cfg) + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.version: + if self.version != self.cur_cli_cfg["version"]: + self.merge_snmp_version() + if self.connect_port: + self.config_connect_port_xml() + if self.cur_netconf_cfg["need_cfg"]: + self.merge_snmp_target_host() + + else: + if self.connect_port: + self.undo_config_connect_port_cli() + if self.cur_netconf_cfg["need_cfg"]: + self.delete_snmp_target_host() + + self.check_netconf_args(self.end_netconf_cfg) + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + version=dict(choices=['none', 'v1', 'v2c', 'v3', + 'v1v2c', 'v1v3', 'v2cv3', 'all']), + connect_port=dict(type='str'), + host_name=dict(type='str'), + address=dict(type='str'), + notify_type=dict(choices=['trap', 'inform']), + vpn_name=dict(type='str'), + recv_port=dict(type='str'), + security_model=dict(choices=['v1', 'v2c', 'v3']), + security_name=dict(type='str', no_log=True), + security_name_v3=dict(type='str'), + security_level=dict( + choices=['noAuthNoPriv', 'authentication', 'privacy']), + is_public_net=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + interface_name=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + module = SnmpTargetHost(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_traps.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_traps.py new file mode 100644 index 00000000..6d9ea6a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_traps.py @@ -0,0 +1,559 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_traps +short_description: Manages SNMP traps configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP traps configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + feature_name: + description: + - Alarm feature name. + choices: ['aaa', 'arp', 'bfd', 'bgp', 'cfg', 'configuration', 'dad', 'devm', + 'dhcpsnp', 'dldp', 'driver', 'efm', 'erps', 'error-down', 'fcoe', + 'fei', 'fei_comm', 'fm', 'ifnet', 'info', 'ipsg', 'ipv6', 'isis', + 'l3vpn', 'lacp', 'lcs', 'ldm', 'ldp', 'ldt', 'lldp', 'mpls_lspm', + 'msdp', 'mstp', 'nd', 'netconf', 'nqa', 'nvo3', 'openflow', 'ospf', + 'ospfv3', 'pim', 'pim-std', 'qos', 'radius', 'rm', 'rmon', 'securitytrap', + 'smlktrap', 'snmp', 'ssh', 'stackmng', 'sysclock', 'sysom', 'system', + 'tcp', 'telnet', 'trill', 'trunk', 'tty', 'vbst', 'vfs', 'virtual-perception', + 'vrrp', 'vstm', 'all'] + trap_name: + description: + - Alarm trap name. + interface_type: + description: + - Interface type. + choices: ['Ethernet', 'Eth-Trunk', 'Tunnel', 'NULL', 'LoopBack', 'Vlanif', '100GE', + '40GE', 'MTunnel', '10GE', 'GE', 'MEth', 'Vbdif', 'Nve'] + interface_number: + description: + - Interface number. + port_number: + description: + - Source port number. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp traps test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP trap all enable" + community.network.ce_snmp_traps: + state: present + feature_name: all + provider: "{{ cli }}" + + - name: "Config SNMP trap interface" + community.network.ce_snmp_traps: + state: present + interface_type: 40GE + interface_number: 2/0/1 + provider: "{{ cli }}" + + - name: "Config SNMP trap port" + community.network.ce_snmp_traps: + state: present + port_number: 2222 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"feature_name": "all", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"snmp-agent trap": [], + "undo snmp-agent trap": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp-agent trap": ["enable"], + "undo snmp-agent trap": []} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent trap enable"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config, ce_argument_spec, run_commands +from ansible.module_utils.connection import exec_command + + +class SnmpTraps(object): + """ Manages SNMP trap configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule( + argument_spec=self.spec, + required_together=[("interface_type", "interface_number")], + supports_check_mode=True + ) + + # config + self.cur_cfg = dict() + self.cur_cfg["snmp-agent trap"] = [] + self.cur_cfg["undo snmp-agent trap"] = [] + + # module args + self.state = self.module.params['state'] + self.feature_name = self.module.params['feature_name'] + self.trap_name = self.module.params['trap_name'] + self.interface_type = self.module.params['interface_type'] + self.interface_number = self.module.params['interface_number'] + self.port_number = self.module.params['port_number'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.existing["snmp-agent trap"] = [] + self.existing["undo snmp-agent trap"] = [] + self.end_state = dict() + self.end_state["snmp-agent trap"] = [] + self.end_state["undo snmp-agent trap"] = [] + + commands = list() + cmd1 = 'display interface brief' + commands.append(cmd1) + self.interface = run_commands(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def check_args(self): + """ Check invalid args """ + + if self.port_number: + if self.port_number.isdigit(): + if int(self.port_number) < 1025 or int(self.port_number) > 65535: + self.module.fail_json( + msg='Error: The value of port_number is out of [1025 - 65535].') + else: + self.module.fail_json( + msg='Error: The port_number is not digit.') + + if self.interface_type and self.interface_number: + tmp_interface = self.interface_type + self.interface_number + if tmp_interface not in self.interface[0]: + self.module.fail_json( + msg='Error: The interface %s is not in the device.' % tmp_interface) + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.feature_name: + self.proposed["feature_name"] = self.feature_name + + if self.trap_name: + self.proposed["trap_name"] = self.trap_name + + if self.interface_type: + self.proposed["interface_type"] = self.interface_type + + if self.interface_number: + self.proposed["interface_number"] = self.interface_number + + if self.port_number: + self.proposed["port_number"] = self.port_number + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_cfg_lower = tmp_cfg.lower() + temp_data = tmp_cfg.split("\n") + temp_data_lower = temp_cfg_lower.split("\n") + + for item in temp_data: + if "snmp-agent trap source-port " in item: + if self.port_number: + item_tmp = item.split("snmp-agent trap source-port ") + self.cur_cfg["trap source-port"] = item_tmp[1] + self.existing["trap source-port"] = item_tmp[1] + elif "snmp-agent trap source " in item: + if self.interface_type: + item_tmp = item.split("snmp-agent trap source ") + self.cur_cfg["trap source interface"] = item_tmp[1] + self.existing["trap source interface"] = item_tmp[1] + + if self.feature_name: + for item in temp_data_lower: + if item == "snmp-agent trap enable": + self.cur_cfg["snmp-agent trap"].append("enable") + self.existing["snmp-agent trap"].append("enable") + elif item == "snmp-agent trap disable": + self.cur_cfg["snmp-agent trap"].append("disable") + self.existing["snmp-agent trap"].append("disable") + elif "undo snmp-agent trap enable " in item: + item_tmp = item.split("undo snmp-agent trap enable ") + self.cur_cfg[ + "undo snmp-agent trap"].append(item_tmp[1]) + self.existing[ + "undo snmp-agent trap"].append(item_tmp[1]) + elif "snmp-agent trap enable " in item: + item_tmp = item.split("snmp-agent trap enable ") + self.cur_cfg["snmp-agent trap"].append(item_tmp[1]) + self.existing["snmp-agent trap"].append(item_tmp[1]) + else: + del self.existing["snmp-agent trap"] + del self.existing["undo snmp-agent trap"] + + def get_end_state(self): + """ Get end_state state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_cfg_lower = tmp_cfg.lower() + temp_data = tmp_cfg.split("\n") + temp_data_lower = temp_cfg_lower.split("\n") + + for item in temp_data: + if "snmp-agent trap source-port " in item: + if self.port_number: + item_tmp = item.split("snmp-agent trap source-port ") + self.end_state["trap source-port"] = item_tmp[1] + elif "snmp-agent trap source " in item: + if self.interface_type: + item_tmp = item.split("snmp-agent trap source ") + self.end_state["trap source interface"] = item_tmp[1] + + if self.feature_name: + for item in temp_data_lower: + if item == "snmp-agent trap enable": + self.end_state["snmp-agent trap"].append("enable") + elif item == "snmp-agent trap disable": + self.end_state["snmp-agent trap"].append("disable") + elif "undo snmp-agent trap enable " in item: + item_tmp = item.split("undo snmp-agent trap enable ") + self.end_state[ + "undo snmp-agent trap"].append(item_tmp[1]) + elif "snmp-agent trap enable " in item: + item_tmp = item.split("snmp-agent trap enable ") + self.end_state["snmp-agent trap"].append(item_tmp[1]) + else: + del self.end_state["snmp-agent trap"] + del self.end_state["undo snmp-agent trap"] + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def cli_load_config(self, commands): + """ Load configure through cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get configure through cli """ + + regular = "| include snmp | include trap" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_trap_feature_name(self): + """ Set feature name for trap """ + + if self.feature_name == "all": + cmd = "snmp-agent trap enable" + else: + cmd = "snmp-agent trap enable feature-name %s" % self.feature_name + if self.trap_name: + cmd += " trap-name %s" % self.trap_name + + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_feature_name(self): + """ Undo feature name for trap """ + + if self.feature_name == "all": + cmd = "undo snmp-agent trap enable" + else: + cmd = "undo snmp-agent trap enable feature-name %s" % self.feature_name + if self.trap_name: + cmd += " trap-name %s" % self.trap_name + + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def set_trap_source_interface(self): + """ Set source interface for trap """ + + cmd = "snmp-agent trap source %s %s" % ( + self.interface_type, self.interface_number) + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_source_interface(self): + """ Undo source interface for trap """ + + cmd = "undo snmp-agent trap source" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def set_trap_source_port(self): + """ Set source port for trap """ + + cmd = "snmp-agent trap source-port %s" % self.port_number + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_source_port(self): + """ Undo source port for trap """ + + cmd = "undo snmp-agent trap source-port" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ The work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + find_flag = False + find_undo_flag = False + tmp_interface = None + + if self.state == "present": + if self.feature_name: + if self.trap_name: + tmp_cfg = "feature-name %s trap-name %s" % ( + self.feature_name, self.trap_name.lower()) + else: + tmp_cfg = "feature-name %s" % self.feature_name + + find_undo_flag = False + if self.cur_cfg["undo snmp-agent trap"]: + for item in self.cur_cfg["undo snmp-agent trap"]: + if item == tmp_cfg: + find_undo_flag = True + elif tmp_cfg in item: + find_undo_flag = True + elif self.feature_name == "all": + find_undo_flag = True + if find_undo_flag: + self.set_trap_feature_name() + + if not find_undo_flag: + find_flag = False + if self.cur_cfg["snmp-agent trap"]: + for item in self.cur_cfg["snmp-agent trap"]: + if item == "enable": + find_flag = True + elif item == tmp_cfg: + find_flag = True + if not find_flag: + self.set_trap_feature_name() + + if self.interface_type: + find_flag = False + tmp_interface = self.interface_type + self.interface_number + + if "trap source interface" in self.cur_cfg.keys(): + if self.cur_cfg["trap source interface"] == tmp_interface: + find_flag = True + + if not find_flag: + self.set_trap_source_interface() + + if self.port_number: + find_flag = False + + if "trap source-port" in self.cur_cfg.keys(): + if self.cur_cfg["trap source-port"] == self.port_number: + find_flag = True + + if not find_flag: + self.set_trap_source_port() + + else: + if self.feature_name: + if self.trap_name: + tmp_cfg = "feature-name %s trap-name %s" % ( + self.feature_name, self.trap_name.lower()) + else: + tmp_cfg = "feature-name %s" % self.feature_name + + find_flag = False + if self.cur_cfg["snmp-agent trap"]: + for item in self.cur_cfg["snmp-agent trap"]: + if item == tmp_cfg: + find_flag = True + elif item == "enable": + find_flag = True + elif tmp_cfg in item: + find_flag = True + else: + find_flag = True + + find_undo_flag = False + if self.cur_cfg["undo snmp-agent trap"]: + for item in self.cur_cfg["undo snmp-agent trap"]: + if item == tmp_cfg: + find_undo_flag = True + elif tmp_cfg in item: + find_undo_flag = True + + if find_undo_flag: + pass + elif find_flag: + self.undo_trap_feature_name() + + if self.interface_type: + if "trap source interface" in self.cur_cfg.keys(): + self.undo_trap_source_interface() + + if self.port_number: + if "trap source-port" in self.cur_cfg.keys(): + self.undo_trap_source_port() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + feature_name=dict(choices=['aaa', 'arp', 'bfd', 'bgp', 'cfg', 'configuration', 'dad', + 'devm', 'dhcpsnp', 'dldp', 'driver', 'efm', 'erps', 'error-down', + 'fcoe', 'fei', 'fei_comm', 'fm', 'ifnet', 'info', 'ipsg', 'ipv6', + 'isis', 'l3vpn', 'lacp', 'lcs', 'ldm', 'ldp', 'ldt', 'lldp', + 'mpls_lspm', 'msdp', 'mstp', 'nd', 'netconf', 'nqa', 'nvo3', + 'openflow', 'ospf', 'ospfv3', 'pim', 'pim-std', 'qos', 'radius', + 'rm', 'rmon', 'securitytrap', 'smlktrap', 'snmp', 'ssh', 'stackmng', + 'sysclock', 'sysom', 'system', 'tcp', 'telnet', 'trill', 'trunk', + 'tty', 'vbst', 'vfs', 'virtual-perception', 'vrrp', 'vstm', 'all']), + trap_name=dict(type='str'), + interface_type=dict(choices=['Ethernet', 'Eth-Trunk', 'Tunnel', 'NULL', 'LoopBack', 'Vlanif', + '100GE', '40GE', 'MTunnel', '10GE', 'GE', 'MEth', 'Vbdif', 'Nve']), + interface_number=dict(type='str'), + port_number=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + module = SnmpTraps(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_user.py new file mode 100644 index 00000000..e666ce8b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_snmp_user.py @@ -0,0 +1,1044 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_user +short_description: Manages SNMP user configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP user configurations on CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_number: + description: + - Access control list number. + usm_user_name: + description: + - Unique name to identify the USM user. + aaa_local_user: + description: + - Unique name to identify the local user. + remote_engine_id: + description: + - Remote engine id of the USM user. + user_group: + description: + - Name of the group where user belongs to. + auth_protocol: + description: + - Authentication protocol. + choices: ['noAuth', 'md5', 'sha'] + auth_key: + description: + - The authentication password. Password length, 8-255 characters. + priv_protocol: + description: + - Encryption protocol. + choices: ['noPriv', 'des56', '3des168', 'aes128', 'aes192', 'aes256'] + priv_key: + description: + - The encryption password. Password length 8-255 characters. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp user test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP usm user" + community.network.ce_snmp_user: + state: present + usm_user_name: wdz_snmp + remote_engine_id: 800007DB03389222111200 + acl_number: 2000 + user_group: wdz_group + provider: "{{ cli }}" + + - name: "Undo SNMP usm user" + community.network.ce_snmp_user: + state: absent + usm_user_name: wdz_snmp + remote_engine_id: 800007DB03389222111200 + acl_number: 2000 + user_group: wdz_group + provider: "{{ cli }}" + + - name: "Config SNMP local user" + community.network.ce_snmp_user: + state: present + aaa_local_user: wdz_user + auth_protocol: md5 + auth_key: huawei123 + priv_protocol: des56 + priv_key: huawei123 + provider: "{{ cli }}" + + - name: "Config SNMP local user" + community.network.ce_snmp_user: + state: absent + aaa_local_user: wdz_user + auth_protocol: md5 + auth_key: huawei123 + priv_protocol: des56 + priv_key: huawei123 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_number": "2000", "remote_engine_id": "800007DB03389222111200", + "state": "present", "user_group": "wdz_group", + "usm_user_name": "wdz_snmp"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"snmp local user": {"local_user_info": []}, + "snmp usm user": {"usm_user_info": []}} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp local user": {"local_user_info": []}, + "snmp usm user": {"usm_user_info": [{"aclNumber": "2000", "engineID": "800007DB03389222111200", + "groupName": "wdz_group", "userName": "wdz_snmp"}]}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent remote-engineid 800007DB03389222111200 usm-user v3 wdz_snmp wdz_group acl 2000"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + +# get snmp v3 USM user +CE_GET_SNMP_V3_USM_USER_HEADER = """ + + + + + + + +""" +CE_GET_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# merge snmp v3 USM user +CE_MERGE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_MERGE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# create snmp v3 USM user +CE_CREATE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_CREATE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# delete snmp v3 USM user +CE_DELETE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_DELETE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" + +# get snmp v3 aaa local user +CE_GET_SNMP_V3_LOCAL_USER = """ + + + + + + + + + + + + + +""" +# merge snmp v3 aaa local user +CE_MERGE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# create snmp v3 aaa local user +CE_CREATE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# delete snmp v3 aaa local user +CE_DELETE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# display info +GET_SNMP_LOCAL_ENGINE = """ + + + + + + + +""" + + +class SnmpUser(object): + """ Manages SNMP user configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_snmp_v3_usm_user_args(self, **kwargs): + """ Check snmp v3 usm user invalid args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + state = module.params['state'] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + local_user_name = module.params['aaa_local_user'] + + if usm_user_name: + if len(usm_user_name) > 32 or len(usm_user_name) == 0: + module.fail_json( + msg='Error: The length of usm_user_name %s is out of [1 - 32].' % usm_user_name) + if remote_engine_id: + if len(remote_engine_id) > 64 or len(remote_engine_id) < 10: + module.fail_json( + msg='Error: The length of remote_engine_id %s is out of [10 - 64].' % remote_engine_id) + + conf_str = CE_GET_SNMP_V3_USM_USER_HEADER + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The length of acl_number %s is out of [1 - 32].' % acl_number) + + conf_str += "" + + if user_group: + if len(user_group) > 32 or len(user_group) == 0: + module.fail_json( + msg='Error: The length of user_group %s is out of [1 - 32].' % user_group) + + conf_str += "" + + if auth_protocol: + conf_str += "" + + if auth_key: + if len(auth_key) > 255 or len(auth_key) == 0: + module.fail_json( + msg='Error: The length of auth_key %s is out of [1 - 255].' % auth_key) + + conf_str += "" + + if priv_protocol: + if not auth_protocol: + module.fail_json( + msg='Error: Please input auth_protocol at the same time.') + + conf_str += "" + + if priv_key: + if len(priv_key) > 255 or len(priv_key) == 0: + module.fail_json( + msg='Error: The length of priv_key %s is out of [1 - 255].' % priv_key) + conf_str += "" + + result["usm_user_info"] = [] + + conf_str += CE_GET_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + usm_user_info = root.findall("snmp/usmUsers/usmUser") + if usm_user_info: + for tmp in usm_user_info: + tmp_dict = dict() + tmp_dict["remoteEngineID"] = None + for site in tmp: + if site.tag in ["userName", "remoteEngineID", "engineID", "groupName", "authProtocol", + "authKey", "privProtocol", "privKey", "aclNumber"]: + tmp_dict[site.tag] = site.text + + result["usm_user_info"].append(tmp_dict) + + cur_cfg = dict() + if usm_user_name: + cur_cfg["userName"] = usm_user_name + if user_group: + cur_cfg["groupName"] = user_group + if auth_protocol: + cur_cfg["authProtocol"] = auth_protocol + if auth_key: + cur_cfg["authKey"] = auth_key + if priv_protocol: + cur_cfg["privProtocol"] = priv_protocol + if priv_key: + cur_cfg["privKey"] = priv_key + if acl_number: + cur_cfg["aclNumber"] = acl_number + + if remote_engine_id: + cur_cfg["engineID"] = remote_engine_id + cur_cfg["remoteEngineID"] = "true" + else: + cur_cfg["engineID"] = self.local_engine_id + cur_cfg["remoteEngineID"] = "false" + + if result["usm_user_info"]: + num = 0 + for tmp in result["usm_user_info"]: + if cur_cfg == tmp: + num += 1 + + if num == 0: + if state == "present": + need_cfg = True + else: + need_cfg = False + else: + if state == "present": + need_cfg = False + else: + need_cfg = True + + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def check_snmp_v3_local_user_args(self, **kwargs): + """ Check snmp v3 local user invalid args """ + + module = kwargs["module"] + result = dict() + + need_cfg = False + state = module.params['state'] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + usm_user_name = module.params['usm_user_name'] + + if local_user_name: + + if usm_user_name: + module.fail_json( + msg='Error: Please do not input usm_user_name and local_user_name at the same time.') + + if not auth_protocol or not auth_key or not priv_protocol or not priv_key: + module.fail_json( + msg='Error: Please input auth_protocol auth_key priv_protocol priv_key for local user.') + + if len(local_user_name) > 32 or len(local_user_name) == 0: + module.fail_json( + msg='Error: The length of local_user_name %s is out of [1 - 32].' % local_user_name) + + if len(auth_key) > 255 or len(auth_key) == 0: + module.fail_json( + msg='Error: The length of auth_key %s is out of [1 - 255].' % auth_key) + + if len(priv_key) > 255 or len(priv_key) == 0: + module.fail_json( + msg='Error: The length of priv_key %s is out of [1 - 255].' % priv_key) + + result["local_user_info"] = [] + + conf_str = CE_GET_SNMP_V3_LOCAL_USER + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_user_info = root.findall( + "snmp/localUsers/localUser") + if local_user_info: + for tmp in local_user_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["userName", "authProtocol", "authKey", "privProtocol", "privKey"]: + tmp_dict[site.tag] = site.text + + result["local_user_info"].append(tmp_dict) + + if result["local_user_info"]: + for tmp in result["local_user_info"]: + if "userName" in tmp.keys(): + if state == "present": + if tmp["userName"] != local_user_name: + need_cfg = True + else: + if tmp["userName"] == local_user_name: + need_cfg = True + if auth_protocol: + if "authProtocol" in tmp.keys(): + if state == "present": + if tmp["authProtocol"] != auth_protocol: + need_cfg = True + else: + if tmp["authProtocol"] == auth_protocol: + need_cfg = True + if auth_key: + if "authKey" in tmp.keys(): + if state == "present": + if tmp["authKey"] != auth_key: + need_cfg = True + else: + if tmp["authKey"] == auth_key: + need_cfg = True + if priv_protocol: + if "privProtocol" in tmp.keys(): + if state == "present": + if tmp["privProtocol"] != priv_protocol: + need_cfg = True + else: + if tmp["privProtocol"] == priv_protocol: + need_cfg = True + if priv_key: + if "privKey" in tmp.keys(): + if state == "present": + if tmp["privKey"] != priv_key: + need_cfg = True + else: + if tmp["privKey"] == priv_key: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def merge_snmp_v3_usm_user(self, **kwargs): + """ Merge snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + cmds = [] + + if remote_engine_id: + conf_str = CE_MERGE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_MERGE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + cmd += " %s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + cmd += " acl %s" % acl_number + + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_protocol != "noAuth": + cmd += " authentication-mode %s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if auth_protocol != "noAuth": + cmd += " cipher %s" % "******" + if auth_protocol or auth_key: + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " privacy-mode %s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " cipher %s" % "******" + if priv_key or priv_protocol: + cmds.append(cmd) + + conf_str += CE_MERGE_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 usm user failed.') + + return cmds + + def create_snmp_v3_usm_user(self, **kwargs): + """ Create snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + cmds = [] + + if remote_engine_id: + conf_str = CE_CREATE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_CREATE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + cmd += " %s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + cmd += " acl %s" % acl_number + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_protocol != "noAuth": + cmd += " authentication-mode %s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if auth_protocol != "noAuth": + cmd += " cipher %s" % "******" + + if auth_key or auth_protocol: + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " privacy-mode %s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " cipher %s" % "******" + + if priv_protocol or priv_key: + cmds.append(cmd) + + conf_str += CE_CREATE_SNMP_V3_USM_USER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 usm user failed.') + + return cmds + + def delete_snmp_v3_usm_user(self, **kwargs): + """ Delete snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + if remote_engine_id: + conf_str = CE_DELETE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "undo snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_DELETE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "undo snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + conf_str += CE_DELETE_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 usm user failed.') + + return cmd + + def merge_snmp_v3_local_user(self, **kwargs): + """ Merge snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_MERGE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 local user failed.') + + cmd = "snmp-agent local-user v3 %s " % local_user_name + "authentication-mode %s " % auth_protocol + \ + "cipher ****** " + "privacy-mode %s " % priv_protocol + "cipher ******" + + return cmd + + def create_snmp_v3_local_user(self, **kwargs): + """ Create snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_CREATE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 local user failed.') + + cmd = "snmp-agent local-user v3 %s " % local_user_name + "authentication-mode %s " % auth_protocol + \ + "cipher ****** " + "privacy-mode %s " % priv_protocol + "cipher ******" + + return cmd + + def delete_snmp_v3_local_user(self, **kwargs): + """ Delete snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_DELETE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 local user failed.') + + cmd = "undo snmp-agent local-user v3 %s" % local_user_name + + return cmd + + def get_snmp_local_engine(self, **kwargs): + """ Get snmp local engine operation """ + + module = kwargs["module"] + + conf_str = GET_SNMP_LOCAL_ENGINE + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_engine_info = root.findall("snmp/engine/engineID") + if local_engine_info: + self.local_engine_id = local_engine_info[0].text + + +def main(): + """ Module main function """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_number=dict(type='str'), + usm_user_name=dict(type='str'), + remote_engine_id=dict(type='str'), + user_group=dict(type='str'), + auth_protocol=dict(choices=['noAuth', 'md5', 'sha']), + auth_key=dict(type='str', no_log=True), + priv_protocol=dict( + choices=['noPriv', 'des56', '3des168', 'aes128', 'aes192', 'aes256']), + priv_key=dict(type='str', no_log=True), + aaa_local_user=dict(type='str') + ) + + mutually_exclusive = [("usm_user_name", "local_user_name")] + argument_spec.update(ce_argument_spec) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True + ) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + acl_number = module.params['acl_number'] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + aaa_local_user = module.params['aaa_local_user'] + + snmp_user_obj = SnmpUser() + + if not snmp_user_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if acl_number: + proposed["acl_number"] = acl_number + if usm_user_name: + proposed["usm_user_name"] = usm_user_name + if remote_engine_id: + proposed["remote_engine_id"] = remote_engine_id + if user_group: + proposed["user_group"] = user_group + if auth_protocol: + proposed["auth_protocol"] = auth_protocol + if auth_key: + proposed["auth_key"] = auth_key + if priv_protocol: + proposed["priv_protocol"] = priv_protocol + if priv_key: + proposed["priv_key"] = priv_key + if aaa_local_user: + proposed["aaa_local_user"] = aaa_local_user + + snmp_user_obj.get_snmp_local_engine(module=module) + snmp_v3_usm_user_rst = snmp_user_obj.check_snmp_v3_usm_user_args( + module=module) + snmp_v3_local_user_rst = snmp_user_obj.check_snmp_v3_local_user_args( + module=module) + + # state exist snmp v3 user config + exist_tmp = dict() + for item in snmp_v3_usm_user_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_usm_user_rst[item] + if exist_tmp: + existing["snmp usm user"] = exist_tmp + + exist_tmp = dict() + for item in snmp_v3_local_user_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_local_user_rst[item] + if exist_tmp: + existing["snmp local user"] = exist_tmp + + if state == "present": + if snmp_v3_usm_user_rst["need_cfg"]: + if len(snmp_v3_usm_user_rst["usm_user_info"]) != 0: + cmd = snmp_user_obj.merge_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_user_obj.create_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + + if snmp_v3_local_user_rst["need_cfg"]: + if len(snmp_v3_local_user_rst["local_user_info"]) != 0: + cmd = snmp_user_obj.merge_snmp_v3_local_user( + module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_user_obj.create_snmp_v3_local_user( + module=module) + changed = True + updates.append(cmd) + + else: + if snmp_v3_usm_user_rst["need_cfg"]: + cmd = snmp_user_obj.delete_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + if snmp_v3_local_user_rst["need_cfg"]: + cmd = snmp_user_obj.delete_snmp_v3_local_user(module=module) + changed = True + updates.append(cmd) + + # state exist snmp v3 user config + snmp_v3_usm_user_rst = snmp_user_obj.check_snmp_v3_usm_user_args( + module=module) + end_tmp = dict() + for item in snmp_v3_usm_user_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_usm_user_rst[item] + if end_tmp: + end_state["snmp usm user"] = end_tmp + + snmp_v3_local_user_rst = snmp_user_obj.check_snmp_v3_local_user_args( + module=module) + end_tmp = dict() + for item in snmp_v3_local_user_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_local_user_rst[item] + if end_tmp: + end_state["snmp local user"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_startup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_startup.py new file mode 100644 index 00000000..fe0e143a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_startup.py @@ -0,0 +1,465 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_startup +short_description: Manages a system startup information on HUAWEI CloudEngine switches. +description: + - Manages a system startup information on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + cfg_file: + description: + - Name of the configuration file that is applied for the next startup. + The value is a string of 5 to 255 characters. + default: present + software_file: + description: + - File name of the system software that is applied for the next startup. + The value is a string of 5 to 255 characters. + patch_file: + description: + - Name of the patch file that is applied for the next startup. + slot: + description: + - Position of the device.The value is a string of 1 to 32 characters. + The possible value of slot is all, slave-board, or the specific slotID. + action: + description: + - Display the startup information. + choices: ['display'] + +''' + +EXAMPLES = ''' +- name: Startup module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Display startup information + community.network.ce_startup: + action: display + provider: "{{ cli }}" + + - name: Set startup patch file + community.network.ce_startup: + patch_file: 2.PAT + slot: all + provider: "{{ cli }}" + + - name: Set startup software file + community.network.ce_startup: + software_file: aa.cc + slot: 1 + provider: "{{ cli }}" + + - name: Set startup cfg file + community.network.ce_startup: + cfg_file: 2.cfg + slot: 1 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"patch_file": "2.PAT", + "slot": "all"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { + "configSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "curentPatchFile": "NULL", + "curentStartupFile": "NULL", + "curentSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "nextPatchFile": "flash:/1.PAT", + "nextStartupFile": "flash:/1.cfg", + "nextSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "position": "5" + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"StartupInfos": null} +updates: + description: command sent to the device + returned: always + type: list + sample: {"startup patch 2.PAT all"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, run_commands +from ansible.module_utils.connection import exec_command + + +class StartUp(object): + """ + Manages system startup information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.cfg_file = self.module.params['cfg_file'] + self.software_file = self.module.params['software_file'] + self.patch_file = self.module.params['patch_file'] + self.slot = self.module.params['slot'] + self.action = self.module.params['action'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # system startup info + self.startup_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_startup_dict(self): + """Retrieves the current config from the device or cache + """ + cmd = 'display startup' + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + startup_info = dict() + startup_info["StartupInfos"] = list() + if not cfg: + return startup_info + else: + re_find = re.findall(r'(.*)\s*' + r'\s*Configured\s*startup\s*system\s*software:\s*(.*)' + r'\s*Startup\s*system\s*software:\s*(.*)' + r'\s*Next\s*startup\s*system\s*software:\s*(.*)' + r'\s*Startup\s*saved-configuration\s*file:\s*(.*)' + r'\s*Next\s*startup\s*saved-configuration\s*file:\s*(.*)' + r'\s*Startup\s*paf\s*file:\s*(.*)' + r'\s*Next\s*startup\s*paf\s*file:\s*(.*)' + r'\s*Startup\s*patch\s*package:\s*(.*)' + r'\s*Next\s*startup\s*patch\s*package:\s*(.*)', cfg) + + if re_find: + for mem in re_find: + startup_info["StartupInfos"].append( + dict(nextStartupFile=mem[5], configSysSoft=mem[1], curentSysSoft=mem[2], + nextSysSoft=mem[3], curentStartupFile=mem[4], curentPatchFile=mem[8], + nextPatchFile=mem[9], postion=mem[0])) + return startup_info + return startup_info + + def get_cfg_filename_type(self, filename): + """Gets the type of cfg filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Configuration file name include spaces.') + + iftype = None + + if filename.endswith('.cfg'): + iftype = 'cfg' + elif filename.endswith('.zip'): + iftype = 'zip' + elif filename.endswith('.dat'): + iftype = 'dat' + else: + return None + return iftype.lower() + + def get_pat_filename_type(self, filename): + """Gets the type of patch filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Patch file name include spaces.') + + iftype = None + + if filename.endswith('.PAT'): + iftype = 'PAT' + else: + return None + return iftype.upper() + + def get_software_filename_type(self, filename): + """Gets the type of software filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Software file name include spaces.') + + iftype = None + + if filename.endswith('.cc'): + iftype = 'cc' + else: + return None + return iftype.lower() + + def startup_next_cfg_file(self): + """set next cfg file""" + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + cmd['command'] = "startup saved-configuration %s slot %s" % ( + self.cfg_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup saved-configuration %s slot %s" % (self.cfg_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup saved-configuration %s" % self.cfg_file + commands.append(cmd) + self.updates_cmd.append( + "startup saved-configuration %s" % self.cfg_file) + run_commands(self.module, commands) + self.changed = True + + def startup_next_software_file(self): + """set next software file""" + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + if self.slot == "all" or self.slot == "slave-board": + cmd['command'] = "startup system-software %s %s" % ( + self.software_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s %s" % (self.software_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup system-software %s slot %s" % ( + self.software_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s slot %s" % (self.software_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + + if not self.slot: + cmd['command'] = "startup system-software %s" % self.software_file + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s" % self.software_file) + run_commands(self.module, commands) + self.changed = True + + def startup_next_pat_file(self): + """set next patch file""" + + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + if self.slot == "all": + cmd['command'] = "startup patch %s %s" % ( + self.patch_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s %s" % (self.patch_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup patch %s slot %s" % ( + self.patch_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s slot %s" % (self.patch_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + + if not self.slot: + cmd['command'] = "startup patch %s" % self.patch_file + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s" % self.patch_file) + run_commands(self.module, commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + # cfg_file check + if self.cfg_file: + if not self.get_cfg_filename_type(self.cfg_file): + self.module.fail_json( + msg='Error: Invalid cfg file name or cfg file name extension ( *.cfg, *.zip, *.dat ).') + + # software_file check + if self.software_file: + if not self.get_software_filename_type(self.software_file): + self.module.fail_json( + msg='Error: Invalid software file name or software file name extension ( *.cc).') + + # patch_file check + if self.patch_file: + if not self.get_pat_filename_type(self.patch_file): + self.module.fail_json( + msg='Error: Invalid patch file name or patch file name extension ( *.PAT ).') + + # slot check + if self.slot: + if self.slot.isdigit(): + if int(self.slot) <= 0 or int(self.slot) > 16: + self.module.fail_json( + msg='Error: The number of slot is not in the range from 1 to 16.') + else: + if len(self.slot) <= 0 or len(self.slot) > 32: + self.module.fail_json( + msg='Error: The length of slot is not in the range from 1 to 32.') + + def get_proposed(self): + """get proposed info""" + + if self.cfg_file: + self.proposed["cfg_file"] = self.cfg_file + if self.software_file: + self.proposed["system_file"] = self.software_file + if self.patch_file: + self.proposed["patch_file"] = self.patch_file + if self.slot: + self.proposed["slot"] = self.slot + + def get_existing(self): + """get existing info""" + + if not self.startup_info: + self.existing["StartupInfos"] = None + else: + self.existing["StartupInfos"] = self.startup_info["StartupInfos"] + + def get_end_state(self): + """get end state info""" + if not self.startup_info: + self.end_state["StartupInfos"] = None + else: + self.end_state["StartupInfos"] = self.startup_info["StartupInfos"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_proposed() + + self.startup_info = self.get_startup_dict() + self.get_existing() + + startup_info = self.startup_info["StartupInfos"][0] + if self.cfg_file: + if self.cfg_file != startup_info["nextStartupFile"]: + self.startup_next_cfg_file() + + if self.software_file: + if self.software_file != startup_info["nextSysSoft"]: + self.startup_next_software_file() + if self.patch_file: + if self.patch_file != startup_info["nextPatchFile"]: + self.startup_next_pat_file() + if self.action == "display": + self.startup_info = self.get_startup_dict() + + self.startup_info = self.get_startup_dict() + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + cfg_file=dict(type='str'), + software_file=dict(type='str'), + patch_file=dict(type='str'), + slot=dict(type='str'), + action=dict(type='str', choices=['display']) + ) + argument_spec.update(ce_argument_spec) + module = StartUp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route.py new file mode 100644 index 00000000..c3d28d8d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route.py @@ -0,0 +1,829 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_static_route +short_description: Manages static route configuration on HUAWEI CloudEngine switches. +description: + - Manages the static routes on HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + prefix: + description: + - Destination ip address of static route. + required: true + mask: + description: + - Destination ip mask of static route. + required: true + aftype: + description: + - Destination ip address family type of static route. + required: true + choices: ['v4','v6'] + next_hop: + description: + - Next hop address of static route. + nhp_interface: + description: + - Next hop interface full name of static route. + vrf: + description: + - VPN instance of destination ip address. + destvrf: + description: + - VPN instance of next hop ip address. + tag: + description: + - Route tag value (numeric). + description: + description: + - Name of the route. Used with the name parameter on the CLI. + pref: + description: + - Preference or administrative difference of route (range 1-255). + state: + description: + - Specify desired state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Static route module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config a ipv4 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + prefix: 2.1.1.2 + mask: 24 + next_hop: 3.1.1.2 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" + - name: Config a ipv4 static route ,next hop is an interface and that it has the proper description + community.network.ce_static_route: + prefix: 2.1.1.2 + mask: 24 + next_hop: 10GE1/0/1 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" + - name: Config a ipv6 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + prefix: fc00:0:0:2001::1 + mask: 64 + next_hop: fc00:0:0:2004::1 + description: 'Configured by Ansible' + aftype: v6 + provider: "{{ cli }}" + - name: Config a ipv4 static route, next hop is an interface and that it has the proper description + community.network.ce_static_route: + prefix: fc00:0:0:2001::1 + mask: 64 + next_hop: 10GE1/0/1 + description: 'Configured by Ansible' + aftype: v6 + provider: "{{ cli }}" + - name: Config a VRF and set ipv4 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + vrf: vpna + prefix: 2.1.1.2 + mask: 24 + next_hop: 3.1.1.2 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.642", "mask": "24", "description": "testing", + "vrf": "_public_"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip route-static 192.168.20.0 255.255.255.0 3.3.3.3 preference 100 description testing"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_STATIC_ROUTE = """ + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_ABSENT = """ + + + + + + + + + + + + + + + + + + +""" + +CE_NC_SET_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s%s%s%s + + + + +""" +CE_NC_SET_DESCRIPTION = """ +%s +""" + +CE_NC_SET_PREFERENCE = """ +%s +""" + +CE_NC_SET_TAG = """ +%s +""" + +CE_NC_DELETE_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_v4addr(addr): + """check if ipv4 addr is valid""" + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + +def is_valid_v6addr(addr): + """check if ipv6 addr is valid""" + if addr.find(':') != -1: + addr_list = addr.split(':') + # The IPv6 binary system has a length of 128 bits and is grouped by 16 bits. + # Each group is separated by a colon ":" and can be divided into 8 groups, each group being represented by 4 hexadecimal + if len(addr_list) > 8: + return False + # You can use a double colon "::" to represent a group of 0 or more consecutive 0s, but only once. + if addr.count('::') > 1: + return False + # if do not use '::', the length of address should not be less than 8. + if addr.count('::') == 0 and len(addr_list) < 8: + return False + for group in addr_list: + if group.strip() == '': + continue + try: + # Each group is represented in 4-digit hexadecimal + int(group, base=16) + except ValueError: + return False + return True + return False + + +def is_valid_tag(tag): + """check if the tag is valid""" + + if not tag.isdigit(): + return False + + if int(tag) < 1 or int(tag) > 4294967295: + return False + + return True + + +def is_valid_preference(pref): + """check if the preference is valid""" + if pref.isdigit(): + return int(pref) > 0 and int(pref) < 256 + else: + return False + + +def is_valid_description(description): + """check if the description is valid""" + if description.find('?') != -1: + return False + if len(description) < 1 or len(description) > 255: + return False + return True + + +class StaticRoute(object): + """static route module""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # static route info + self.prefix = self.module.params['prefix'] + self.mask = self.module.params['mask'] + self.aftype = self.module.params['aftype'] + self.next_hop = self.module.params['next_hop'] + self.nhp_interface = self.module.params['nhp_interface'] + if self.nhp_interface is None: + self.nhp_interface = "Invalid0" + self.tag = self.module.params['tag'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + self.pref = self.module.params['pref'] + + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + self.destvrf = self.module.params['destvrf'] + if self.destvrf is None: + self.destvrf = "_public_" + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.static_routes_info = dict() + + def init_module(self): + """init module""" + + required_one_of = [["next_hop", "nhp_interface"]] + self.module = AnsibleModule( + argument_spec=self.spec, required_one_of=required_one_of, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def convert_len_to_mask(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def convert_ip_prefix(self): + """convert prefix to real value i.e. 2.2.2.2/24 to 2.2.2.0/24""" + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + if self.mask == '32': + return True + if self.mask == '0': + self.prefix = '0.0.0.0' + return True + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + byte_len = 8 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + else: + if self.prefix.find(':') == -1: + return False + if self.mask == '128': + return True + if self.mask == '0': + self.prefix = '::' + return True + addr_list = self.prefix.split(':') + length = len(addr_list) + if length > 6: + return False + byte_len = 16 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + + if self.aftype == "v4": + for i in range(ip_len + 1, length): + addr_list[i] = 0 + else: + for i in range(length - ip_len, length): + addr_list[i] = 0 + for j in range(0, byte_len - ip_bit): + if self.aftype == "v4": + addr_list[ip_len] = int(addr_list[ip_len]) & (0 << j) + else: + if addr_list[length - ip_len - 1] == "": + continue + addr_list[length - ip_len - + 1] = '0x%s' % addr_list[length - ip_len - 1] + addr_list[length - ip_len - + 1] = int(addr_list[length - ip_len - 1], 16) & (0 << j) + + if self.aftype == "v4": + self.prefix = '%s.%s.%s.%s' % (addr_list[0], addr_list[1], addr_list[2], addr_list[3]) + return True + else: + ipv6_addr_str = "" + for num in range(0, length - ip_len): + ipv6_addr_str += '%s:' % addr_list[num] + self.prefix = ipv6_addr_str + return True + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + if self.aftype == "v4": + aftype = "ip" + maskstr = self.convert_len_to_mask(self.mask) + else: + aftype = "ipv6" + maskstr = self.mask + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + if self.vrf == "_public_": + vrf = '' + else: + vrf = self.vrf + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.state == "present": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('%s route-static vpn-instance %s %s %s vpn-instance %s %s' + % (aftype, vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('%s route-static vpn-instance %s %s %s %s %s' + % (aftype, vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('%s route-static %s %s vpn-instance %s %s' + % (aftype, self.prefix, maskstr, self.destvrf, next_hop)) + else: + self.updates_cmd.append('%s route-static %s %s %s %s' + % (aftype, self.prefix, maskstr, nhp_interface, next_hop)) + if self.pref: + self.updates_cmd[0] += ' preference %s' % (self.pref) + if self.tag: + self.updates_cmd[0] += ' tag %s' % (self.tag) + if self.description: + self.updates_cmd[0] += ' description %s' % (self.description) + + if self.state == "absent": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('undo %s route-static vpn-instance %s %s %s vpn-instance %s %s' + % (aftype, vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('undo %s route-static vpn-instance %s %s %s %s %s' + % (aftype, vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('undo %s route-static %s %s vpn-instance %s %s' + % (aftype, self.prefix, maskstr, self.destvrf, self.next_hop)) + else: + self.updates_cmd.append('undo %s route-static %s %s %s %s' + % (aftype, self.prefix, maskstr, nhp_interface, next_hop)) + + def operate_static_route(self, version, prefix, mask, nhp_interface, next_hop, vrf, destvrf, state): + """operate ipv4 static route""" + + description_xml = """\n""" + preference_xml = """\n""" + tag_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + if nhp_interface is None: + nhp_interface = "Invalid0" + + if vrf is None: + vpn_instance = "_public_" + else: + vpn_instance = vrf + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + if self.description: + description_xml = CE_NC_SET_DESCRIPTION % self.description + if self.pref: + preference_xml = CE_NC_SET_PREFERENCE % self.pref + if self.tag: + tag_xml = CE_NC_SET_TAG % self.tag + + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, + dest_vpn_instance, next_hop, description_xml, preference_xml, tag_xml) + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_STATIC_ROUTE") + + def get_static_route(self, state): + """get ipv4 static route""" + + self.static_routes_info["sroute"] = list() + + if state == 'absent': + getxmlstr = CE_NC_GET_STATIC_ROUTE_ABSENT + else: + getxmlstr = CE_NC_GET_STATIC_ROUTE + + xml_str = get_nc_config(self.module, getxmlstr) + + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + static_routes = root.findall( + "staticrt/staticrtbase/srRoutes/srRoute") + + if static_routes: + for static_route in static_routes: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["vrfName", "afType", "topologyName", + "prefix", "maskLength", "destVrfName", + "nexthop", "ifName", "preference", "description"]: + static_info[ + static_ele.tag] = static_ele.text + if static_ele.tag == "tag": + if static_ele.text is not None: + static_info["tag"] = static_ele.text + else: + static_info["tag"] = "None" + self.static_routes_info["sroute"].append(static_info) + + def check_params(self): + """check all input params""" + + # check prefix and mask + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: Mask is invalid.') + # ipv4 check + if self.aftype == "v4": + if int(self.mask) > 32 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv4 mask must be an integer between 1 and 32.') + # next_hop check + if self.next_hop: + if not is_valid_v4addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.next_hop) + # ipv6 check + if self.aftype == "v6": + if int(self.mask) > 128 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv6 mask must be an integer between 1 and 128.') + if self.next_hop: + if not is_valid_v6addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.next_hop) + + # description check + if self.description: + if not is_valid_description(self.description): + self.module.fail_json( + msg='Error: Dsecription length should be 1 - 35, and can not contain "?".') + # tag check + if self.tag: + if not is_valid_tag(self.tag): + self.module.fail_json( + msg='Error: Tag should be integer 1 - 4294967295.') + # preference check + if self.pref: + if not is_valid_preference(self.pref): + self.module.fail_json( + msg='Error: Preference should be integer 1 - 255.') + if self.nhp_interface != "Invalid0" and self.destvrf != "_public_": + self.module.fail_json( + msg='Error: Destination vrf dose no support next hop is interface.') + # convert prefix + if not self.convert_ip_prefix(): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.prefix) + + def set_ip_static_route(self): + """set ip static route""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route(version, self.prefix, self.mask, self.nhp_interface, + self.next_hop, self.vrf, self.destvrf, self.state) + + def is_prefix_exist(self, static_route, version): + """is prefix mask nex_thop exist""" + if static_route is None: + return False + if self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if self.next_hop and not self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if not self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() + + def get_ip_static_route(self): + """get ip static route""" + + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + change = False + self.get_static_route(self.state) + if self.state == 'present': + for static_route in self.static_routes_info["sroute"]: + if self.is_prefix_exist(static_route, version): + if self.vrf: + if static_route["vrfName"] != self.vrf: + change = True + if self.tag: + if static_route["tag"] != self.tag: + change = True + if self.destvrf: + if static_route["destVrfName"] != self.destvrf: + change = True + if self.description: + if static_route["description"] != self.description: + change = True + if self.pref: + if static_route["preference"] != self.pref: + change = True + if self.nhp_interface: + if static_route["ifName"].lower() != self.nhp_interface.lower(): + change = True + if self.next_hop: + if static_route["nexthop"].lower() != self.next_hop.lower(): + change = True + return change + else: + continue + change = True + else: + for static_route in self.static_routes_info["sroute"]: + if static_route["nexthop"] and self.next_hop: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + if static_route["ifName"] and self.nhp_interface: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_proposed(self): + """get proposed information""" + + self.proposed['prefix'] = self.prefix + self.proposed['mask'] = self.mask + self.proposed['afType'] = self.aftype + self.proposed['next_hop'] = self.next_hop + self.proposed['ifName'] = self.nhp_interface + self.proposed['vrfName'] = self.vrf + self.proposed['destVrfName'] = self.destvrf + if self.tag: + self.proposed['tag'] = self.tag + if self.description: + self.proposed['description'] = self.description + if self.pref is None: + self.proposed['preference'] = 60 + else: + self.proposed['preference'] = self.pref + self.proposed['state'] = self.state + + def get_existing(self): + """get existing information""" + + change = self.get_ip_static_route() + self.existing['sroute'] = self.static_routes_info["sroute"] + self.changed = bool(change) + + def get_end_state(self): + """get end state information""" + + self.get_static_route(self.state) + self.end_state['sroute'] = self.static_routes_info["sroute"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.set_ip_static_route() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + prefix=dict(required=True, type='str'), + mask=dict(required=True, type='str'), + aftype=dict(choices=['v4', 'v6'], required=True), + next_hop=dict(required=False, type='str'), + nhp_interface=dict(required=False, type='str'), + vrf=dict(required=False, type='str'), + destvrf=dict(required=False, type='str'), + tag=dict(required=False, type='str'), + description=dict(required=False, type='str'), + pref=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = StaticRoute(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route_bfd.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route_bfd.py new file mode 100644 index 00000000..a35f05ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_static_route_bfd.py @@ -0,0 +1,1593 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_static_route_bfd +version_added: '0.2.0' +short_description: Manages static route configuration on HUAWEI CloudEngine switches. +description: + - Manages the static routes on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route configuration will be removed, regardless of the non-required parameters. +options: + prefix: + description: + - Destination ip address of static route. + required: true + type: str + mask: + description: + - Destination ip mask of static route. + type: str + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + next_hop: + description: + - Next hop address of static route. + type: str + nhp_interface: + description: + - Next hop interface full name of static route. + type: str + vrf: + description: + - VPN instance of destination ip address. + type: str + destvrf: + description: + - VPN instance of next hop ip address. + type: str + tag: + description: + - Route tag value (numeric). + type: int + description: + description: + - Name of the route. Used with the name parameter on the CLI. + type: str + pref: + description: + - Preference or administrative difference of route (range 1-255). + type: int + function_flag: + description: + - Used to distinguish between command line functions. + required: true + choices: ['globalBFD','singleBFD','dynamicBFD','staticBFD'] + type: str + min_tx_interval: + description: + - Set the minimum BFD session sending interval (range 50-1000). + type: int + min_rx_interval: + description: + - Set the minimum BFD receive interval (range 50-1000). + type: int + detect_multiplier: + description: + - Configure the BFD multiplier (range 3-50). + type: int + bfd_session_name: + description: + - bfd name (range 1-15). + type: str + commands: + description: + - Incoming command line is used to send sys,undo ip route-static default-bfd,commit. + type: list + state: + description: + - Specify desired state of the resource. + required: false + choices: ['present','absent'] + type: str + default: present +''' + +EXAMPLES = ''' + #ip route-static bfd interface-type interface-number nexthop-address [ local-address address ] + #[ min-rx-interval min-rx-interval | min-tx-interval min-tx-interval | detect-multiplier multiplier ] + - name: Config an ip route-static bfd 10GE1/0/1 3.3.3.3 min-rx-interval 50 min-tx-interval 50 detect-multiplier 5 + community.network.ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.3 + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 5 + aftype: v4 + state: present + + #undo ip route-static bfd [ interface-type interface-number | vpn-instance vpn-instance-name ] nexthop-address + - name: Undo ip route-static bfd 10GE1/0/1 3.3.3.4 + community.network.ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.4 + aftype: v4 + state: absent + + #ip route-static default-bfd { min-rx-interval {min-rx-interval} | min-tx-interval {min-tx-interval} | detect-multiplier {multiplier}} + - name: Config an ip route-static default-bfd min-rx-interval 50 min-tx-interval 50 detect-multiplier 6 + community.network.ce_static_route_bfd: + function_flag: 'globalBFD' + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 6 + aftype: v4 + state: present + + - name: Undo ip route-static default-bfd + community.network.ce_static_route_bfd: + function_flag: 'globalBFD' + aftype: v4 + state: absent + commands: 'sys,undo ip route-static default-bfd,commit' + + - name: Config an ipv4 static route 2.2.2.0/24 2.2.2.1 preference 1 tag 2 description test for staticBFD + community.network.ce_static_route_bfd: + function_flag: 'staticBFD' + prefix: 2.2.2.2 + mask: 24 + next_hop: 2.2.2.1 + tag: 2 + description: test + pref: 1 + aftype: v4 + bfd_session_name: btoa + state: present +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.642", "mask": "24", "description": "testing", + "vrf": "_public_", "bfd_session_name": "btoa"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"function_flag": "", "next_hop": "", "pref": "101", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip route-static 192.168.20.0 255.255.255.0 3.3.3.3 preference 100 description testing"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME = """ + + + + + + + + + + + + + + + + + + + + + + +""" +# bfd enable +CE_NC_GET_STATIC_ROUTE_BFD_ENABLE = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD_ABSENT = """ + + + + + + %s + %s + %s + %s + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD = """ + + + + + + %s + %s + %s + %s + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD = """ + + + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_ABSENT = """ + + + + + + + + + + + + + + + + + + +""" + +CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s%s%s%s%s + + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD = """ + + + + %s%s%s + + + +""" + +CE_NC_SET_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s%s%s%s%s + + + + +""" +CE_NC_SET_DESCRIPTION = """ +%s +""" + +CE_NC_SET_PREFERENCE = """ +%s +""" + +CE_NC_SET_TAG = """ +%s +""" +CE_NC_SET_BFDSESSIONNAME = """ +%s +""" +CE_NC_SET_BFDENABLE = """ +true +""" +CE_NC_DELETE_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_v4addr(addr): + """check if ipv4 addr is valid""" + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + +def is_valid_v6addr(addr): + """check if ipv6 addr is valid""" + if addr.find(':') != -1: + addr_list = addr.split(':') + if len(addr_list) > 6: + return False + if addr_list[1] == "": + return False + return True + return False + + +def is_valid_tag(tag): + """check if the tag is valid""" + + if int(tag) < 1 or int(tag) > 4294967295: + return False + return True + + +def is_valid_bdf_interval(interval): + """check if the min_tx_interva,min-rx-interval is valid""" + + if interval < 50 or interval > 1000: + return False + return True + + +def is_valid_bdf_multiplier(multiplier): + """check if the detect_multiplier is valid""" + + if multiplier < 3 or multiplier > 50: + return False + return True + + +def is_valid_bdf_session_name(session_name): + """check if the bfd_session_name is valid""" + if session_name.find(' ') != -1: + return False + if len(session_name) < 1 or len(session_name) > 15: + return False + return True + + +def is_valid_preference(pref): + """check if the preference is valid""" + + if int(pref) > 0 and int(pref) < 256: + return True + return False + + +def is_valid_description(description): + """check if the description is valid""" + if description.find('?') != -1: + return False + if len(description) < 1 or len(description) > 255: + return False + return True + + +def compare_command(commands): + """check if the commands is valid""" + if len(commands) < 3: + return True + if commands[0] != 'sys' or commands[1] != 'undo ip route-static default-bfd' \ + or commands[2] != 'commit': + return True + + +def get_to_lines(stdout): + """data conversion""" + lines = list() + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + lines.append(item) + return lines + + +def get_change_state(oldvalue, newvalue, change): + """get change state""" + if newvalue is not None: + if oldvalue != str(newvalue): + change = True + else: + if oldvalue != newvalue: + change = True + return change + + +def get_xml(xml, value): + """operate xml""" + if value is None: + value = '' + else: + value = value + tempxml = xml % value + return tempxml + + +class StaticRouteBFD(object): + """static route module""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self._initmodule_() + + # static route info + self.function_flag = self.module.params['function_flag'] + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.version = "ipv4unicast" + else: + self.version = "ipv6unicast" + if self.function_flag != 'globalBFD': + self.nhp_interface = self.module.params['nhp_interface'] + if self.nhp_interface is None: + self.nhp_interface = "Invalid0" + + self.destvrf = self.module.params['destvrf'] + if self.destvrf is None: + self.destvrf = "_public_" + + self.next_hop = self.module.params['next_hop'] + self.prefix = self.module.params['prefix'] + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.mask = self.module.params['mask'] + self.tag = self.module.params['tag'] + self.description = self.module.params['description'] + self.pref = self.module.params['pref'] + if self.pref is None: + self.pref = 60 + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + # bfd session name + self.bfd_session_name = self.module.params['bfd_session_name'] + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.min_tx_interval = self.module.params['min_tx_interval'] + self.min_rx_interval = self.module.params['min_rx_interval'] + self.detect_multiplier = self.module.params['detect_multiplier'] + if self.function_flag == 'globalBFD' and self.state == 'absent': + self.commands = self.module.params['commands'] + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.static_routes_info = dict() + + def _initmodule_(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def _convertlentomask_(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def _convertipprefix_(self): + """convert prefix to real value i.e. 2.2.2.2/24 to 2.2.2.0/24""" + if self.function_flag == 'singleBFD': + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + else: + if self.prefix.find(':') == -1: + return False + else: + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + if self.mask == '32': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '0.0.0.0' + return True + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + byte_len = 8 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + else: + if self.prefix.find(':') == -1: + return False + if self.mask == '128': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '::' + return True + addr_list = self.prefix.split(':') + length = len(addr_list) + if length > 6: + return False + byte_len = 16 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + + if self.aftype == "v4": + for i in range(ip_len + 1, length): + addr_list[i] = 0 + else: + for i in range(length - ip_len, length): + addr_list[i] = 0 + for j in range(0, byte_len - ip_bit): + if self.aftype == "v4": + addr_list[ip_len] = int(addr_list[ip_len]) & (0 << j) + else: + if addr_list[length - ip_len - 1] == "": + continue + addr_list[length - ip_len - + 1] = '0x%s' % addr_list[length - ip_len - 1] + addr_list[length - ip_len - + 1] = int(addr_list[length - ip_len - 1], 16) & (0 << j) + + if self.aftype == "v4": + self.prefix = '%s.%s.%s.%s' % (addr_list[0], addr_list[1], addr_list[2], addr_list[3]) + return True + if self.aftype == "v6": + ipv6_addr_str = "" + for num in range(0, length - ip_len): + ipv6_addr_str += '%s:' % addr_list[num] + self.prefix = ipv6_addr_str + + return True + + def set_update_cmd_globalbfd(self): + """set globalBFD update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('ip route-static default-bfd') + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + self.updates_cmd.append('undo ip route-static default-bfd') + + def set_update_cmd_singlebfd(self): + """set singleBFD update command""" + if not self.changed: + return + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.prefix == "0.0.0.0": + prefix = '' + else: + prefix = self.prefix + if self.state == "present": + if nhp_interface: + self.updates_cmd.append('ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static bfd %s' % (next_hop)) + if prefix: + self.updates_cmd.append(' local-address %s' % (self.prefix)) + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + if nhp_interface: + self.updates_cmd.append('undo ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('undo ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static bfd %s' % (next_hop)) + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + + if self.aftype == "v4": + maskstr = self._convertlentomask_(self.mask) + else: + maskstr = self.mask + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + if self.vrf == "_public_": + vrf = '' + else: + vrf = self.vrf + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.state == "present": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + if self.pref != 60: + self.updates_cmd.append(' preference %s' % (self.pref)) + if self.tag: + self.updates_cmd.append(' tag %s' % (self.tag)) + if not static_bfd_flag: + self.updates_cmd.append(' track bfd-session %s' % (self.bfd_session_name)) + else: + self.updates_cmd.append(' bfd enable') + if self.description: + self.updates_cmd.append(' description %s' % (self.description)) + + if self.state == "absent": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, self.next_hop)) + else: + self.updates_cmd.append('undo ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + + def operate_static_route_globalbfd(self): + """set globalbfd update command""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + if self.state == "present": + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + if self.state == "absent" and self.commands: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % 1000 + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % 1000 + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % 3 + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + def operate_static_route_singlebfd(self, version, prefix, nhp_interface, next_hop, destvrf, state): + """operate ipv4 static route singleBFD""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + local_address_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + if nhp_interface is None: + nhp_interface = "Invalid0" + + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + if prefix is not None: + local_address_xml = CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS % prefix + + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, + next_hop, local_address_xml, min_tx_interval_xml, + min_rx_interval_xml, multiplier_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_singleBFD") + + def operate_static_route(self, version, prefix, mask, nhp_interface, next_hop, vrf, destvrf, state): + """operate ipv4 static route""" + description_xml = """\n""" + preference_xml = """\n""" + tag_xml = """\n""" + bfd_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + if nhp_interface is None: + nhp_interface = "Invalid0" + + if vrf is None: + vpn_instance = "_public_" + else: + vpn_instance = vrf + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + description_xml = get_xml(CE_NC_SET_DESCRIPTION, self.description) + + preference_xml = get_xml(CE_NC_SET_PREFERENCE, self.pref) + + tag_xml = get_xml(CE_NC_SET_TAG, self.tag) + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + bfd_xml = CE_NC_SET_BFDSESSIONNAME % self.bfd_session_name + else: + bfd_xml = CE_NC_SET_BFDENABLE + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, + dest_vpn_instance, next_hop, description_xml, preference_xml, tag_xml, bfd_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE") + + def get_change_state_global_bfd(self): + """get ipv4 global bfd change state""" + + self.get_global_bfd(self.state) + change = False + if self.state == "present": + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if self.min_tx_interval is not None: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval is not None: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier is not None: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + else: + continue + else: + change = True + else: + if self.commands: + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if int(static_route["minTxInterval"]) != 1000 or \ + int(static_route["minRxInterval"]) != 1000 or \ + int(static_route["multiplier"]) != 3: + change = True + return change + + def get_global_bfd(self, state): + """get ipv4 global bfd""" + + self.static_routes_info["sroute_global_bfd"] = list() + + getglobalbfdxmlstr = None + if self.aftype == 'v4': + getglobalbfdxmlstr = CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD + + if getglobalbfdxmlstr is not None: + xml_global_bfd_str = get_nc_config(self.module, getglobalbfdxmlstr) + + if 'data/' in xml_global_bfd_str: + return + + xml_global_bfd_str = xml_global_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_global_bfd_str) + static_routes_global_bfd = root.findall( + "staticrt/staticrtbase/srIPv4StaticSite") + + if static_routes_global_bfd: + for static_route in static_routes_global_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + + self.static_routes_info["sroute_global_bfd"].append(static_info) + + def get_change_state_single_bfd(self): + """get ipv4 single bfd change state""" + + self.get_single_bfd(self.state) + change = False + version = self.version + if self.state == 'present': + if self.static_routes_info["sroute_single_bfd"]: + for static_route in self.static_routes_info["sroute_single_bfd"]: + if static_route is not None and static_route['afType'] == version: + if self.nhp_interface: + if static_route["ifName"].lower() != self.nhp_interface.lower(): + change = True + if self.destvrf: + if static_route["destVrfName"].lower() != self.destvrf.lower(): + change = True + if self.next_hop: + if static_route["nexthop"].lower() != self.next_hop.lower(): + change = True + if self.prefix: + if static_route["localAddress"].lower() != self.prefix.lower(): + change = True + if self.min_tx_interval: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + + else: + continue + else: + change = True + else: + for static_route in self.static_routes_info["sroute_single_bfd"]: + # undo ip route-static bfd [ interface-type interface-number | + # vpn-instance vpn-instance-name ] nexthop-address + + if static_route["ifName"] and self.nhp_interface: + if static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["destVrfName"] and self.destvrf: + if static_route["destVrfName"].lower() == self.destvrf.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["nexthop"] and self.next_hop: + if static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_single_bfd(self, state): + """get ipv4 sigle bfd""" + self.static_routes_info["sroute_single_bfd"] = list() + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + if state == 'absent': + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ABSENT % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + else: + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + xml_bfd_str = get_nc_config(self.module, getbfdxmlstr) + + if 'data/' in xml_bfd_str: + return + xml_bfd_str = xml_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_bfd_str) + static_routes_bfd = root.findall( + "staticrt/staticrtbase/srBfdParas/srBfdPara") + if static_routes_bfd: + for static_route in static_routes_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["afType", "destVrfName", "nexthop", "ifName"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "localAddress": + if static_ele.text is not None: + static_info["localAddress"] = static_ele.text + else: + static_info["localAddress"] = "None" + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + self.static_routes_info["sroute_single_bfd"].append(static_info) + + def get_static_route(self, state): + """get ipv4 static route about BFD""" + self.static_routes_info["sroute"] = list() + # Increase the parameter used to distinguish whether the incoming bfdSessionName + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + + if state == 'absent': + getxmlstr = CE_NC_GET_STATIC_ROUTE_ABSENT + else: + # self.static_bfd_flag is true + if static_bfd_flag: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ENABLE + + else: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + static_routes = root.findall( + "staticrt/staticrtbase/srRoutes/srRoute") + + if static_routes: + for static_route in static_routes: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["vrfName", "afType", "topologyName", + "prefix", "maskLength", "destVrfName", + "nexthop", "ifName", "preference", "description"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "tag": + if static_ele.text is not None: + static_info["tag"] = static_ele.text + else: + static_info["tag"] = "None" + if static_bfd_flag: + if static_ele.tag == "bfdEnable": + if static_ele.text is not None: + static_info["bfdEnable"] = static_ele.text + else: + static_info["bfdEnable"] = "None" + else: + if static_ele.tag == "sessionName": + if static_ele.text is not None: + static_info["sessionName"] = static_ele.text + else: + static_info["sessionName"] = "None" + self.static_routes_info["sroute"].append(static_info) + + def _checkparams_(self): + """check all input params""" + if self.function_flag == 'singleBFD': + if not self.next_hop: + self.module.fail_json(msg='Error: missing required argument: next_hop.') + if self.state != 'absent': + if self.nhp_interface == "Invalid0" and (not self.prefix or self.prefix == '0.0.0.0'): + self.module.fail_json(msg='Error: If a nhp_interface is not configured, ' + 'the prefix must be configured.') + + if self.function_flag != 'globalBFD': + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if not self.mask: + self.module.fail_json(msg='Error: missing required argument: mask.') + # check prefix and mask + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: Mask is invalid.') + if self.function_flag != 'singleBFD' or (self.function_flag == 'singleBFD' and self.destvrf != "_public_"): + if not self.prefix: + self.module.fail_json(msg='Error: missing required argument: prefix.') + # convert prefix + if not self._convertipprefix_(): + self.module.fail_json(msg='Error: The %s is not a valid address' % self.prefix) + + if self.nhp_interface != "Invalid0" and self.destvrf != "_public_": + self.module.fail_json(msg='Error: Destination vrf dose not support next hop is interface.') + + if not self.next_hop and self.nhp_interface == "Invalid0": + self.module.fail_json(msg='Error: one of the following is required: next_hop,nhp_interface.') + + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + # description check + if self.description: + if not is_valid_description(self.description): + self.module.fail_json( + msg='Error: Dsecription length should be 1 - 35, and can not contain "?".') + # tag check + if self.tag is not None: + if not is_valid_tag(self.tag): + self.module.fail_json( + msg='Error: Tag should be integer 1 - 4294967295.') + # preference check + if self.pref is not None: + if not is_valid_preference(self.pref): + self.module.fail_json( + msg='Error: Preference should be integer 1 - 255.') + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + if not is_valid_bdf_session_name(self.bfd_session_name): + self.module.fail_json( + msg='Error: bfd_session_name length should be 1 - 15, and can not contain Space.') + + # ipv4 check + if self.aftype == "v4": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 32 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv4 mask must be an integer between 1 and 32.') + # next_hop check + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v4addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + # ipv6 check + if self.aftype == "v6": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 128 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv6 mask must be an integer between 1 and 128.') + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v6addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + # BFD prarams + if self.min_tx_interval: + if not is_valid_bdf_interval(self.min_tx_interval): + self.module.fail_json( + msg='Error: min_tx_interval should be integer 50 - 1000.') + if self.min_rx_interval: + if not is_valid_bdf_interval(self.min_rx_interval): + self.module.fail_json( + msg='Error: min_rx_interval should be integer 50 - 1000.') + if self.detect_multiplier: + if not is_valid_bdf_multiplier(self.detect_multiplier): + self.module.fail_json( + msg='Error: detect_multiplier should be integer 3 - 50.') + + if self.function_flag == 'globalBFD': + if self.state != 'absent': + if not self.min_tx_interval and not self.min_rx_interval and not self.detect_multiplier: + self.module.fail_json( + msg='Error: one of the following is required: min_tx_interval,' + 'detect_multiplier,min_rx_interval.') + else: + if not self.commands: + self.module.fail_json( + msg='Error: missing required argument: command.') + if compare_command(self.commands): + self.module.fail_json( + msg='Error: The command %s line is incorrect.' % (',').join(self.commands)) + + def set_ip_static_route_globalbfd(self): + """set ip static route globalBFD""" + if not self.changed: + return + if self.aftype == "v4": + self.operate_static_route_globalbfd() + + def set_ip_static_route_singlebfd(self): + """set ip static route singleBFD""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route_singlebfd(version, self.prefix, self.nhp_interface, + self.next_hop, self.destvrf, self.state) + + def set_ip_static_route(self): + """set ip static route""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route(version, self.prefix, self.mask, self.nhp_interface, + self.next_hop, self.vrf, self.destvrf, self.state) + + def is_prefix_exist(self, static_route, version): + """is prefix mask nex_thop exist""" + if static_route is None: + return False + if self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if self.next_hop and not self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if not self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() + + def get_ip_static_route(self): + """get ip static route""" + change = False + version = self.version + self.get_static_route(self.state) + change_list = list() + if self.state == 'present': + for static_route in self.static_routes_info["sroute"]: + if self.is_prefix_exist(static_route, self.version): + info_dict = dict() + exist_dict = dict() + if self.vrf: + info_dict["vrfName"] = self.vrf + exist_dict["vrfName"] = static_route["vrfName"] + if self.destvrf: + info_dict["destVrfName"] = self.destvrf + exist_dict["destVrfName"] = static_route["destVrfName"] + if self.description: + info_dict["description"] = self.description + exist_dict["description"] = static_route["description"] + if self.tag: + info_dict["tag"] = self.tag + exist_dict["tag"] = static_route["tag"] + if self.pref: + info_dict["preference"] = str(self.pref) + exist_dict["preference"] = static_route["preference"] + if self.nhp_interface: + if self.nhp_interface.lower() == "invalid0": + info_dict["ifName"] = "Invalid0" + else: + info_dict["ifName"] = "Invalid0" + exist_dict["ifName"] = static_route["ifName"] + if self.next_hop: + info_dict["nexthop"] = self.next_hop + exist_dict["nexthop"] = static_route["nexthop"] + + if self.bfd_session_name: + info_dict["bfdEnable"] = 'true' + + else: + info_dict["bfdEnable"] = 'false' + exist_dict["bfdEnable"] = static_route["bfdEnable"] + + if exist_dict != info_dict: + change = True + else: + change = False + change_list.append(change) + + if False in change_list: + change = False + else: + change = True + return change + + else: + for static_route in self.static_routes_info["sroute"]: + if static_route["nexthop"] and self.next_hop: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + if static_route["ifName"] and self.nhp_interface: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_proposed(self): + """get proposed information""" + self.proposed['afType'] = self.aftype + self.proposed['state'] = self.state + if self.function_flag != 'globalBFD': + self.proposed['ifName'] = self.nhp_interface + self.proposed['destVrfName'] = self.destvrf + self.proposed['next_hop'] = self.next_hop + + if self.function_flag == 'singleBFD': + if self.prefix: + self.proposed['localAddress'] = self.prefix + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.proposed['minTxInterval'] = self.min_tx_interval + self.proposed['minRxInterval'] = self.min_rx_interval + self.proposed['multiplier'] = self.detect_multiplier + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.proposed['prefix'] = self.prefix + self.proposed['mask'] = self.mask + self.proposed['vrfName'] = self.vrf + if self.tag: + self.proposed['tag'] = self.tag + if self.description: + self.proposed['description'] = self.description + if self.pref is None: + self.proposed['preference'] = 60 + else: + self.proposed['preference'] = self.pref + + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if not static_bfd_flag: + self.proposed['sessionName'] = self.bfd_session_name + else: + self.proposed['bfdEnable'] = 'true' + + def get_existing(self): + """get existing information""" + # globalBFD + if self.function_flag == 'globalBFD': + change = self.get_change_state_global_bfd() + self.existing['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + change = self.get_change_state_single_bfd() + self.existing['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + change = self.get_ip_static_route() + self.existing['static_sroute'] = self.static_routes_info["sroute"] + self.changed = bool(change) + + def get_end_state(self): + """get end state information""" + + # globalBFD + if self.function_flag == 'globalBFD': + self.get_global_bfd(self.state) + self.end_state['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + self.static_routes_info["sroute_single_bfd"] = list() + self.get_single_bfd(self.state) + self.end_state['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + self.get_static_route(self.state) + self.end_state['static_sroute'] = self.static_routes_info["sroute"] + + def work(self): + """worker""" + self._checkparams_() + self.get_existing() + self.get_proposed() + + if self.function_flag == 'globalBFD': + self.set_ip_static_route_globalbfd() + self.set_update_cmd_globalbfd() + elif self.function_flag == 'singleBFD': + self.set_ip_static_route_singlebfd() + self.set_update_cmd_singlebfd() + else: + self.set_ip_static_route() + self.set_update_cmd() + + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + aftype=dict(choices=['v4', 'v6'], required=True), + next_hop=dict(type='str'), + nhp_interface=dict(type='str'), + vrf=dict(type='str'), + destvrf=dict(type='str'), + tag=dict(type='int'), + description=dict(type='str'), + pref=dict(type='int'), + # bfd + function_flag=dict(required=True, choices=['globalBFD', 'singleBFD', 'dynamicBFD', 'staticBFD']), + min_tx_interval=dict(type='int'), + min_rx_interval=dict(type='int'), + detect_multiplier=dict(type='int'), + # bfd session name + bfd_session_name=dict(type='str'), + commands=dict(type='list', required=False), + state=dict(choices=['absent', 'present'], default='present', required=False), + ) + interface = StaticRouteBFD(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_stp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_stp.py new file mode 100644 index 00000000..0f3d67ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_stp.py @@ -0,0 +1,969 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_stp +short_description: Manages STP configuration on HUAWEI CloudEngine switches. +description: + - Manages STP configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + stp_mode: + description: + - Set an operation mode for the current MSTP process. + The mode can be STP, RSTP, or MSTP. + choices: ['stp', 'rstp', 'mstp'] + stp_enable: + description: + - Enable or disable STP on a switch. + choices: ['enable', 'disable'] + stp_converge: + description: + - STP convergence mode. + Fast means set STP aging mode to Fast. + Normal means set STP aging mode to Normal. + choices: ['fast', 'normal'] + bpdu_protection: + description: + - Configure BPDU protection on an edge port. + This function prevents network flapping caused by attack packets. + choices: ['enable', 'disable'] + tc_protection: + description: + - Configure the TC BPDU protection function for an MSTP process. + choices: ['enable', 'disable'] + tc_protection_interval: + description: + - Set the time the MSTP device takes to handle the maximum number of TC BPDUs + and immediately refresh forwarding entries. + The value is an integer ranging from 1 to 600, in seconds. + tc_protection_threshold: + description: + - Set the maximum number of TC BPDUs that the MSTP can handle. + The value is an integer ranging from 1 to 255. The default value is 1 on the switch. + interface: + description: + - Interface name. + If the value is C(all), will apply configuration to all interfaces. + if the value is a special name, only support input the full name. + edged_port: + description: + - Set the current port as an edge port. + choices: ['enable', 'disable'] + bpdu_filter: + description: + - Specify a port as a BPDU filter port. + choices: ['enable', 'disable'] + cost: + description: + - Set the path cost of the current port. + The default instance is 0. + root_protection: + description: + - Enable root protection on the current port. + choices: ['enable', 'disable'] + loop_protection: + description: + - Enable loop protection on the current port. + choices: ['enable', 'disable'] +''' + +EXAMPLES = ''' + +- name: CloudEngine stp test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config stp mode" + community.network.ce_stp: + state: present + stp_mode: stp + provider: "{{ cli }}" + + - name: "Undo stp mode" + community.network.ce_stp: + state: absent + stp_mode: stp + provider: "{{ cli }}" + + - name: "Enable bpdu protection" + community.network.ce_stp: + state: present + bpdu_protection: enable + provider: "{{ cli }}" + + - name: "Disable bpdu protection" + community.network.ce_stp: + state: present + bpdu_protection: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"bpdu_protection": "enable", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bpdu_protection": "disable"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bpdu_protection": "enable"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["stp bpdu-protection"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache""" + + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg=err) + config = str(out).strip() + if config.startswith("display"): + configs = config.split("\n") + if len(configs) > 1: + return "\n".join(configs[1:]) + else: + return "" + else: + return config + + +class Stp(object): + """ Manages stp/rstp/mstp configuration """ + + def __init__(self, **kwargs): + """ Stp module init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + self.stp_cfg = None + self.interface_stp_cfg = None + + # module args + self.state = self.module.params['state'] or None + self.stp_mode = self.module.params['stp_mode'] or None + self.stp_enable = self.module.params['stp_enable'] or None + self.stp_converge = self.module.params['stp_converge'] or None + self.interface = self.module.params['interface'] or None + self.edged_port = self.module.params['edged_port'] or None + self.bpdu_filter = self.module.params['bpdu_filter'] or None + self.cost = self.module.params['cost'] or None + self.bpdu_protection = self.module.params['bpdu_protection'] or None + self.tc_protection = self.module.params['tc_protection'] or None + self.tc_protection_interval = self.module.params['tc_protection_interval'] or None + self.tc_protection_threshold = self.module.params['tc_protection_threshold'] or None + self.root_protection = self.module.params['root_protection'] or None + self.loop_protection = self.module.params['loop_protection'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def cli_load_config(self, commands): + """ Cli load configuration """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_stp_config(self): + """ Cli get stp configuration """ + + flags = [r"| section include #\s*\n\s*stp", r"| section exclude #\s*\n+\s*stp process \d+"] + self.stp_cfg = get_config(self.module, flags) + + def cli_get_interface_stp_config(self): + """ Cli get interface's stp configuration """ + + if self.interface: + regular = r"| ignore-case section include ^#\s+interface %s\s+" % self.interface.replace(" ", "") + flags = list() + flags.append(regular) + tmp_cfg = get_config(self.module, flags) + + if not tmp_cfg: + self.module.fail_json( + msg='Error: The interface %s is not exist.' % self.interface) + + if "undo portswitch" in tmp_cfg: + self.module.fail_json( + msg='Error: The interface %s is not switch mode.' % self.interface) + + self.interface_stp_cfg = tmp_cfg + + def check_params(self): + """ Check module params """ + + if self.cost: + if self.cost.isdigit(): + if int(self.cost) < 1 or int(self.cost) > 200000000: + self.module.fail_json( + msg='Error: The value of cost is out of [1 - 200000000].') + else: + self.module.fail_json( + msg='Error: The cost is not digit.') + + if self.tc_protection_interval: + if self.tc_protection_interval.isdigit(): + if int(self.tc_protection_interval) < 1 or int(self.tc_protection_interval) > 600: + self.module.fail_json( + msg='Error: The value of tc_protection_interval is out of [1 - 600].') + else: + self.module.fail_json( + msg='Error: The tc_protection_interval is not digit.') + + if self.tc_protection_threshold: + if self.tc_protection_threshold.isdigit(): + if int(self.tc_protection_threshold) < 1 or int(self.tc_protection_threshold) > 255: + self.module.fail_json( + msg='Error: The value of tc_protection_threshold is out of [1 - 255].') + else: + self.module.fail_json( + msg='Error: The tc_protection_threshold is not digit.') + + if self.root_protection or self.loop_protection or self.cost: + if not self.interface: + self.module.fail_json( + msg='Error: Please input interface.') + elif self.interface == "all": + self.module.fail_json( + msg='Error: Interface can not be all when config root_protection or loop_protection or cost.') + + if self.root_protection and self.root_protection == "enable": + if self.loop_protection and self.loop_protection == "enable": + self.module.fail_json( + msg='Error: Can not enable root_protection and loop_protection at the same interface.') + + if self.edged_port or self.bpdu_filter: + if not self.interface: + self.module.fail_json( + msg='Error: Please input interface.') + + def get_proposed(self): + """ Get module proposed """ + + self.proposed["state"] = self.state + + if self.stp_mode: + self.proposed["stp_mode"] = self.stp_mode + if self.stp_enable: + self.proposed["stp_enable"] = self.stp_enable + if self.stp_converge: + self.proposed["stp_converge"] = self.stp_converge + if self.interface: + self.proposed["interface"] = self.interface + if self.edged_port: + self.proposed["edged_port"] = self.edged_port + if self.bpdu_filter: + self.proposed["bpdu_filter"] = self.bpdu_filter + if self.cost: + self.proposed["cost"] = self.cost + if self.bpdu_protection: + self.proposed["bpdu_protection"] = self.bpdu_protection + if self.tc_protection: + self.proposed["tc_protection"] = self.tc_protection + if self.tc_protection_interval: + self.proposed["tc_protection_interval"] = self.tc_protection_interval + if self.tc_protection_threshold: + self.proposed["tc_protection_threshold"] = self.tc_protection_threshold + if self.root_protection: + self.proposed["root_protection"] = self.root_protection + if self.loop_protection: + self.proposed["loop_protection"] = self.loop_protection + + def get_existing(self): + """ Get existing configuration """ + + self.cli_get_stp_config() + if self.interface and self.interface != "all": + self.cli_get_interface_stp_config() + + if self.stp_mode: + if "stp mode stp" in self.stp_cfg: + self.cur_cfg["stp_mode"] = "stp" + self.existing["stp_mode"] = "stp" + elif "stp mode rstp" in self.stp_cfg: + self.cur_cfg["stp_mode"] = "rstp" + self.existing["stp_mode"] = "rstp" + else: + self.cur_cfg["stp_mode"] = "mstp" + self.existing["stp_mode"] = "mstp" + + if self.stp_enable: + if "stp disable" in self.stp_cfg: + self.cur_cfg["stp_enable"] = "disable" + self.existing["stp_enable"] = "disable" + else: + self.cur_cfg["stp_enable"] = "enable" + self.existing["stp_enable"] = "enable" + + if self.stp_converge: + if "stp converge fast" in self.stp_cfg: + self.cur_cfg["stp_converge"] = "fast" + self.existing["stp_converge"] = "fast" + else: + self.cur_cfg["stp_converge"] = "normal" + self.existing["stp_converge"] = "normal" + + if self.edged_port: + if self.interface == "all": + if "stp edged-port default" in self.stp_cfg: + self.cur_cfg["edged_port"] = "enable" + self.existing["edged_port"] = "enable" + else: + self.cur_cfg["edged_port"] = "disable" + self.existing["edged_port"] = "disable" + else: + if "stp edged-port enable" in self.interface_stp_cfg: + self.cur_cfg["edged_port"] = "enable" + self.existing["edged_port"] = "enable" + else: + self.cur_cfg["edged_port"] = "disable" + self.existing["edged_port"] = "disable" + + if self.bpdu_filter: + if self.interface == "all": + if "stp bpdu-filter default" in self.stp_cfg: + self.cur_cfg["bpdu_filter"] = "enable" + self.existing["bpdu_filter"] = "enable" + else: + self.cur_cfg["bpdu_filter"] = "disable" + self.existing["bpdu_filter"] = "disable" + else: + if "stp bpdu-filter enable" in self.interface_stp_cfg: + self.cur_cfg["bpdu_filter"] = "enable" + self.existing["bpdu_filter"] = "enable" + else: + self.cur_cfg["bpdu_filter"] = "disable" + self.existing["bpdu_filter"] = "disable" + + if self.bpdu_protection: + if "stp bpdu-protection" in self.stp_cfg: + self.cur_cfg["bpdu_protection"] = "enable" + self.existing["bpdu_protection"] = "enable" + else: + self.cur_cfg["bpdu_protection"] = "disable" + self.existing["bpdu_protection"] = "disable" + + if self.tc_protection: + pre_cfg = self.stp_cfg.split("\n") + if "stp tc-protection" in pre_cfg: + self.cur_cfg["tc_protection"] = "enable" + self.existing["tc_protection"] = "enable" + else: + self.cur_cfg["tc_protection"] = "disable" + self.existing["tc_protection"] = "disable" + + if self.tc_protection_interval: + if "stp tc-protection interval" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection interval (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection interval on the device.') + self.cur_cfg["tc_protection_interval"] = tmp_value[0] + self.existing["tc_protection_interval"] = tmp_value[0] + else: + self.cur_cfg["tc_protection_interval"] = "null" + self.existing["tc_protection_interval"] = "null" + + if self.tc_protection_threshold: + if "stp tc-protection threshold" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection threshold (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection threshold on the device.') + self.cur_cfg["tc_protection_threshold"] = tmp_value[0] + self.existing["tc_protection_threshold"] = tmp_value[0] + else: + self.cur_cfg["tc_protection_threshold"] = "1" + self.existing["tc_protection_threshold"] = "1" + + if self.cost: + tmp_value = re.findall(r'stp instance (.*) cost (.*)', self.interface_stp_cfg) + if not tmp_value: + self.cur_cfg["cost"] = "null" + self.existing["cost"] = "null" + else: + self.cur_cfg["cost"] = tmp_value[0][1] + self.existing["cost"] = tmp_value[0][1] + + # root_protection and loop_protection should get configuration at the same time + if self.root_protection or self.loop_protection: + if "stp root-protection" in self.interface_stp_cfg: + self.cur_cfg["root_protection"] = "enable" + self.existing["root_protection"] = "enable" + else: + self.cur_cfg["root_protection"] = "disable" + self.existing["root_protection"] = "disable" + + if "stp loop-protection" in self.interface_stp_cfg: + self.cur_cfg["loop_protection"] = "enable" + self.existing["loop_protection"] = "enable" + else: + self.cur_cfg["loop_protection"] = "disable" + self.existing["loop_protection"] = "disable" + + def get_end_state(self): + """ Get end state """ + + self.cli_get_stp_config() + if self.interface and self.interface != "all": + self.cli_get_interface_stp_config() + + if self.stp_mode: + if "stp mode stp" in self.stp_cfg: + self.end_state["stp_mode"] = "stp" + elif "stp mode rstp" in self.stp_cfg: + self.end_state["stp_mode"] = "rstp" + else: + self.end_state["stp_mode"] = "mstp" + + if self.stp_enable: + if "stp disable" in self.stp_cfg: + self.end_state["stp_enable"] = "disable" + else: + self.end_state["stp_enable"] = "enable" + + if self.stp_converge: + if "stp converge fast" in self.stp_cfg: + self.end_state["stp_converge"] = "fast" + else: + self.end_state["stp_converge"] = "normal" + + if self.edged_port: + if self.interface == "all": + if "stp edged-port default" in self.stp_cfg: + self.end_state["edged_port"] = "enable" + else: + self.end_state["edged_port"] = "disable" + else: + if "stp edged-port enable" in self.interface_stp_cfg: + self.end_state["edged_port"] = "enable" + else: + self.end_state["edged_port"] = "disable" + + if self.bpdu_filter: + if self.interface == "all": + if "stp bpdu-filter default" in self.stp_cfg: + self.end_state["bpdu_filter"] = "enable" + else: + self.end_state["bpdu_filter"] = "disable" + else: + if "stp bpdu-filter enable" in self.interface_stp_cfg: + self.end_state["bpdu_filter"] = "enable" + else: + self.end_state["bpdu_filter"] = "disable" + + if self.bpdu_protection: + if "stp bpdu-protection" in self.stp_cfg: + self.end_state["bpdu_protection"] = "enable" + else: + self.end_state["bpdu_protection"] = "disable" + + if self.tc_protection: + pre_cfg = self.stp_cfg.split("\n") + if "stp tc-protection" in pre_cfg: + self.end_state["tc_protection"] = "enable" + else: + self.end_state["tc_protection"] = "disable" + + if self.tc_protection_interval: + if "stp tc-protection interval" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection interval (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection interval on the device.') + self.end_state["tc_protection_interval"] = tmp_value[0] + else: + self.end_state["tc_protection_interval"] = "null" + + if self.tc_protection_threshold: + if "stp tc-protection threshold" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection threshold (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection threshold on the device.') + self.end_state["tc_protection_threshold"] = tmp_value[0] + else: + self.end_state["tc_protection_threshold"] = "1" + + if self.cost: + tmp_value = re.findall(r'stp instance (.*) cost (.*)', self.interface_stp_cfg) + if not tmp_value: + self.end_state["cost"] = "null" + else: + self.end_state["cost"] = tmp_value[0][1] + + if self.root_protection or self.loop_protection: + if "stp root-protection" in self.interface_stp_cfg: + self.end_state["root_protection"] = "enable" + else: + self.end_state["root_protection"] = "disable" + + if "stp loop-protection" in self.interface_stp_cfg: + self.end_state["loop_protection"] = "enable" + else: + self.end_state["loop_protection"] = "disable" + + if self.existing == self.end_state: + self.changed = False + self.updates_cmd = list() + + def present_stp(self): + """ Present stp configuration """ + + cmds = list() + + # config stp global + if self.stp_mode: + if self.stp_mode != self.cur_cfg["stp_mode"]: + cmd = "stp mode %s" % self.stp_mode + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_enable: + if self.stp_enable != self.cur_cfg["stp_enable"]: + cmd = "stp %s" % self.stp_enable + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_converge: + if self.stp_converge != self.cur_cfg["stp_converge"]: + cmd = "stp converge %s" % self.stp_converge + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.interface == "all": + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_filter: + if self.interface == "all": + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_protection: + if self.bpdu_protection != self.cur_cfg["bpdu_protection"]: + if self.bpdu_protection == "enable": + cmd = "stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection: + if self.tc_protection != self.cur_cfg["tc_protection"]: + if self.tc_protection == "enable": + cmd = "stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_interval: + if self.tc_protection_interval != self.cur_cfg["tc_protection_interval"]: + cmd = "stp tc-protection interval %s" % self.tc_protection_interval + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_threshold: + if self.tc_protection_threshold != self.cur_cfg["tc_protection_threshold"]: + cmd = "stp tc-protection threshold %s" % self.tc_protection_threshold + cmds.append(cmd) + self.updates_cmd.append(cmd) + + # config interface stp + if self.interface and self.interface != "all": + tmp_changed = False + + cmd = "interface %s" % self.interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp edged-port" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.bpdu_filter: + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp bpdu-filter" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.root_protection: + if self.root_protection == "enable" and self.cur_cfg["loop_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable loop_protection, can not enable root_protection.') + if self.root_protection != self.cur_cfg["root_protection"]: + if self.root_protection == "enable": + cmd = "stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.loop_protection: + if self.loop_protection == "enable" and self.cur_cfg["root_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable root_protection, can not enable loop_protection.') + if self.loop_protection != self.cur_cfg["loop_protection"]: + if self.loop_protection == "enable": + cmd = "stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.cost: + if self.cost != self.cur_cfg["cost"]: + cmd = "stp cost %s" % self.cost + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if not tmp_changed: + cmd = "interface %s" % self.interface + self.updates_cmd.remove(cmd) + cmds.remove(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def absent_stp(self): + """ Absent stp configuration """ + + cmds = list() + + if self.stp_mode: + if self.stp_mode == self.cur_cfg["stp_mode"]: + if self.stp_mode != "mstp": + cmd = "undo stp mode" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.stp_enable: + if self.stp_enable != self.cur_cfg["stp_enable"]: + cmd = "stp %s" % self.stp_enable + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_converge: + if self.stp_converge == self.cur_cfg["stp_converge"]: + cmd = "undo stp converge" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.edged_port: + if self.interface == "all": + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_filter: + if self.interface == "all": + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_protection: + if self.bpdu_protection != self.cur_cfg["bpdu_protection"]: + if self.bpdu_protection == "enable": + cmd = "stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection: + if self.tc_protection != self.cur_cfg["tc_protection"]: + if self.tc_protection == "enable": + cmd = "stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_interval: + if self.tc_protection_interval == self.cur_cfg["tc_protection_interval"]: + cmd = "undo stp tc-protection interval" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.tc_protection_threshold: + if self.tc_protection_threshold == self.cur_cfg["tc_protection_threshold"]: + if self.tc_protection_threshold != "1": + cmd = "undo stp tc-protection threshold" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + # undo interface stp + if self.interface and self.interface != "all": + tmp_changed = False + + cmd = "interface %s" % self.interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp edged-port" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.bpdu_filter: + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp bpdu-filter" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.root_protection: + if self.root_protection == "enable" and self.cur_cfg["loop_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable loop_protection, can not enable root_protection.') + if self.root_protection != self.cur_cfg["root_protection"]: + if self.root_protection == "enable": + cmd = "stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.loop_protection: + if self.loop_protection == "enable" and self.cur_cfg["root_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable root_protection, can not enable loop_protection.') + if self.loop_protection != self.cur_cfg["loop_protection"]: + if self.loop_protection == "enable": + cmd = "stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.cost: + if self.cost == self.cur_cfg["cost"]: + cmd = "undo stp cost" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if not tmp_changed: + cmd = "interface %s" % self.interface + self.updates_cmd.remove(cmd) + cmds.remove(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Work function """ + + self.check_params() + self.get_proposed() + self.get_existing() + + if self.state == "present": + self.present_stp() + else: + self.absent_stp() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + stp_mode=dict(choices=['stp', 'rstp', 'mstp']), + stp_enable=dict(choices=['enable', 'disable']), + stp_converge=dict(choices=['fast', 'normal']), + bpdu_protection=dict(choices=['enable', 'disable']), + tc_protection=dict(choices=['enable', 'disable']), + tc_protection_interval=dict(type='str'), + tc_protection_threshold=dict(type='str'), + interface=dict(type='str'), + edged_port=dict(choices=['enable', 'disable']), + bpdu_filter=dict(choices=['enable', 'disable']), + cost=dict(type='str'), + root_protection=dict(choices=['enable', 'disable']), + loop_protection=dict(choices=['enable', 'disable']) + ) + + argument_spec.update(ce_argument_spec) + module = Stp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_switchport.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_switchport.py new file mode 100644 index 00000000..10dee874 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_switchport.py @@ -0,0 +1,997 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_switchport +short_description: Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. +description: + - Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - When C(state=absent), VLANs can be added/removed from trunk links and + the existing access VLAN can be 'unconfigured' to just having VLAN 1 on that interface. + - When working with trunks VLANs the keywords add/remove are always sent + in the C(port trunk allow-pass vlan) command. Use verbose mode to see commands sent. + - When C(state=unconfigured), the interface will result with having a default Layer 2 interface, i.e. vlan 1 in access mode. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of the interface, i.e. 40GE1/0/22. + required: true + mode: + description: + - The link type of an interface. + choices: ['access','trunk', 'hybrid', 'dot1qtunnel'] + default_vlan: + description: + - If C(mode=access, or mode=dot1qtunnel), used as the access VLAN ID, in the range from 1 to 4094. + pvid_vlan: + description: + - If C(mode=trunk, or mode=hybrid), used as the trunk native VLAN ID, in the range from 1 to 4094. + trunk_vlans: + description: + - If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + untagged_vlans: + description: + - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + tagged_vlans: + description: + - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present', 'absent', 'unconfigured'] +''' + +EXAMPLES = ''' +- name: Switchport module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure 10GE1/0/22 is in its default switchport state + community.network.ce_switchport: + interface: 10GE1/0/22 + state: unconfigured + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 is configured for access vlan 20 + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: access + default_vlan: 20 + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 only has vlans 5-10 as trunk vlans + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + pvid_vlan: 10 + trunk_vlans: 5-10 + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + pvid_vlan: 10 + trunk_vlans: 2-50 + provider: '{{ cli }}' + + - name: Ensure these VLANs are not being tagged on the trunk + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + trunk_vlans: 51-4000 + state: absent + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"default_vlan": "20", "interface": "10GE1/0/22", "mode": "access"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"default_vlan": "10", "interface": "10GE1/0/22", + "mode": "access", "switchport": "enable"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"default_vlan": "20", "interface": "10GE1/0/22", + "mode": "access", "switchport": "enable"} +updates: + description: command string sent to the device + returned: always + type: list + sample: ["10GE1/0/22", "port default vlan 20"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree as ET +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_PORT_ATTR = """ + + + + + %s + + + + + + + + + + + +""" + +CE_NC_SET_PORT = """ + + + + %s + + %s + %s + %s + %s + + + + +""" + +CE_NC_SET_PORT_MODE = """ + + + + %s + + %s + + + + +""" + +CE_NC_SET_DEFAULT_PORT = """ + + + + + %s + + access + 1 + + + + + + + +""" + + +SWITCH_PORT_TYPE = ('ge', '10ge', '25ge', + '4x10ge', '40ge', '100ge', 'eth-trunk') + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +def is_portswitch_enalbed(iftype): + """"[undo] portswitch""" + + return bool(iftype in SWITCH_PORT_TYPE) + + +def vlan_bitmap_undo(bitmap): + """convert vlan bitmap to undo bitmap""" + + vlan_bit = ['F'] * 1024 + + if not bitmap or len(bitmap) == 0: + return ''.join(vlan_bit) + + bit_len = len(bitmap) + for num in range(bit_len): + undo = (~int(bitmap[num], 16)) & 0xF + vlan_bit[num] = hex(undo)[2] + + return ''.join(vlan_bit) + + +def is_vlan_bitmap_empty(bitmap): + """check vlan bitmap empty""" + + if not bitmap or len(bitmap) == 0: + return True + + bit_len = len(bitmap) + for num in range(bit_len): + if bitmap[num] != '0': + return False + + return True + + +class SwitchPort(object): + """ + Manages Layer 2 switchport interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface and vlan info + self.interface = self.module.params['interface'] + self.mode = self.module.params['mode'] + self.state = self.module.params['state'] + self.default_vlan = self.module.params['default_vlan'] + self.pvid_vlan = self.module.params['pvid_vlan'] + self.trunk_vlans = self.module.params['trunk_vlans'] + self.untagged_vlans = self.module.params['untagged_vlans'] + self.tagged_vlans = self.module.params['tagged_vlans'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intf_info = dict() # interface vlan info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """ init module """ + + required_if = [('state', 'absent', ['mode']), ('state', 'present', ['mode'])] + mutually_exclusive = [['default_vlan', 'trunk_vlans'], + ['default_vlan', 'pvid_vlan'], + ['default_vlan', 'untagged_vlans'], + ['trunk_vlans', 'untagged_vlans'], + ['trunk_vlans', 'tagged_vlans'], + ['default_vlan', 'tagged_vlans']] + + self.module = AnsibleModule( + argument_spec=self.spec, required_if=required_if, supports_check_mode=True, mutually_exclusive=mutually_exclusive) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_PORT_ATTR % ifname + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return intf_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + tree = ET.fromstring(xml_str) + l2Enable = tree.find('ethernet/ethernetIfs/ethernetIf/l2Enable') + intf_info["l2Enable"] = l2Enable.text + port_type = tree.find('ethernet/ethernetIfs/ethernetIf/l2Attribute') + for pre in port_type: + intf_info[pre.tag] = pre.text + intf_info["ifName"] = ifname + if intf_info["trunkVlans"] is None: + intf_info["trunkVlans"] = "" + if intf_info["untagVlans"] is None: + intf_info["untagVlans"] = "" + return intf_info + + def is_l2switchport(self): + """Check layer2 switch port""" + + return bool(self.intf_info["l2Enable"] == "enable") + + def merge_access_vlan(self, ifname, default_vlan): + """Merge access interface vlan""" + + change = False + conf_str = "" + + self.updates_cmd.append("interface %s" % ifname) + if self.state == "present": + if self.intf_info["linkType"] == "access": + if default_vlan and self.intf_info["pvid"] != default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") + change = True + else: # not access + self.updates_cmd.append("port link-type access") + if default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") + else: + conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") + change = True + elif self.state == "absent": + if self.intf_info["linkType"] == "access": + if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": + self.updates_cmd.append( + "undo port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") + change = True + + if not change: + self.updates_cmd.pop() # remove interface + return + conf_str = "" + conf_str + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_ACCESS_PORT") + self.changed = True + + def merge_trunk_vlan(self, ifname, pvid_vlan, trunk_vlans): + """Merge trunk interface vlan""" + + change = False + xmlstr = "" + pvid = "" + trunk = "" + self.updates_cmd.append("interface %s" % ifname) + if trunk_vlans: + vlan_list = self.vlan_range_to_list(trunk_vlans) + vlan_map = self.vlan_list_to_bitmap(vlan_list) + if self.state == "present": + if self.intf_info["linkType"] == "trunk": + if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: + self.updates_cmd.append( + "port trunk pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + change = True + + if trunk_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["trunkVlans"], vlan_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + trunk = "%s:%s" % (add_vlans, add_vlans) + change = True + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + else: # not trunk + self.updates_cmd.append("port link-type trunk") + change = True + if pvid_vlan: + self.updates_cmd.append( + "port trunk pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + if trunk_vlans: + self.updates_cmd.append( + "port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + trunk = "%s:%s" % (vlan_map, vlan_map) + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + if not pvid_vlan and not trunk_vlans: + xmlstr += CE_NC_SET_PORT_MODE % (ifname, "trunk") + self.updates_cmd.append( + "undo port trunk allow-pass vlan 1") + elif self.state == "absent": + if self.intf_info["linkType"] == "trunk": + if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': + self.updates_cmd.append( + "undo port trunk pvid vlan %s" % pvid_vlan) + pvid = "1" + change = True + if trunk_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["trunkVlans"], vlan_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + trunk = "%s:%s" % (undo_map, del_vlans) + change = True + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + if not change: + self.updates_cmd.pop() + return + conf_str = "" + xmlstr + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_TRUNK_PORT") + self.changed = True + + def merge_hybrid_vlan(self, ifname, pvid_vlan, tagged_vlans, untagged_vlans): + """Merge hybrid interface vlan""" + + change = False + xmlstr = "" + pvid = "" + tagged = "" + untagged = "" + self.updates_cmd.append("interface %s" % ifname) + if tagged_vlans: + vlan_targed_list = self.vlan_range_to_list(tagged_vlans) + vlan_targed_map = self.vlan_list_to_bitmap(vlan_targed_list) + if untagged_vlans: + vlan_untarged_list = self.vlan_range_to_list(untagged_vlans) + vlan_untarged_map = self.vlan_list_to_bitmap(vlan_untarged_list) + if self.state == "present": + if self.intf_info["linkType"] == "hybrid": + if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: + self.updates_cmd.append( + "port hybrid pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + change = True + if tagged_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["trunkVlans"], vlan_targed_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + tagged = "%s:%s" % (add_vlans, add_vlans) + change = True + if untagged_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["untagVlans"], vlan_untarged_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + untagged = "%s:%s" % (add_vlans, add_vlans) + change = True + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + else: + self.updates_cmd.append("port link-type hybrid") + change = True + if pvid_vlan: + self.updates_cmd.append( + "port hybrid pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + if tagged_vlans: + self.updates_cmd.append( + "port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + tagged = "%s:%s" % (vlan_targed_map, vlan_targed_map) + if untagged_vlans: + self.updates_cmd.append( + "port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + untagged = "%s:%s" % (vlan_untarged_map, vlan_untarged_map) + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + if not pvid_vlan and not tagged_vlans and not untagged_vlans: + xmlstr += CE_NC_SET_PORT_MODE % (ifname, "hybrid") + self.updates_cmd.append( + "undo port hybrid untagged vlan 1") + elif self.state == "absent": + if self.intf_info["linkType"] == "hybrid": + if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': + self.updates_cmd.append( + "undo port hybrid pvid vlan %s" % pvid_vlan) + pvid = "1" + change = True + if tagged_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["trunkVlans"], vlan_targed_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + tagged = "%s:%s" % (undo_map, del_vlans) + change = True + if untagged_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["untagVlans"], vlan_untarged_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + untagged = "%s:%s" % (undo_map, del_vlans) + change = True + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + + if not change: + self.updates_cmd.pop() + return + + conf_str = "" + xmlstr + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_HYBRID_PORT") + self.changed = True + + def merge_dot1qtunnel_vlan(self, ifname, default_vlan): + """Merge dot1qtunnel""" + + change = False + conf_str = "" + + self.updates_cmd.append("interface %s" % ifname) + if self.state == "present": + if self.intf_info["linkType"] == "dot1qtunnel": + if default_vlan and self.intf_info["pvid"] != default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") + change = True + else: + self.updates_cmd.append("port link-type dot1qtunnel") + if default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") + else: + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") + change = True + elif self.state == "absent": + if self.intf_info["linkType"] == "dot1qtunnel": + if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": + self.updates_cmd.append( + "undo port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") + change = True + if not change: + self.updates_cmd.pop() # remove interface + return + conf_str = "" + conf_str + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_DOT1QTUNNEL_PORT") + self.changed = True + + def default_switchport(self, ifname): + """Set interface default or unconfigured""" + + change = False + if self.intf_info["linkType"] != "access": + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("port link-type access") + self.updates_cmd.append("port default vlan 1") + change = True + else: + if self.intf_info["pvid"] != "1": + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("port default vlan 1") + change = True + + if not change: + return + + conf_str = CE_NC_SET_DEFAULT_PORT % ifname + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "DEFAULT_INTF_VLAN") + self.changed = True + + def vlan_series(self, vlanid_s): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_s) + if peerlistlen != 2: + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + for num in range(peerlistlen): + if not vlanid_s[num].isdigit(): + self.module.fail_json( + msg='Error: Format of vlanid is invalid.') + if int(vlanid_s[0]) > int(vlanid_s[1]): + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + elif int(vlanid_s[0]) == int(vlanid_s[1]): + vlan_list.append(str(vlanid_s[0])) + return vlan_list + for num in range(int(vlanid_s[0]), int(vlanid_s[1])): + vlan_list.append(str(num)) + vlan_list.append(vlanid_s[1]) + + return vlan_list + + def vlan_region(self, vlanid_list): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_list) + for num in range(peerlistlen): + if vlanid_list[num].isdigit(): + vlan_list.append(vlanid_list[num]) + else: + vlan_s = self.vlan_series(vlanid_list[num].split('-')) + vlan_list.extend(vlan_s) + + return vlan_list + + def vlan_range_to_list(self, vlan_range): + """ convert vlan range to vlan list """ + + vlan_list = self.vlan_region(vlan_range.split(',')) + + return vlan_list + + def vlan_list_to_bitmap(self, vlanlist): + """ convert vlan list to vlan bitmap """ + + vlan_bit = ['0'] * 1024 + bit_int = [0] * 1024 + + vlan_list_len = len(vlanlist) + for num in range(vlan_list_len): + tagged_vlans = int(vlanlist[num]) + if tagged_vlans <= 0 or tagged_vlans > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + j = tagged_vlans // 4 + bit_int[j] |= 0x8 >> (tagged_vlans % 4) + vlan_bit[j] = hex(bit_int[j])[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def vlan_bitmap_add(self, oldmap, newmap): + """vlan add bitmap""" + + vlan_bit = ['0'] * 1024 + + if len(newmap) != 1024: + self.module.fail_json(msg='Error: New vlan bitmap is invalid.') + + if len(oldmap) != 1024 and len(oldmap) != 0: + self.module.fail_json(msg='Error: old vlan bitmap is invalid.') + + if len(oldmap) == 0: + return newmap + + for num in range(1024): + new_tmp = int(newmap[num], 16) + old_tmp = int(oldmap[num], 16) + add = (~(new_tmp & old_tmp)) & new_tmp + vlan_bit[num] = hex(add)[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def vlan_bitmap_del(self, oldmap, delmap): + """vlan del bitmap""" + + vlan_bit = ['0'] * 1024 + + if not oldmap or len(oldmap) == 0: + return ''.join(vlan_bit) + + if len(oldmap) != 1024 or len(delmap) != 1024: + self.module.fail_json(msg='Error: vlan bitmap is invalid.') + + for num in range(1024): + tmp = int(delmap[num], 16) & int(oldmap[num], 16) + vlan_bit[num] = hex(tmp)[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def check_params(self): + """Check all input params""" + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + + if not self.intf_type or not is_portswitch_enalbed(self.intf_type): + self.module.fail_json(msg='Error: Interface %s is error.') + + # check default_vlan + if self.default_vlan: + if not self.default_vlan.isdigit(): + self.module.fail_json(msg='Error: Access vlan id is invalid.') + if int(self.default_vlan) <= 0 or int(self.default_vlan) > 4094: + self.module.fail_json( + msg='Error: Access vlan id is not in the range from 1 to 4094.') + + # check pvid_vlan + if self.pvid_vlan: + if not self.pvid_vlan.isdigit(): + self.module.fail_json(msg='Error: Pvid vlan id is invalid.') + if int(self.pvid_vlan) <= 0 or int(self.pvid_vlan) > 4094: + self.module.fail_json( + msg='Error: Pvid vlan id is not in the range from 1 to 4094.') + + # get interface info + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: Interface does not exist.') + + if not self.is_l2switchport(): + self.module.fail_json( + msg='Error: Interface is not layer2 switch port.') + if self.state == "unconfigured": + if any([self.mode, self.default_vlan, self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When state is unconfigured, only interface name exists.') + else: + if self.mode == "access": + if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is access, only default_vlan can be supported.') + elif self.mode == "trunk": + if any([self.default_vlan, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is trunk, only pvid_vlan and trunk_vlans can exist.') + elif self.mode == "hybrid": + if any([self.default_vlan, self.trunk_vlans]): + self.module.fail_json( + msg='Error: When mode is hybrid, default_vlan and trunk_vlans cannot exist') + else: + if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is dot1qtunnel, only default_vlan can be supported.') + + def get_proposed(self): + """get proposed info""" + + self.proposed['state'] = self.state + self.proposed['interface'] = self.interface + self.proposed['mode'] = self.mode + if self.mode: + if self.mode == "access": + self.proposed['access_pvid'] = self.default_vlan + elif self.mode == "trunk": + self.proposed['pvid_vlan'] = self.pvid_vlan + self.proposed['trunk_vlans'] = self.trunk_vlans + elif self.mode == "hybrid": + self.proposed['pvid_vlan'] = self.pvid_vlan + self.proposed['untagged_vlans'] = self.untagged_vlans + self.proposed['tagged_vlans'] = self.tagged_vlans + else: + self.proposed['dot1qtunnel_pvid'] = self.default_vlan + + def get_existing(self): + """get existing info""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + self.existing["switchport"] = self.intf_info["l2Enable"] + self.existing["mode"] = self.intf_info["linkType"] + if self.intf_info["linkType"] == "access": + self.existing['access_pvid'] = self.intf_info["pvid"] + elif self.intf_info["linkType"] == "trunk": + self.existing['trunk_pvid'] = self.intf_info["pvid"] + self.existing['trunk_vlans'] = self.intf_info["trunkVlans"] + elif self.intf_info["linkType"] == "hybrid": + self.existing['hybrid_pvid'] = self.intf_info["pvid"] + self.existing['hybrid_untagged_vlans'] = self.intf_info["untagVlans"] + self.existing['hybrid_tagged_vlans'] = self.intf_info["trunkVlans"] + else: + self.existing['dot1qtunnel_pvid'] = self.intf_info["pvid"] + + def get_end_state(self): + """get end state info""" + + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + self.end_state["switchport"] = end_info["l2Enable"] + self.end_state["mode"] = end_info["linkType"] + if end_info["linkType"] == "access": + self.end_state['access_pvid'] = end_info["pvid"] + elif end_info["linkType"] == "trunk": + self.end_state['trunk_pvid'] = end_info["pvid"] + self.end_state['trunk_vlans'] = end_info["trunkVlans"] + elif end_info["linkType"] == "hybrid": + self.end_state['hybrid_pvid'] = end_info["pvid"] + self.end_state['hybrid_untagged_vlans'] = end_info["untagVlans"] + self.end_state['hybrid_tagged_vlans'] = end_info["trunkVlans"] + else: + self.end_state['dot1qtunnel_pvid'] = end_info["pvid"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if not self.intf_info: + self.module.fail_json(msg='Error: interface does not exist.') + self.get_existing() + self.get_proposed() + + # present or absent + if self.state == "present" or self.state == "absent": + if self.mode == "access": + self.merge_access_vlan(self.interface, self.default_vlan) + elif self.mode == "trunk": + self.merge_trunk_vlan( + self.interface, self.pvid_vlan, self.trunk_vlans) + elif self.mode == "hybrid": + self.merge_hybrid_vlan(self.interface, self.pvid_vlan, self.tagged_vlans, self.untagged_vlans) + else: + self.merge_dot1qtunnel_vlan(self.interface, self.default_vlan) + + # unconfigured + else: + self.default_switchport(self.interface) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + mode=dict(choices=['access', 'trunk', 'dot1qtunnel', 'hybrid'], required=False), + default_vlan=dict(type='str', required=False), + pvid_vlan=dict(type='str', required=False), + trunk_vlans=dict(type='str', required=False), + untagged_vlans=dict(type='str', required=False), + tagged_vlans=dict(type='str', required=False), + state=dict(choices=['absent', 'present', 'unconfigured'], + default='present') + ) + + argument_spec.update(ce_argument_spec) + switchport = SwitchPort(argument_spec) + switchport.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vlan.py new file mode 100644 index 00000000..61029410 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vlan.py @@ -0,0 +1,687 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vlan +short_description: Manages VLAN resources and attributes on Huawei CloudEngine switches. +description: + - Manages VLAN configurations on Huawei CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vlan_id: + description: + - Single VLAN ID, in the range from 1 to 4094. + vlan_range: + description: + - Range of VLANs such as C(2-10) or C(2,5,10-15), etc. + name: + description: + - Name of VLAN, minimum of 1 character, maximum of 31 characters. + description: + description: + - Specify VLAN description, minimum of 1 character, maximum of 80 characters. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Vlan module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Ensure a range of VLANs are not present on the switch + community.network.ce_vlan: + vlan_range: "2-10,20,50,55-60,100-150" + state: absent + provider: "{{ cli }}" + + - name: Ensure VLAN 50 exists with the name WEB + community.network.ce_vlan: + vlan_id: 50 + name: WEB + state: absent + provider: "{{ cli }}" + + - name: Ensure VLAN is NOT on the device + community.network.ce_vlan: + vlan_id: 50 + state: absent + provider: "{{ cli }}" + +''' + +RETURN = ''' +proposed_vlans_list: + description: list of VLANs being proposed + returned: always + type: list + sample: ["100"] +existing_vlans_list: + description: list of existing VLANs on the switch prior to making changes + returned: always + type: list + sample: ["1", "2", "3", "4", "5", "20"] +end_state_vlans_list: + description: list of VLANs after the module is executed + returned: always + type: list + sample: ["1", "2", "3", "4", "5", "20", "100"] +proposed: + description: k/v pairs of parameters passed into module (does not include + vlan_id or vlan_range) + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "vlan for app" } +existing: + description: k/v pairs of existing vlan or null when using vlan_range + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "" } +end_state: + description: k/v pairs of the VLAN after executing module or null + when using vlan_range + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "vlan for app" } +updates: + description: command string sent to the device + returned: always + type: list + sample: ["vlan 20", "name VLAN20"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, execute_nc_action, ce_argument_spec + +CE_NC_CREATE_VLAN = """ + + + + + %s + %s + %s + + + + + + +""" + +CE_NC_DELETE_VLAN = """ + + + + + %s + + + + +""" + +CE_NC_MERGE_VLAN_DES = """ + + + + + %s + %s + + + + + + +""" + +CE_NC_MERGE_VLAN_NAME = """ + + + + + %s + %s + + + + + + +""" + + +CE_NC_MERGE_VLAN = """ + + + + + %s + %s + %s + + + + + + +""" + +CE_NC_GET_VLAN = """ + + + + + %s + + + + + + +""" + +CE_NC_GET_VLANS = """ + + + + + + + + + + +""" + +CE_NC_CREATE_VLAN_BATCH = """ + + + + %s:%s + + + +""" + +CE_NC_DELETE_VLAN_BATCH = """ + + + + %s:%s + + + +""" + + +class Vlan(object): + """ + Manages VLAN resources and attributes + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # vlan config info + self.vlan_id = self.module.params['vlan_id'] + self.vlan_range = self.module.params['vlan_range'] + self.name = self.module.params['name'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.vlan_exist = False + self.vlan_attr_exist = None + self.vlans_list_exist = list() + self.vlans_list_change = list() + self.updates_cmd = list() + self.results = dict() + self.vlan_attr_end = dict() + + def init_module(self): + """ + init ansible NetworkModule. + """ + + required_one_of = [["vlan_id", "vlan_range"]] + mutually_exclusive = [["vlan_id", "vlan_range"]] + + self.module = AnsibleModule( + argument_spec=self.spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_vlan(self, vlan_id, name='', description=''): + """Create vlan.""" + + if name is None: + name = '' + if description is None: + description = '' + + conf_str = CE_NC_CREATE_VLAN % (vlan_id, name, description) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "CREATE_VLAN") + self.changed = True + + def merge_vlan(self, vlan_id, name, description): + """Merge vlan.""" + + conf_str = None + + if not name and description: + conf_str = CE_NC_MERGE_VLAN_DES % (vlan_id, description) + if not description and name: + conf_str = CE_NC_MERGE_VLAN_NAME % (vlan_id, name) + if description and name: + conf_str = CE_NC_MERGE_VLAN % (vlan_id, name, description) + + if not conf_str: + return + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_VLAN") + self.changed = True + + def create_vlan_batch(self, vlan_list): + """Create vlan batch.""" + + if not vlan_list: + return + + vlan_bitmap = self.vlan_list_to_bitmap(vlan_list) + xmlstr = CE_NC_CREATE_VLAN_BATCH % (vlan_bitmap, vlan_bitmap) + + recv_xml = execute_nc_action(self.module, xmlstr) + self.check_response(recv_xml, "CREATE_VLAN_BATCH") + self.updates_cmd.append('vlan batch %s' % ( + self.vlan_range.replace(',', ' ').replace('-', ' to '))) + self.changed = True + + def delete_vlan_batch(self, vlan_list): + """Delete vlan batch.""" + + if not vlan_list: + return + + vlan_bitmap = self.vlan_list_to_bitmap(vlan_list) + xmlstr = CE_NC_DELETE_VLAN_BATCH % (vlan_bitmap, vlan_bitmap) + + recv_xml = execute_nc_action(self.module, xmlstr) + self.check_response(recv_xml, "DELETE_VLAN_BATCH") + self.updates_cmd.append('undo vlan batch %s' % ( + self.vlan_range.replace(',', ' ').replace('-', ' to '))) + self.changed = True + + def undo_config_vlan(self, vlanid): + """Delete vlan.""" + + conf_str = CE_NC_DELETE_VLAN % vlanid + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_VLAN") + self.changed = True + self.updates_cmd.append('undo vlan %s' % self.vlan_id) + + def get_vlan_attr(self, vlan_id): + """ get vlan attributes.""" + + conf_str = CE_NC_GET_VLAN % vlan_id + xml_str = get_nc_config(self.module, conf_str) + attr = dict() + + if "" in xml_str: + return attr + else: + re_find_id = re.findall(r'.*(.*).*\s*', xml_str) + re_find_name = re.findall(r'.*(.*).*\s*', xml_str) + re_find_desc = re.findall(r'.*(.*).*\s*', xml_str) + + if re_find_id: + if re_find_name: + attr = dict(vlan_id=re_find_id[0], name=re_find_name[0], + description=re_find_desc[0]) + else: + attr = dict(vlan_id=re_find_id[0], name=None, + description=re_find_desc[0]) + return attr + + def get_vlans_name(self): + """ get all vlan vid and its name list, + sample: [ ("20", "VLAN_NAME_20"), ("30", "VLAN_NAME_30") ]""" + + conf_str = CE_NC_GET_VLANS + xml_str = get_nc_config(self.module, conf_str) + vlan_list = list() + + if "" in xml_str: + return vlan_list + else: + vlan_list = re.findall( + r'.*(.*).*\s*(.*).*', xml_str) + return vlan_list + + def get_vlans_list(self): + """ get all vlan vid list, sample: [ "20", "30", "31" ]""" + + conf_str = CE_NC_GET_VLANS + xml_str = get_nc_config(self.module, conf_str) + vlan_list = list() + + if "" in xml_str: + return vlan_list + else: + vlan_list = re.findall( + r'.*(.*).*', xml_str) + return vlan_list + + def vlan_series(self, vlanid_s): + """ convert vlan range to list """ + + vlan_list = [] + peerlistlen = len(vlanid_s) + if peerlistlen != 2: + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + for num in range(peerlistlen): + if not vlanid_s[num].isdigit(): + self.module.fail_json( + msg='Error: Format of vlanid is invalid.') + if int(vlanid_s[0]) > int(vlanid_s[1]): + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + elif int(vlanid_s[0]) == int(vlanid_s[1]): + vlan_list.append(str(vlanid_s[0])) + return vlan_list + for num in range(int(vlanid_s[0]), int(vlanid_s[1])): + vlan_list.append(str(num)) + vlan_list.append(vlanid_s[1]) + + return vlan_list + + def vlan_region(self, vlanid_list): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_list) + for num in range(peerlistlen): + if vlanid_list[num].isdigit(): + vlan_list.append(vlanid_list[num]) + else: + vlan_s = self.vlan_series(vlanid_list[num].split('-')) + vlan_list.extend(vlan_s) + + return vlan_list + + def vlan_range_to_list(self, vlan_range): + """ convert vlan range to vlan list """ + + vlan_list = self.vlan_region(vlan_range.split(',')) + + return vlan_list + + def vlan_list_to_bitmap(self, vlanlist): + """ convert vlan list to vlan bitmap """ + + vlan_bit = ['0'] * 1024 + bit_int = [0] * 1024 + + vlan_list_len = len(vlanlist) + for num in range(vlan_list_len): + tagged_vlans = int(vlanlist[num]) + if tagged_vlans <= 0 or tagged_vlans > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + j = tagged_vlans // 4 + bit_int[j] |= 0x8 >> (tagged_vlans % 4) + vlan_bit[j] = hex(bit_int[j])[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def check_params(self): + """Check all input params""" + + if not self.vlan_id and self.description: + self.module.fail_json( + msg='Error: Vlan description could be set only at one vlan.') + + if not self.vlan_id and self.name: + self.module.fail_json( + msg='Error: Vlan name could be set only at one vlan.') + + # check vlan id + if self.vlan_id: + if not self.vlan_id.isdigit(): + self.module.fail_json( + msg='Error: Vlan id is not digit.') + if int(self.vlan_id) <= 0 or int(self.vlan_id) > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + + # check vlan description + if self.description: + if len(self.description) > 81 or len(self.description.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: vlan description is not in the range from 1 to 80.') + + # check vlan name + if self.name: + if len(self.name) > 31 or len(self.name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: Vlan name is not in the range from 1 to 31.') + + def get_proposed(self): + """ + get proposed config. + """ + + if self.vlans_list_change: + if self.state == 'present': + proposed_vlans_tmp = list(self.vlans_list_change) + proposed_vlans_tmp.extend(self.vlans_list_exist) + self.results['proposed_vlans_list'] = list( + set(proposed_vlans_tmp)) + else: + self.results['proposed_vlans_list'] = list( + set(self.vlans_list_exist) - set(self.vlans_list_change)) + self.results['proposed_vlans_list'].sort() + else: + self.results['proposed_vlans_list'] = self.vlans_list_exist + + if self.vlan_id: + if self.state == "present": + self.results['proposed'] = dict( + vlan_id=self.vlan_id, + name=self.name, + description=self.description + ) + else: + self.results['proposed'] = None + else: + self.results['proposed'] = None + + def get_existing(self): + """ + get existing config. + """ + + self.results['existing_vlans_list'] = self.vlans_list_exist + + if self.vlan_id: + if self.vlan_attr_exist: + self.results['existing'] = dict( + vlan_id=self.vlan_attr_exist['vlan_id'], + name=self.vlan_attr_exist['name'], + description=self.vlan_attr_exist['description'] + ) + else: + self.results['existing'] = None + else: + self.results['existing'] = None + + def get_end_state(self): + """ + get end state config. + """ + + self.results['end_state_vlans_list'] = self.get_vlans_list() + + if self.vlan_id: + if self.vlan_attr_end: + self.results['end_state'] = dict( + vlan_id=self.vlan_attr_end['vlan_id'], + name=self.vlan_attr_end['name'], + description=self.vlan_attr_end['description'] + ) + else: + self.results['end_state'] = None + + else: + self.results['end_state'] = None + + def work(self): + """ + worker. + """ + + # check param + self.check_params() + + # get all vlan info + self.vlans_list_exist = self.get_vlans_list() + + # get vlan attributes + if self.vlan_id: + self.vlans_list_change.append(self.vlan_id) + self.vlan_attr_exist = self.get_vlan_attr(self.vlan_id) + if self.vlan_attr_exist: + self.vlan_exist = True + + if self.vlan_range: + new_vlans_tmp = self.vlan_range_to_list(self.vlan_range) + if self.state == 'present': + self.vlans_list_change = list( + set(new_vlans_tmp) - set(self.vlans_list_exist)) + else: + self.vlans_list_change = [ + val for val in new_vlans_tmp if val in self.vlans_list_exist] + + if self.state == 'present': + if self.vlan_id: + if not self.vlan_exist: + # create a new vlan + self.config_vlan(self.vlan_id, self.name, self.description) + elif self.description and self.description != self.vlan_attr_exist['description']: + # merge vlan description + self.merge_vlan(self.vlan_id, self.name, self.description) + elif self.name and self.name != self.vlan_attr_exist['name']: + # merge vlan name + self.merge_vlan(self.vlan_id, self.name, self.description) + + # update command for results + if self.changed: + self.updates_cmd.append('vlan %s' % self.vlan_id) + if self.name: + self.updates_cmd.append('name %s' % self.name) + if self.description: + self.updates_cmd.append( + 'description %s' % self.description) + elif self.vlan_range and self.vlans_list_change: + self.create_vlan_batch(self.vlans_list_change) + else: # absent + if self.vlan_id: + if self.vlan_exist: + # delete the vlan + self.undo_config_vlan(self.vlan_id) + elif self.vlan_range and self.vlans_list_change: + self.delete_vlan_batch(self.vlans_list_change) + + # result + if self.vlan_id: + self.vlan_attr_end = self.get_vlan_attr(self.vlan_id) + + self.get_existing() + self.get_proposed() + self.get_end_state() + + self.results['changed'] = self.changed + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ module main """ + + argument_spec = dict( + vlan_id=dict(required=False), + vlan_range=dict(required=False, type='str'), + name=dict(required=False, type='str'), + description=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + + argument_spec.update(ce_argument_spec) + vlancfg = Vlan(argument_spec) + vlancfg.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf.py new file mode 100644 index 00000000..f3293c9a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf +short_description: Manages VPN instance on HUAWEI CloudEngine switches. +description: + - Manages VPN instance of HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If I(state=absent), the route will be removed, regardless of the non-required options. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance, the length of vrf name is 1 - 31, i.e. "test", but can not be C(_public_). + required: true + description: + description: + - Description of the vrf, the string length is 1 - 242 . + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Vrf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config a vpn install named vpna, description is test + community.network.ce_vrf: + vrf: vpna + description: test + state: present + provider: "{{ cli }}" + - name: Delete a vpn install named vpna + community.network.ce_vrf: + vrf: vpna + state: absent + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"vrf": "vpna", + "description": "test", + "state": "present"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"vrf": "vpna", + "description": "test", + "present": "present"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip vpn-instance vpna", + "description test"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VRF = """ + + + + + + + + + + + + +""" + +CE_NC_CREATE_VRF = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_VRF = """ + + + + + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +class Vrf(object): + """Manage vpn instance""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.description: + self.updates_cmd.append('description %s' % (self.description)) + else: + self.updates_cmd.append('undo ip vpn-instance %s' % (self.vrf)) + + def get_vrf(self): + """ check if vrf is need to change""" + + getxmlstr = CE_NC_GET_VRF + xml_str = get_nc_config(self.module, getxmlstr) + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + vpn_instances = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance") + if vpn_instances: + for vpn_instance in vpn_instances: + if vpn_instance.find('vrfName').text == self.vrf: + if vpn_instance.find('vrfDescription').text == self.description: + if self.state == "present": + return False + else: + return True + else: + return True + return self.state == "present" + else: + return self.state == "present" + + def check_params(self): + """Check all input params""" + + # vrf and description check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if len(self.vrf) < 1 or len(self.vrf) > 31: + self.module.fail_json( + msg='Error: The vrf name length must between 1 and 242.') + if self.description: + if len(self.description) < 1 or len(self.description) > 242: + self.module.fail_json( + msg='Error: The vrf description length must between 1 and 242.') + + def operate_vrf(self): + """config/delete vrf""" + if not self.changed: + return + if self.state == "present": + if self.description is None: + configxmlstr = CE_NC_CREATE_VRF % (self.vrf, '') + else: + configxmlstr = CE_NC_CREATE_VRF % (self.vrf, self.description) + else: + configxmlstr = CE_NC_DELETE_VRF % (self.vrf, self.description) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF") + + def get_proposed(self): + """get_proposed""" + + if self.state == 'present': + self.proposed['vrf'] = self.vrf + if self.description: + self.proposed['description'] = self.description + + else: + self.proposed = dict() + self.proposed['state'] = self.state + + def get_existing(self): + """get_existing""" + + change = self.get_vrf() + if change: + if self.state == 'present': + self.existing = dict() + else: + self.existing['vrf'] = self.vrf + if self.description: + self.existing['description'] = self.description + self.changed = True + else: + if self.state == 'absent': + self.existing = dict() + else: + self.existing['vrf'] = self.vrf + if self.description: + self.existing['description'] = self.description + self.changed = False + + def get_end_state(self): + """get_end_state""" + + change = self.get_vrf() + if not change: + if self.state == 'present': + self.end_state['vrf'] = self.vrf + if self.description: + self.end_state['description'] = self.description + else: + self.end_state = dict() + else: + if self.state == 'present': + self.end_state = dict() + else: + self.end_state['vrf'] = self.vrf + if self.description: + self.end_state['description'] = self.description + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_vrf() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + description=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = Vrf(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_af.py new file mode 100644 index 00000000..05a205e6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_af.py @@ -0,0 +1,848 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf_af +short_description: Manages VPN instance address family on HUAWEI CloudEngine switches. +description: + - Manages VPN instance address family of HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If I(state=absent), the vrf will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance. + required: true + vrf_aftype: + description: + - VPN instance address family. + choices: ['v4','v6'] + default: v4 + route_distinguisher: + description: + - VPN instance route distinguisher,the RD used to distinguish same route prefix from different vpn. + The RD must be setted before setting vpn_target_value. + vpn_target_state: + description: + - Manage the state of the vpn target. + choices: ['present','absent'] + vpn_target_type: + description: + - VPN instance vpn target type. + choices: ['export_extcommunity', 'import_extcommunity'] + vpn_target_value: + description: + - VPN instance target value. Such as X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295> + or number<0-65535>.number<0-65535>:number<0-65535> or number<65536-4294967295>:number<0-65535> + but not support 0:0 and 0.0:0. + evpn: + description: + - Is extend vpn or normal vpn. + type: bool + default: 'no' + state: + description: + - Manage the state of the af. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Vrf af module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config vpna, set address family is ipv4 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + state: present + provider: "{{ cli }}" + - name: Config vpna, delete address family is ipv4 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + state: absent + provider: "{{ cli }}" + - name: Config vpna, set address family is ipv4,rd=1:1,set vpn_target_type=export_extcommunity,vpn_target_value=2:2 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + route_distinguisher: 1:1 + vpn_target_type: export_extcommunity + vpn_target_value: 2:2 + vpn_target_state: present + state: present + provider: "{{ cli }}" + - name: Config vpna, set address family is ipv4,rd=1:1,delete vpn_target_type=export_extcommunity,vpn_target_value=2:2 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + route_distinguisher: 1:1 + vpn_target_type: export_extcommunity + vpn_target_value: 2:2 + vpn_target_state: absent + state: present + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"vrf": "vpna", + "vrf_aftype": "v4", + "state": "present", + "vpn_targe_state":"absent", + "evpn": "none", + "vpn_target_type": "none", + "vpn_target_value": "none"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: { + "route_distinguisher": [ + "1:1", + "2:2" + ], + "vpn_target_type": [], + "vpn_target_value": [], + "vrf": "vpna", + "vrf_aftype": [ + "ipv4uni", + "ipv6uni" + ] + } +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: { + "route_distinguisher": [ + "1:1", + "2:2" + ], + "vpn_target_type": [ + "import_extcommunity", + "3:3" + ], + "vpn_target_value": [], + "vrf": "vpna", + "vrf_aftype": [ + "ipv4uni", + "ipv6uni" + ] + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "ip vpn-instance vpna", + "vpn-target 3:3 import_extcommunity" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_VRF = """ + + + + + + + + + + + + +""" + +CE_NC_GET_VRF_AF = """ + + + + + + %s + + + + %s + + + + + + + +""" + +CE_NC_DELETE_VRF_AF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_CREATE_VRF_AF = """ + + + + + %s + + + %s + %s%s + + + + + +""" +CE_NC_CREATE_VRF_TARGET = """ + + + %s + %s + + +""" + +CE_NC_DELETE_VRF_TARGET = """ + + + %s + %s + + +""" + +CE_NC_GET_VRF_TARGET = """ + + + + + + +""" + +CE_NC_CREATE_EXTEND_VRF_TARGET = """ + + + %s + %s + evpn + + +""" + +CE_NC_DELETE_EXTEND_VRF_TARGET = """ + + + %s + %s + evpn + + +""" + +CE_NC_GET_EXTEND_VRF_TARGET = """ + + + + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_value(vrf_targe_value): + """check if the vrf target value is valid""" + + each_num = None + if len(vrf_targe_value) > 21 or len(vrf_targe_value) < 3: + return False + if vrf_targe_value.find(':') == -1: + return False + elif vrf_targe_value == '0:0': + return False + elif vrf_targe_value == '0.0:0': + return False + else: + value_list = vrf_targe_value.split(':') + if value_list[0].find('.') != -1: + if not value_list[1].isdigit(): + return False + if int(value_list[1]) > 65535: + return False + value = value_list[0].split('.') + if len(value) == 4: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + elif len(value) == 2: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + else: + return False + elif not value_list[0].isdigit(): + return False + elif not value_list[1].isdigit(): + return False + elif int(value_list[0]) < 65536 and int(value_list[1]) < 4294967296: + return True + elif int(value_list[0]) > 65535 and int(value_list[0]) < 4294967296: + return bool(int(value_list[1]) < 65536) + else: + return False + + +class VrfAf(object): + """manage the vrf address family and export/import target""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.vrf_aftype = self.module.params['vrf_aftype'] + if self.vrf_aftype == 'v4': + self.vrf_aftype = 'ipv4uni' + else: + self.vrf_aftype = 'ipv6uni' + self.route_distinguisher = self.module.params['route_distinguisher'] + self.evpn = self.module.params['evpn'] + self.vpn_target_type = self.module.params['vpn_target_type'] + self.vpn_target_value = self.module.params['vpn_target_value'] + self.vpn_target_state = self.module.params['vpn_target_state'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.vpn_target_changed = False + self.vrf_af_type_changed = False + self.vrf_rd_changed = False + self.vrf_af_info = dict() + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def is_vrf_af_exist(self): + """is vrf address family exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + return True + else: + continue + return False + + def get_exist_rd(self): + """get exist route distinguisher """ + + if not self.vrf_af_info: + return None + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if vrf_af_ele["vrfRD"] is None: + return None + else: + return vrf_af_ele["vrfRD"] + else: + continue + return None + + def is_vrf_rd_exist(self): + """is vrf route distinguisher exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if vrf_af_ele["vrfRD"] is None: + return False + if self.route_distinguisher is not None: + return bool(vrf_af_ele["vrfRD"] == self.route_distinguisher) + else: + return True + else: + continue + return False + + def is_vrf_rt_exist(self): + """is vpn target exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if self.evpn is False: + if not vrf_af_ele.get("vpnTargets"): + return False + for vpn_target in vrf_af_ele.get("vpnTargets"): + if vpn_target["vrfRTType"] == self.vpn_target_type \ + and vpn_target["vrfRTValue"] == self.vpn_target_value: + return True + else: + continue + else: + if not vrf_af_ele.get("evpnTargets"): + return False + for evpn_target in vrf_af_ele.get("evpnTargets"): + if evpn_target["vrfRTType"] == self.vpn_target_type \ + and evpn_target["vrfRTValue"] == self.vpn_target_value: + return True + else: + continue + else: + continue + return False + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + if self.vpn_target_type: + if self.vpn_target_type == "export_extcommunity": + vpn_target_type = "export-extcommunity" + else: + vpn_target_type = "import-extcommunity" + if self.state == "present": + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.vrf_aftype == 'ipv4uni': + self.updates_cmd.append('ipv4-family') + elif self.vrf_aftype == 'ipv6uni': + self.updates_cmd.append('ipv6-family') + if self.route_distinguisher: + if not self.is_vrf_rd_exist(): + self.updates_cmd.append( + 'route-distinguisher %s' % self.route_distinguisher) + else: + if self.get_exist_rd() is not None: + self.updates_cmd.append( + 'undo route-distinguisher %s' % self.get_exist_rd()) + if self.vpn_target_state == "present": + if not self.is_vrf_rt_exist(): + if self.evpn is False: + self.updates_cmd.append( + 'vpn-target %s %s' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append( + 'vpn-target %s %s evpn' % (self.vpn_target_value, vpn_target_type)) + elif self.vpn_target_state == "absent": + if self.is_vrf_rt_exist(): + if self.evpn is False: + self.updates_cmd.append( + 'undo vpn-target %s %s' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append( + 'undo vpn-target %s %s evpn' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.vrf_aftype == 'ipv4uni': + self.updates_cmd.append('undo ipv4-family') + elif self.vrf_aftype == 'ipv6uni': + self.updates_cmd.append('undo ipv6-family') + + def get_vrf(self): + """ check if vrf is need to change""" + + getxmlstr = CE_NC_GET_VRF + xmlstr_new_1 = (self.vrf.lower()) + + xml_str = get_nc_config(self.module, getxmlstr) + re_find_1 = re.findall( + r'.*(.*).*', xml_str.lower()) + + if re_find_1 is None: + return False + + return xmlstr_new_1 in re_find_1 + + def get_vrf_af(self): + """ check if vrf is need to change""" + + self.vrf_af_info["vpnInstAF"] = list() + if self.evpn is True: + getxmlstr = CE_NC_GET_VRF_AF % ( + self.vrf, CE_NC_GET_EXTEND_VRF_TARGET) + else: + getxmlstr = CE_NC_GET_VRF_AF % (self.vrf, CE_NC_GET_VRF_TARGET) + + xml_str = get_nc_config(self.module, getxmlstr) + + if 'data/' in xml_str: + return self.state == 'present' + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # get the vpn address family and RD text + vrf_addr_types = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance/vpnInstAFs/vpnInstAF") + if vrf_addr_types: + for vrf_addr_type in vrf_addr_types: + vrf_af_info = dict() + for vrf_addr_type_ele in vrf_addr_type: + if vrf_addr_type_ele.tag in ["vrfName", "afType", "vrfRD"]: + vrf_af_info[vrf_addr_type_ele.tag] = vrf_addr_type_ele.text + if vrf_addr_type_ele.tag == 'vpnTargets': + vrf_af_info["vpnTargets"] = list() + for rtargets in vrf_addr_type_ele: + rt_dict = dict() + for rtarget in rtargets: + if rtarget.tag in ["vrfRTValue", "vrfRTType"]: + rt_dict[rtarget.tag] = rtarget.text + vrf_af_info["vpnTargets"].append(rt_dict) + if vrf_addr_type_ele.tag == 'exVpnTargets': + vrf_af_info["evpnTargets"] = list() + for rtargets in vrf_addr_type_ele: + rt_dict = dict() + for rtarget in rtargets: + if rtarget.tag in ["vrfRTValue", "vrfRTType"]: + rt_dict[rtarget.tag] = rtarget.text + vrf_af_info["evpnTargets"].append(rt_dict) + self.vrf_af_info["vpnInstAF"].append(vrf_af_info) + + def check_params(self): + """Check all input params""" + + # vrf and description check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if not self.get_vrf(): + self.module.fail_json( + msg='Error: The vrf name do not exist.') + if self.state == 'present': + if self.route_distinguisher: + if not is_valid_value(self.route_distinguisher): + self.module.fail_json(msg='Error:The vrf route distinguisher length must between 3 ~ 21,' + 'i.e. X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295>' + 'or number<0-65535>.number<0-65535>:number<0-65535>' + 'or number<65536-4294967295>:number<0-65535>' + ' but not be 0:0 or 0.0:0.') + if not self.vpn_target_state: + if self.vpn_target_value or self.vpn_target_type: + self.module.fail_json( + msg='Error: The vpn target state should be exist.') + if self.vpn_target_state: + if not self.vpn_target_value or not self.vpn_target_type: + self.module.fail_json( + msg='Error: The vpn target value and type should be exist.') + if self.vpn_target_value: + if not is_valid_value(self.vpn_target_value): + self.module.fail_json(msg='Error:The vrf target value length must between 3 ~ 21,' + 'i.e. X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295>' + 'or number<0-65535>.number<0-65535>:number<0-65535>' + 'or number<65536-4294967295>:number<0-65535>' + ' but not be 0:0 or 0.0:0.') + + def operate_vrf_af(self): + """config/delete vrf""" + + vrf_target_operate = '' + if self.route_distinguisher is None: + route_d = '' + else: + route_d = self.route_distinguisher + + if self.state == 'present': + if self.vrf_aftype: + if self.is_vrf_af_exist(): + self.vrf_af_type_changed = False + else: + self.vrf_af_type_changed = True + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + else: + self.vrf_af_type_changed = bool(self.is_vrf_af_exist()) + + if self.vpn_target_state == 'present': + if self.evpn is False and not self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_CREATE_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + if self.evpn is True and not self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_CREATE_EXTEND_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + elif self.vpn_target_state == 'absent': + if self.evpn is False and self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_DELETE_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + if self.evpn is True and self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_DELETE_EXTEND_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + else: + if self.route_distinguisher: + if not self.is_vrf_rd_exist(): + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vrf_rd_changed = True + else: + self.vrf_rd_changed = False + else: + if self.is_vrf_rd_exist(): + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vrf_rd_changed = True + else: + self.vrf_rd_changed = False + if not self.vrf_rd_changed and not self.vrf_af_type_changed and not self.vpn_target_changed: + self.changed = False + else: + self.changed = True + else: + if self.is_vrf_af_exist(): + configxmlstr = CE_NC_DELETE_VRF_AF % ( + self.vrf, self.vrf_aftype) + self.changed = True + else: + self.changed = False + + if not self.changed: + return + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + + def get_proposed(self): + """get_proposed""" + + if self.state == 'present': + self.proposed['vrf'] = self.vrf + if self.vrf_aftype is None: + self.proposed['vrf_aftype'] = 'ipv4uni' + else: + self.proposed['vrf_aftype'] = self.vrf_aftype + if self.route_distinguisher is not None: + self.proposed['route_distinguisher'] = self.route_distinguisher + else: + self.proposed['route_distinguisher'] = list() + if self.vpn_target_state == 'present': + self.proposed['evpn'] = self.evpn + self.proposed['vpn_target_type'] = self.vpn_target_type + self.proposed['vpn_target_value'] = self.vpn_target_value + else: + self.proposed['vpn_target_type'] = list() + self.proposed['vpn_target_value'] = list() + else: + self.proposed = dict() + self.proposed['state'] = self.state + self.proposed['vrf'] = self.vrf + self.proposed['vrf_aftype'] = list() + self.proposed['route_distinguisher'] = list() + self.proposed['vpn_target_value'] = list() + self.proposed['vpn_target_type'] = list() + + def get_existing(self): + """get_existing""" + + self.get_vrf_af() + self.existing['vrf'] = self.vrf + self.existing['vrf_aftype'] = list() + self.existing['route_distinguisher'] = list() + self.existing['vpn_target_value'] = list() + self.existing['vpn_target_type'] = list() + self.existing['evpn_target_value'] = list() + self.existing['evpn_target_type'] = list() + if self.vrf_af_info["vpnInstAF"] is None: + return + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + self.existing['vrf_aftype'].append(vrf_af_ele["afType"]) + self.existing['route_distinguisher'].append( + vrf_af_ele["vrfRD"]) + if vrf_af_ele.get("vpnTargets"): + for vpn_target in vrf_af_ele.get("vpnTargets"): + self.existing['vpn_target_type'].append( + vpn_target["vrfRTType"]) + self.existing['vpn_target_value'].append( + vpn_target["vrfRTValue"]) + if vrf_af_ele.get("evpnTargets"): + for evpn_target in vrf_af_ele.get("evpnTargets"): + self.existing['evpn_target_type'].append( + evpn_target["vrfRTType"]) + self.existing['evpn_target_value'].append( + evpn_target["vrfRTValue"]) + + def get_end_state(self): + """get_end_state""" + + self.get_vrf_af() + self.end_state['vrf'] = self.vrf + self.end_state['vrf_aftype'] = list() + self.end_state['route_distinguisher'] = list() + self.end_state['vpn_target_value'] = list() + self.end_state['vpn_target_type'] = list() + self.end_state['evpn_target_value'] = list() + self.end_state['evpn_target_type'] = list() + if self.vrf_af_info["vpnInstAF"] is None: + return + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + self.end_state['vrf_aftype'].append(vrf_af_ele["afType"]) + self.end_state['route_distinguisher'].append(vrf_af_ele["vrfRD"]) + if vrf_af_ele.get("vpnTargets"): + for vpn_target in vrf_af_ele.get("vpnTargets"): + self.end_state['vpn_target_type'].append( + vpn_target["vrfRTType"]) + self.end_state['vpn_target_value'].append( + vpn_target["vrfRTValue"]) + if vrf_af_ele.get("evpnTargets"): + for evpn_target in vrf_af_ele.get("evpnTargets"): + self.end_state['evpn_target_type'].append( + evpn_target["vrfRTType"]) + self.end_state['evpn_target_value'].append( + evpn_target["vrfRTValue"]) + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_vrf_af() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + vrf_aftype=dict(choices=['v4', 'v6'], + default='v4', required=False), + route_distinguisher=dict(required=False, type='str'), + evpn=dict(type='bool', default=False), + vpn_target_type=dict( + choices=['export_extcommunity', 'import_extcommunity'], required=False), + vpn_target_value=dict(required=False, type='str'), + vpn_target_state=dict(choices=['absent', 'present'], required=False), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = VrfAf(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_interface.py new file mode 100644 index 00000000..10a207ce --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrf_interface.py @@ -0,0 +1,517 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf_interface +short_description: Manages interface specific VPN configuration on HUAWEI CloudEngine switches. +description: + - Manages interface specific VPN configuration of HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that a VPN instance has been created and the IPv4 address family has been enabled for the VPN instance. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance, the length of vrf name is 1 ~ 31, i.e. "test", but can not be C(_public_). + required: true + vpn_interface: + description: + - An interface that can binding VPN instance, i.e. 40GE1/0/22, Vlanif10. + Must be fully qualified interface name. + Interface types, such as 10GE, 40GE, 100GE, LoopBack, MEth, Tunnel, Vlanif.... + required: true + state: + description: + - Manage the state of the resource. + required: false + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: VRF interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure a VPN instance for the interface" + community.network.ce_vrf_interface: + vpn_interface: 40GE1/0/2 + vrf: test + state: present + provider: "{{ cli }}" + + - name: "Disable the association between a VPN instance and an interface" + community.network.ce_vrf_interface: + vpn_interface: 40GE1/0/2 + vrf: test + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "state": "present", + "vpn_interface": "40GE2/0/17", + "vrf": "jss" + } +existing: + description: k/v pairs of existing attributes on the interface + returned: verbose mode + type: dict + sample: { + "vpn_interface": "40GE2/0/17", + "vrf": null + } +end_state: + description: k/v pairs of end attributes on the interface + returned: verbose mode + type: dict + sample: { + "vpn_interface": "40GE2/0/17", + "vrf": "jss" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "ip binding vpn-instance jss", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config + +CE_NC_GET_VRF = """ + + + + + + %s + + + + + +""" + +CE_NC_GET_VRF_INTERFACE = """ + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_VRF_INTERFACE = """ + + + + + + %s + + + %s + + + + + + + +""" + +CE_NC_GET_INTF = """ + + + + + %s + + + + + +""" + +CE_NC_DEL_INTF_VPN = """ + + + + + + %s + + + %s + + + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class VrfInterface(object): + """Manage vpn instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.vpn_interface = self.module.params['vpn_interface'] + self.vpn_interface = self.vpn_interface.upper().replace(' ', '') + self.state = self.module.params['state'] + self.intf_info = dict() + self.intf_info['isL2SwitchPort'] = None + self.intf_info['vrfName'] = None + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + + required_one_of = [("vrf", "vpn_interface")] + self.module = AnsibleModule( + argument_spec=self.spec, required_one_of=required_one_of, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_update_cmd(self): + """ get updated command""" + + if self.conf_exist: + return + + if self.state == 'absent': + self.updates_cmd.append( + "undo ip binding vpn-instance %s" % self.vrf) + return + + if self.vrf != self.intf_info['vrfName']: + self.updates_cmd.append("ip binding vpn-instance %s" % self.vrf) + + return + + def check_params(self): + """Check all input params""" + + if not self.is_vrf_exist(): + self.module.fail_json( + msg='Error: The VPN instance is not existed.') + + if self.state == 'absent': + if self.vrf != self.intf_info['vrfName']: + self.module.fail_json( + msg='Error: The VPN instance is not bound to the interface.') + + if self.intf_info['isL2SwitchPort'] == 'true': + self.module.fail_json( + msg='Error: L2Switch Port can not binding a VPN instance.') + + # interface type check + if self.vpn_interface: + intf_type = get_interface_type(self.vpn_interface) + if not intf_type: + self.module.fail_json( + msg='Error: interface name of %s' + ' is error.' % self.vpn_interface) + + # vrf check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if len(self.vrf) < 1 or len(self.vrf) > 31: + self.module.fail_json( + msg='Error: The vrf name length must be between 1 and 31.') + + def get_interface_vpn_name(self, vpninfo, vpn_name): + """ get vpn instance name""" + + l3vpn_if = vpninfo.findall("l3vpnIf") + for l3vpn_ifinfo in l3vpn_if: + for ele in l3vpn_ifinfo: + if ele.tag in ['ifName']: + if ele.text.lower() == self.vpn_interface.lower(): + self.intf_info['vrfName'] = vpn_name + + def get_interface_vpn(self): + """ get the VPN instance associated with the interface""" + + xml_str = CE_NC_GET_VRF_INTERFACE + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global vrf interface info + root = ElementTree.fromstring(xml_str) + vpns = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance") + if vpns: + for vpnele in vpns: + vpn_name = None + for vpninfo in vpnele: + if vpninfo.tag == 'vrfName': + vpn_name = vpninfo.text + if vpninfo.tag == 'l3vpnIfs': + self.get_interface_vpn_name(vpninfo, vpn_name) + + return + + def is_vrf_exist(self): + """ judge whether the VPN instance is existed""" + + conf_str = CE_NC_GET_VRF % self.vrf + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return False + + return True + + def get_intf_conf_info(self): + """ get related configuration of the interface""" + + conf_str = CE_NC_GET_INTF % self.vpn_interface + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return + + # get interface base info + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + interface = root.find("ifm/interfaces/interface") + if interface: + for eles in interface: + if eles.tag in ["isL2SwitchPort"]: + self.intf_info[eles.tag] = eles.text + + self.get_interface_vpn() + return + + def get_existing(self): + """get existing config""" + + self.existing = dict(vrf=self.intf_info['vrfName'], + vpn_interface=self.vpn_interface) + + def get_proposed(self): + """get_proposed""" + + self.proposed = dict(vrf=self.vrf, + vpn_interface=self.vpn_interface, + state=self.state) + + def get_end_state(self): + """get_end_state""" + + self.intf_info['vrfName'] = None + self.get_intf_conf_info() + + self.end_state = dict(vrf=self.intf_info['vrfName'], + vpn_interface=self.vpn_interface) + + def show_result(self): + """ show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """ judge whether configuration has existed""" + + if self.state == 'absent': + return False + + delta = set(self.proposed.items()).difference( + self.existing.items()) + delta = dict(delta) + if len(delta) == 1 and delta['state']: + return True + + return False + + def config_interface_vrf(self): + """ configure VPN instance of the interface""" + + if not self.conf_exist and self.state == 'present': + + xml_str = CE_NC_MERGE_VRF_INTERFACE % ( + self.vrf, self.vpn_interface) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "VRF_INTERFACE_CONFIG") + self.changed = True + elif self.state == 'absent': + xml_str = CE_NC_DEL_INTF_VPN % (self.vrf, self.vpn_interface) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "DEL_VRF_INTERFACE_CONFIG") + self.changed = True + + def work(self): + """execute task""" + + self.get_intf_conf_info() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_interface_vrf() + + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + vpn_interface=dict(required=True, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + vrf_intf = VrfInterface(argument_spec) + vrf_intf.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrrp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrrp.py new file mode 100644 index 00000000..9d9fc349 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vrrp.py @@ -0,0 +1,1327 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrrp +short_description: Manages VRRP interfaces on HUAWEI CloudEngine devices. +description: + - Manages VRRP interface attributes on HUAWEI CloudEngine devices. +author: + - Li Yanfeng (@numone213) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Name of an interface. The value is a string of 1 to 63 characters. + vrid: + description: + - VRRP backup group ID. + The value is an integer ranging from 1 to 255. + default: present + virtual_ip : + description: + - Virtual IP address. The value is a string of 0 to 255 characters. + vrrp_type: + description: + - Type of a VRRP backup group. + type: str + choices: ['normal', 'member', 'admin'] + admin_ignore_if_down: + description: + - mVRRP ignores an interface Down event. + type: bool + default: 'false' + admin_vrid: + description: + - Tracked mVRRP ID. The value is an integer ranging from 1 to 255. + admin_interface: + description: + - Tracked mVRRP interface name. The value is a string of 1 to 63 characters. + admin_flowdown: + description: + - Disable the flowdown function for service VRRP. + type: bool + default: 'false' + priority: + description: + - Configured VRRP priority. + The value ranges from 1 to 254. The default value is 100. A larger value indicates a higher priority. + version: + description: + - VRRP version. The default version is v2. + type: str + choices: ['v2','v3'] + advertise_interval: + description: + - Configured interval between sending advertisements, in milliseconds. + Only the master router sends VRRP advertisements. The default value is 1000 milliseconds. + preempt_timer_delay: + description: + - Preemption delay. + The value is an integer ranging from 0 to 3600. The default value is 0. + gratuitous_arp_interval: + description: + - Interval at which gratuitous ARP packets are sent, in seconds. + The value ranges from 30 to 1200.The default value is 300. + recover_delay: + description: + - Delay in recovering after an interface goes Up. + The delay is used for interface flapping suppression. + The value is an integer ranging from 0 to 3600. + The default value is 0 seconds. + holding_multiplier: + description: + - The configured holdMultiplier.The value is an integer ranging from 3 to 10. The default value is 3. + auth_mode: + description: + - Authentication type used for VRRP packet exchanges between virtual routers. + The values are noAuthentication, simpleTextPassword, md5Authentication. + The default value is noAuthentication. + type: str + choices: ['simple','md5','none'] + is_plain: + description: + - Select the display mode of an authentication key. + By default, an authentication key is displayed in ciphertext. + type: bool + default: 'false' + auth_key: + description: + - This object is set based on the authentication type. + When noAuthentication is specified, the value is empty. + When simpleTextPassword or md5Authentication is specified, the value is a string of 1 to 8 characters + in plaintext and displayed as a blank text for security. + fast_resume: + description: + - mVRRP's fast resume mode. + type: str + choices: ['enable','disable'] + state: + description: + - Specify desired state of the resource. + type: str + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Vrrp module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + tasks: + - name: Set vrrp version + community.network.ce_vrrp: + version: v3 + provider: "{{ cli }}" + - name: Set vrrp gratuitous-arp interval + community.network.ce_vrrp: + gratuitous_arp_interval: 40 + mlag_id: 4 + provider: "{{ cli }}" + - name: Set vrrp recover-delay + community.network.ce_vrrp: + recover_delay: 10 + provider: "{{ cli }}" + - name: Set vrrp vrid virtual-ip + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + virtual_ip: 10.14.2.7 + provider: "{{ cli }}" + - name: Set vrrp vrid admin + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + vrrp_type: admin + provider: "{{ cli }}" + - name: Set vrrp vrid fast_resume + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + fast_resume: enable + provider: "{{ cli }}" + - name: Set vrrp vrid holding-multiplier + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + holding_multiplier: 4 + provider: "{{ cli }}" + - name: Set vrrp vrid preempt timer delay + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + preempt_timer_delay: 10 + provider: "{{ cli }}" + - name: Set vrrp vrid admin-vrrp + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + admin_interface: 40GE2/0/9 + admin_vrid: 2 + vrrp_type: member + provider: "{{ cli }}" + - name: Set vrrp vrid authentication-mode + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + is_plain: true + auth_mode: simple + auth_key: aaa + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "auth_key": "aaa", + "auth_mode": "simple", + "interface": "40GE2/0/8", + "is_plain": true, + "state": "present", + "vrid": "1" + } +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { + "auth_mode": "none", + "interface": "40GE2/0/8", + "is_plain": "false", + "vrid": "1", + "vrrp_type": "normal" + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: { + "auth_mode": "simple", + "interface": "40GE2/0/8", + "is_plain": "true", + "vrid": "1", + "vrrp_type": "normal" + } +updates: + description: command sent to the device + returned: always + type: list + sample: { "interface 40GE2/0/8", + "vrrp vrid 1 authentication-mode simple plain aaa"} +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VRRP_GROUP_INFO = """ + + + + + %s + %s + + + + +""" + +CE_NC_SET_VRRP_GROUP_INFO_HEAD = """ + + + + + %s + %s +""" +CE_NC_SET_VRRP_GROUP_INFO_TAIL = """ + + + + +""" +CE_NC_GET_VRRP_GLOBAL_INFO = """ + + + + + + + + + + +""" + +CE_NC_SET_VRRP_GLOBAL_HEAD = """ + + + +""" +CE_NC_SET_VRRP_GLOBAL_TAIL = """ + + + +""" + +CE_NC_GET_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + + + + + + + +""" +CE_NC_CREATE_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + %s + + + + + + +""" +CE_NC_DELETE_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + %s + + + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('NULL'): + iftype = 'null' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + else: + return None + + return iftype.lower() + + +class Vrrp(object): + """ + Manages Manages vrrp information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.interface = self.module.params['interface'] + self.vrid = self.module.params['vrid'] + self.virtual_ip = self.module.params['virtual_ip'] + self.vrrp_type = self.module.params['vrrp_type'] + self.admin_ignore_if_down = 'false' if self.module.params['admin_ignore_if_down'] is False else 'true' + self.admin_vrid = self.module.params['admin_vrid'] + self.admin_interface = self.module.params['admin_interface'] + self.admin_flowdown = 'false' if self.module.params['admin_flowdown'] is False else 'true' + self.priority = self.module.params['priority'] + self.version = self.module.params['version'] + self.advertise_interval = self.module.params['advertise_interval'] + self.preempt_timer_delay = self.module.params['preempt_timer_delay'] + self.gratuitous_arp_interval = self.module.params[ + 'gratuitous_arp_interval'] + self.recover_delay = self.module.params['recover_delay'] + self.holding_multiplier = self.module.params['holding_multiplier'] + self.auth_mode = self.module.params['auth_mode'] + self.is_plain = 'false' if self.module.params['is_plain'] is False else 'true' + self.auth_key = self.module.params['auth_key'] + self.fast_resume = self.module.params['fast_resume'] + self.state = self.module.params['state'] + + # vrrp info + self.vrrp_global_info = None + self.virtual_ip_info = None + self.vrrp_group_info = None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_virtual_ip_info(self): + """ get vrrp virtual ip info.""" + virtual_ip_info = dict() + conf_str = CE_NC_GET_VRRP_VIRTUAL_IP_INFO % (self.vrid, self.interface) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return virtual_ip_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + virtual_ip_info["vrrpVirtualIpInfos"] = list() + root = ElementTree.fromstring(xml_str) + vrrp_virtual_ip_infos = root.findall( + "vrrp/vrrpGroups/vrrpGroup/virtualIps/virtualIp") + if vrrp_virtual_ip_infos: + for vrrp_virtual_ip_info in vrrp_virtual_ip_infos: + virtual_ip_dict = dict() + for ele in vrrp_virtual_ip_info: + if ele.tag in ["virtualIpAddress"]: + virtual_ip_dict[ele.tag] = ele.text + virtual_ip_info["vrrpVirtualIpInfos"].append( + virtual_ip_dict) + return virtual_ip_info + + def get_vrrp_global_info(self): + """ get vrrp global info.""" + + vrrp_global_info = dict() + conf_str = CE_NC_GET_VRRP_GLOBAL_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vrrp_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "vrrp/vrrpGlobalCfg") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["gratuitousArpTimeOut", "gratuitousArpFlag", "recoverDelay", "version"]: + vrrp_global_info[site.tag] = site.text + return vrrp_global_info + + def get_vrrp_group_info(self): + """ get vrrp group info.""" + + vrrp_group_info = dict() + conf_str = CE_NC_GET_VRRP_GROUP_INFO % (self.interface, self.vrid) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vrrp_group_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "vrrp/vrrpGroups/vrrpGroup") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["ifName", "vrrpId", "priority", "advertiseInterval", "preemptMode", "delayTime", + "authenticationMode", "authenticationKey", "vrrpType", "adminVrrpId", + "adminIfName", "adminIgnoreIfDown", "isPlain", "unflowdown", "fastResume", + "holdMultiplier"]: + vrrp_group_info[site.tag] = site.text + return vrrp_group_info + + def check_params(self): + """Check all input params""" + + # interface check + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + # vrid check + if self.vrid: + if not self.vrid.isdigit(): + self.module.fail_json( + msg='Error: The value of vrid is an integer.') + if int(self.vrid) < 1 or int(self.vrid) > 255: + self.module.fail_json( + msg='Error: The value of vrid ranges from 1 to 255.') + + # virtual_ip check + if self.virtual_ip: + if not is_valid_address(self.virtual_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.virtual_ip) + + # admin_vrid check + if self.admin_vrid: + if not self.admin_vrid.isdigit(): + self.module.fail_json( + msg='Error: The value of admin_vrid is an integer.') + if int(self.admin_vrid) < 1 or int(self.admin_vrid) > 255: + self.module.fail_json( + msg='Error: The value of admin_vrid ranges from 1 to 255.') + + # admin_interface check + if self.admin_interface: + intf_type = get_interface_type(self.admin_interface) + if not intf_type: + self.module.fail_json( + msg='Error: Admin interface name of %s ' + 'is error.' % self.admin_interface) + + # priority check + if self.priority: + if not self.priority.isdigit(): + self.module.fail_json( + msg='Error: The value of priority is an integer.') + if int(self.priority) < 1 or int(self.priority) > 254: + self.module.fail_json( + msg='Error: The value of priority ranges from 1 to 254. The default value is 100.') + + # advertise_interval check + if self.advertise_interval: + if not self.advertise_interval.isdigit(): + self.module.fail_json( + msg='Error: The value of advertise_interval is an integer.') + if int(self.advertise_interval) < 1000 or int(self.advertise_interval) > 255000: + self.module.fail_json( + msg='Error: The value of advertise_interval ranges from 1000 to 255000 milliseconds. The default value is 1000 milliseconds.') + if int(self.advertise_interval) % 1000 != 0: + self.module.fail_json( + msg='Error: The advertisement interval value of VRRP must be a multiple of 1000 milliseconds.') + # preempt_timer_delay check + if self.preempt_timer_delay: + if not self.preempt_timer_delay.isdigit(): + self.module.fail_json( + msg='Error: The value of preempt_timer_delay is an integer.') + if int(self.preempt_timer_delay) < 1 or int(self.preempt_timer_delay) > 3600: + self.module.fail_json( + msg='Error: The value of preempt_timer_delay ranges from 1 to 3600. The default value is 0.') + + # holding_multiplier check + if self.holding_multiplier: + if not self.holding_multiplier.isdigit(): + self.module.fail_json( + msg='Error: The value of holding_multiplier is an integer.') + if int(self.holding_multiplier) < 3 or int(self.holding_multiplier) > 10: + self.module.fail_json( + msg='Error: The value of holding_multiplier ranges from 3 to 10. The default value is 3.') + + # auth_key check + if self.auth_key: + if len(self.auth_key) > 16 \ + or len(self.auth_key.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: The length of auth_key is not in the range from 1 to 16.') + + def is_virtual_ip_change(self): + """whether virtual ip change""" + + if not self.virtual_ip_info: + return True + + for info in self.virtual_ip_info["vrrpVirtualIpInfos"]: + if info["virtualIpAddress"] == self.virtual_ip: + return False + return True + + def is_virtual_ip_exist(self): + """whether virtual ip info exist""" + + if not self.virtual_ip_info: + return False + + for info in self.virtual_ip_info["vrrpVirtualIpInfos"]: + if info["virtualIpAddress"] == self.virtual_ip: + return True + return False + + def is_vrrp_global_info_change(self): + """whether vrrp global attribute info change""" + + if not self.vrrp_global_info: + return True + + if self.gratuitous_arp_interval: + if self.vrrp_global_info["gratuitousArpFlag"] == "false": + self.module.fail_json(msg="Error: gratuitousArpFlag is false.") + if self.vrrp_global_info["gratuitousArpTimeOut"] != self.gratuitous_arp_interval: + return True + if self.recover_delay: + if self.vrrp_global_info["recoverDelay"] != self.recover_delay: + return True + if self.version: + if self.vrrp_global_info["version"] != self.version: + return True + return False + + def is_vrrp_global_info_exist(self): + """whether vrrp global attribute info exist""" + + if self.gratuitous_arp_interval or self.recover_delay or self.version: + if self.gratuitous_arp_interval: + if self.vrrp_global_info["gratuitousArpFlag"] == "false": + self.module.fail_json( + msg="Error: gratuitousArpFlag is false.") + if self.vrrp_global_info["gratuitousArpTimeOut"] != self.gratuitous_arp_interval: + return False + if self.recover_delay: + if self.vrrp_global_info["recoverDelay"] != self.recover_delay: + return False + if self.version: + if self.vrrp_global_info["version"] != self.version: + return False + return True + + return False + + def is_vrrp_group_info_change(self): + """whether vrrp group attribute info change""" + if self.vrrp_type: + if self.vrrp_group_info["vrrpType"] != self.vrrp_type: + return True + if self.admin_ignore_if_down: + if self.vrrp_group_info["adminIgnoreIfDown"] != self.admin_ignore_if_down: + return True + if self.admin_vrid: + if self.vrrp_group_info["adminVrrpId"] != self.admin_vrid: + return True + if self.admin_interface: + if self.vrrp_group_info["adminIfName"] != self.admin_interface: + return True + if self.admin_flowdown: + if self.vrrp_group_info["unflowdown"] != self.admin_flowdown: + return True + if self.priority: + if self.vrrp_group_info["priority"] != self.priority: + return True + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + if self.vrrp_group_info["fastResume"] != fast_resume: + return True + if self.advertise_interval: + if self.vrrp_group_info["advertiseInterval"] != self.advertise_interval: + return True + if self.preempt_timer_delay: + if self.vrrp_group_info["delayTime"] != self.preempt_timer_delay: + return True + if self.holding_multiplier: + if self.vrrp_group_info["holdMultiplier"] != self.holding_multiplier: + return True + if self.auth_mode: + if self.vrrp_group_info["authenticationMode"] != self.auth_mode: + return True + if self.auth_key: + return True + if self.is_plain: + if self.vrrp_group_info["isPlain"] != self.is_plain: + return True + + return False + + def is_vrrp_group_info_exist(self): + """whether vrrp group attribute info exist""" + + if self.vrrp_type: + if self.vrrp_group_info["vrrpType"] != self.vrrp_type: + return False + if self.admin_ignore_if_down: + if self.vrrp_group_info["adminIgnoreIfDown"] != self.admin_ignore_if_down: + return False + if self.admin_vrid: + if self.vrrp_group_info["adminVrrpId"] != self.admin_vrid: + return False + if self.admin_interface: + if self.vrrp_group_info["adminIfName"] != self.admin_interface: + return False + if self.admin_flowdown: + if self.vrrp_group_info["unflowdown"] != self.admin_flowdown: + return False + if self.priority: + if self.vrrp_group_info["priority"] != self.priority: + return False + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + if self.vrrp_group_info["fastResume"] != fast_resume: + return False + if self.advertise_interval: + if self.vrrp_group_info["advertiseInterval"] != self.advertise_interval: + return False + if self.preempt_timer_delay: + if self.vrrp_group_info["delayTime"] != self.preempt_timer_delay: + return False + if self.holding_multiplier: + if self.vrrp_group_info["holdMultiplier"] != self.holding_multiplier: + return False + if self.auth_mode: + if self.vrrp_group_info["authenticationMode"] != self.auth_mode: + return False + if self.is_plain: + if self.vrrp_group_info["isPlain"] != self.is_plain: + return False + return True + + def create_virtual_ip(self): + """create virtual ip info""" + + if self.is_virtual_ip_change(): + conf_str = CE_NC_CREATE_VRRP_VIRTUAL_IP_INFO % ( + self.vrid, self.interface, self.virtual_ip) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create virtual ip info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append( + "vrrp vrid %s virtual-ip %s" % (self.vrid, self.virtual_ip)) + self.changed = True + + def delete_virtual_ip(self): + """delete virtual ip info""" + + if self.is_virtual_ip_exist(): + conf_str = CE_NC_DELETE_VRRP_VIRTUAL_IP_INFO % ( + self.vrid, self.interface, self.virtual_ip) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete virtual ip info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append( + "undo vrrp vrid %s virtual-ip %s " % (self.vrid, self.virtual_ip)) + self.changed = True + + def set_vrrp_global(self): + """set vrrp global attribute info""" + + if self.is_vrrp_global_info_change(): + conf_str = CE_NC_SET_VRRP_GLOBAL_HEAD + if self.gratuitous_arp_interval: + conf_str += "%s" % self.gratuitous_arp_interval + if self.recover_delay: + conf_str += "%s" % self.recover_delay + if self.version: + conf_str += "%s" % self.version + conf_str += CE_NC_SET_VRRP_GLOBAL_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + + if self.gratuitous_arp_interval: + self.updates_cmd.append( + "vrrp gratuitous-arp interval %s" % self.gratuitous_arp_interval) + + if self.recover_delay: + self.updates_cmd.append( + "vrrp recover-delay %s" % self.recover_delay) + + if self.version: + version = "3" + if self.version == "v2": + version = "2" + self.updates_cmd.append("vrrp version %s" % version) + self.changed = True + + def delete_vrrp_global(self): + """delete vrrp global attribute info""" + + if self.is_vrrp_global_info_exist(): + conf_str = CE_NC_SET_VRRP_GLOBAL_HEAD + if self.gratuitous_arp_interval: + if self.gratuitous_arp_interval == "120": + self.module.fail_json( + msg='Error: The default value of gratuitous_arp_interval is 120.') + gratuitous_arp_interval = "120" + conf_str += "%s" % gratuitous_arp_interval + if self.recover_delay: + if self.recover_delay == "0": + self.module.fail_json( + msg='Error: The default value of recover_delay is 0.') + recover_delay = "0" + conf_str += "%s" % recover_delay + if self.version: + if self.version == "v2": + self.module.fail_json( + msg='Error: The default value of version is v2.') + version = "v2" + conf_str += "%s" % version + conf_str += CE_NC_SET_VRRP_GLOBAL_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + if self.gratuitous_arp_interval: + self.updates_cmd.append("undo vrrp gratuitous-arp interval") + + if self.recover_delay: + self.updates_cmd.append("undo vrrp recover-delay") + + if self.version == "v3": + self.updates_cmd.append("undo vrrp version") + self.changed = True + + def set_vrrp_group(self): + """set vrrp group attribute info""" + + if self.is_vrrp_group_info_change(): + conf_str = CE_NC_SET_VRRP_GROUP_INFO_HEAD % ( + self.interface, self.vrid) + if self.vrrp_type: + conf_str += "%s" % self.vrrp_type + if self.admin_vrid: + conf_str += "%s" % self.admin_vrid + if self.admin_interface: + conf_str += "%s" % self.admin_interface + if self.admin_flowdown: + conf_str += "%s" % self.admin_flowdown + if self.priority: + conf_str += "%s" % self.priority + if self.vrrp_type == "admin": + if self.admin_ignore_if_down: + conf_str += "%s" % self.admin_ignore_if_down + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + conf_str += "%s" % fast_resume + if self.advertise_interval: + conf_str += "%s" % self.advertise_interval + if self.preempt_timer_delay: + conf_str += "%s" % self.preempt_timer_delay + if self.holding_multiplier: + conf_str += "%s" % self.holding_multiplier + if self.auth_mode: + conf_str += "%s" % self.auth_mode + if self.auth_key: + conf_str += "%s" % self.auth_key + if self.auth_mode == "simple": + conf_str += "%s" % self.is_plain + + conf_str += CE_NC_SET_VRRP_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp group attribute info failed.') + if self.interface and self.vrid: + self.updates_cmd.append("interface %s" % self.interface) + if self.vrrp_type == "admin": + if self.admin_ignore_if_down == "true": + self.updates_cmd.append( + "vrrp vrid %s admin ignore-if-down" % self.vrid) + else: + self.updates_cmd.append( + "vrrp vrid %s admin" % self.vrid) + + if self.priority: + self.updates_cmd.append( + "vrrp vrid %s priority %s" % (self.vrid, self.priority)) + + if self.fast_resume == "enable": + self.updates_cmd.append( + "vrrp vrid %s fast-resume" % self.vrid) + if self.fast_resume == "disable": + self.updates_cmd.append( + "undo vrrp vrid %s fast-resume" % self.vrid) + + if self.advertise_interval: + advertise_interval = int(self.advertise_interval) / 1000 + self.updates_cmd.append("vrrp vrid %s timer advertise %s" % ( + self.vrid, int(advertise_interval))) + + if self.preempt_timer_delay: + self.updates_cmd.append("vrrp vrid %s preempt timer delay %s" % (self.vrid, + self.preempt_timer_delay)) + + if self.holding_multiplier: + self.updates_cmd.append( + "vrrp vrid %s holding-multiplier %s" % (self.vrid, self.holding_multiplier)) + + if self.admin_vrid and self.admin_interface: + if self.admin_flowdown == "true": + self.updates_cmd.append("vrrp vrid %s track admin-vrrp interface %s vrid %s unflowdown" % + (self.vrid, self.admin_interface, self.admin_vrid)) + else: + self.updates_cmd.append("vrrp vrid %s track admin-vrrp interface %s vrid %s" % + (self.vrid, self.admin_interface, self.admin_vrid)) + + if self.auth_mode and self.auth_key: + if self.auth_mode == "simple": + if self.is_plain == "true": + self.updates_cmd.append("vrrp vrid %s authentication-mode simple plain %s" % + (self.vrid, self.auth_key)) + else: + self.updates_cmd.append("vrrp vrid %s authentication-mode simple cipher %s" % + (self.vrid, self.auth_key)) + if self.auth_mode == "md5": + self.updates_cmd.append( + "vrrp vrid %s authentication-mode md5 %s" % (self.vrid, self.auth_key)) + self.changed = True + + def delete_vrrp_group(self): + """delete vrrp group attribute info""" + + if self.is_vrrp_group_info_exist(): + conf_str = CE_NC_SET_VRRP_GROUP_INFO_HEAD % ( + self.interface, self.vrid) + if self.vrrp_type: + vrrp_type = self.vrrp_type + if self.vrrp_type == "admin": + vrrp_type = "normal" + if self.vrrp_type == "member" and self.admin_vrid and self.admin_interface: + vrrp_type = "normal" + conf_str += "%s" % vrrp_type + if self.priority: + if self.priority == "100": + self.module.fail_json( + msg='Error: The default value of priority is 100.') + priority = "100" + conf_str += "%s" % priority + + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + conf_str += "%s" % fast_resume + if self.advertise_interval: + if self.advertise_interval == "1000": + self.module.fail_json( + msg='Error: The default value of advertise_interval is 1000.') + advertise_interval = "1000" + conf_str += "%s" % advertise_interval + if self.preempt_timer_delay: + if self.preempt_timer_delay == "0": + self.module.fail_json( + msg='Error: The default value of preempt_timer_delay is 0.') + preempt_timer_delay = "0" + conf_str += "%s" % preempt_timer_delay + if self.holding_multiplier: + if self.holding_multiplier == "0": + self.module.fail_json( + msg='Error: The default value of holding_multiplier is 3.') + holding_multiplier = "3" + conf_str += "%s" % holding_multiplier + if self.auth_mode: + auth_mode = self.auth_mode + if self.auth_mode == "md5" or self.auth_mode == "simple": + auth_mode = "none" + conf_str += "%s" % auth_mode + + conf_str += CE_NC_SET_VRRP_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + if self.interface and self.vrid: + self.updates_cmd.append("interface %s" % self.interface) + if self.vrrp_type == "admin": + self.updates_cmd.append( + "undo vrrp vrid %s admin" % self.vrid) + + if self.priority: + self.updates_cmd.append( + "undo vrrp vrid %s priority" % self.vrid) + + if self.fast_resume: + self.updates_cmd.append( + "undo vrrp vrid %s fast-resume" % self.vrid) + + if self.advertise_interval: + self.updates_cmd.append( + "undo vrrp vrid %s timer advertise" % self.vrid) + + if self.preempt_timer_delay: + self.updates_cmd.append( + "undo vrrp vrid %s preempt timer delay" % self.vrid) + + if self.holding_multiplier: + self.updates_cmd.append( + "undo vrrp vrid %s holding-multiplier" % self.vrid) + + if self.admin_vrid and self.admin_interface: + self.updates_cmd.append( + "undo vrrp vrid %s track admin-vrrp" % self.vrid) + + if self.auth_mode: + self.updates_cmd.append( + "undo vrrp vrid %s authentication-mode" % self.vrid) + self.changed = True + + def get_proposed(self): + """get proposed info""" + + if self.interface: + self.proposed["interface"] = self.interface + if self.vrid: + self.proposed["vrid"] = self.vrid + if self.virtual_ip: + self.proposed["virtual_ip"] = self.virtual_ip + if self.vrrp_type: + self.proposed["vrrp_type"] = self.vrrp_type + if self.admin_vrid: + self.proposed["admin_vrid"] = self.admin_vrid + if self.admin_interface: + self.proposed["admin_interface"] = self.admin_interface + if self.admin_flowdown: + self.proposed["unflowdown"] = self.admin_flowdown + if self.admin_ignore_if_down: + self.proposed["admin_ignore_if_down"] = self.admin_ignore_if_down + if self.priority: + self.proposed["priority"] = self.priority + if self.version: + self.proposed["version"] = self.version + if self.advertise_interval: + self.proposed["advertise_interval"] = self.advertise_interval + if self.preempt_timer_delay: + self.proposed["preempt_timer_delay"] = self.preempt_timer_delay + if self.gratuitous_arp_interval: + self.proposed[ + "gratuitous_arp_interval"] = self.gratuitous_arp_interval + if self.recover_delay: + self.proposed["recover_delay"] = self.recover_delay + if self.holding_multiplier: + self.proposed["holding_multiplier"] = self.holding_multiplier + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.is_plain: + self.proposed["is_plain"] = self.is_plain + if self.auth_key: + self.proposed["auth_key"] = self.auth_key + if self.fast_resume: + self.proposed["fast_resume"] = self.fast_resume + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.gratuitous_arp_interval: + self.existing["gratuitous_arp_interval"] = self.vrrp_global_info[ + "gratuitousArpTimeOut"] + if self.version: + self.existing["version"] = self.vrrp_global_info["version"] + if self.recover_delay: + self.existing["recover_delay"] = self.vrrp_global_info[ + "recoverDelay"] + + if self.virtual_ip: + if self.virtual_ip_info: + self.existing["interface"] = self.interface + self.existing["vrid"] = self.vrid + self.existing["virtual_ip_info"] = self.virtual_ip_info[ + "vrrpVirtualIpInfos"] + + if self.vrrp_group_info: + self.existing["interface"] = self.vrrp_group_info["ifName"] + self.existing["vrid"] = self.vrrp_group_info["vrrpId"] + self.existing["vrrp_type"] = self.vrrp_group_info["vrrpType"] + if self.vrrp_type == "admin": + self.existing["admin_ignore_if_down"] = self.vrrp_group_info[ + "adminIgnoreIfDown"] + if self.admin_vrid and self.admin_interface: + self.existing["admin_vrid"] = self.vrrp_group_info[ + "adminVrrpId"] + self.existing["admin_interface"] = self.vrrp_group_info[ + "adminIfName"] + self.existing["admin_flowdown"] = self.vrrp_group_info[ + "unflowdown"] + if self.priority: + self.existing["priority"] = self.vrrp_group_info["priority"] + if self.advertise_interval: + self.existing["advertise_interval"] = self.vrrp_group_info[ + "advertiseInterval"] + if self.preempt_timer_delay: + self.existing["preempt_timer_delay"] = self.vrrp_group_info[ + "delayTime"] + if self.holding_multiplier: + self.existing["holding_multiplier"] = self.vrrp_group_info[ + "holdMultiplier"] + if self.fast_resume: + fast_resume_exist = "disable" + fast_resume = self.vrrp_group_info["fastResume"] + if fast_resume == "true": + fast_resume_exist = "enable" + self.existing["fast_resume"] = fast_resume_exist + if self.auth_mode: + self.existing["auth_mode"] = self.vrrp_group_info[ + "authenticationMode"] + self.existing["is_plain"] = self.vrrp_group_info["isPlain"] + + def get_end_state(self): + """get end state info""" + + if self.gratuitous_arp_interval or self.version or self.recover_delay: + self.vrrp_global_info = self.get_vrrp_global_info() + if self.interface and self.vrid: + if self.virtual_ip: + self.virtual_ip_info = self.get_virtual_ip_info() + if self.virtual_ip_info: + self.vrrp_group_info = self.get_vrrp_group_info() + + if self.gratuitous_arp_interval: + self.end_state["gratuitous_arp_interval"] = self.vrrp_global_info[ + "gratuitousArpTimeOut"] + if self.version: + self.end_state["version"] = self.vrrp_global_info["version"] + if self.recover_delay: + self.end_state["recover_delay"] = self.vrrp_global_info[ + "recoverDelay"] + + if self.virtual_ip: + if self.virtual_ip_info: + self.end_state["interface"] = self.interface + self.end_state["vrid"] = self.vrid + self.end_state["virtual_ip_info"] = self.virtual_ip_info[ + "vrrpVirtualIpInfos"] + + if self.vrrp_group_info: + self.end_state["interface"] = self.vrrp_group_info["ifName"] + self.end_state["vrid"] = self.vrrp_group_info["vrrpId"] + self.end_state["vrrp_type"] = self.vrrp_group_info["vrrpType"] + if self.vrrp_type == "admin": + self.end_state["admin_ignore_if_down"] = self.vrrp_group_info[ + "adminIgnoreIfDown"] + if self.admin_vrid and self.admin_interface: + self.end_state["admin_vrid"] = self.vrrp_group_info[ + "adminVrrpId"] + self.end_state["admin_interface"] = self.vrrp_group_info[ + "adminIfName"] + self.end_state["admin_flowdown"] = self.vrrp_group_info[ + "unflowdown"] + if self.priority: + self.end_state["priority"] = self.vrrp_group_info["priority"] + if self.advertise_interval: + self.end_state["advertise_interval"] = self.vrrp_group_info[ + "advertiseInterval"] + if self.preempt_timer_delay: + self.end_state["preempt_timer_delay"] = self.vrrp_group_info[ + "delayTime"] + if self.holding_multiplier: + self.end_state["holding_multiplier"] = self.vrrp_group_info[ + "holdMultiplier"] + if self.fast_resume: + fast_resume_end = "disable" + fast_resume = self.vrrp_group_info["fastResume"] + if fast_resume == "true": + fast_resume_end = "enable" + self.end_state["fast_resume"] = fast_resume_end + if self.auth_mode: + self.end_state["auth_mode"] = self.vrrp_group_info[ + "authenticationMode"] + self.end_state["is_plain"] = self.vrrp_group_info["isPlain"] + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if self.gratuitous_arp_interval or self.version or self.recover_delay: + self.vrrp_global_info = self.get_vrrp_global_info() + if self.interface and self.vrid: + self.virtual_ip_info = self.get_virtual_ip_info() + if self.virtual_ip_info: + self.vrrp_group_info = self.get_vrrp_group_info() + self.get_proposed() + self.get_existing() + + if self.gratuitous_arp_interval or self.version or self.recover_delay: + if self.state == "present": + self.set_vrrp_global() + else: + self.delete_vrrp_global() + else: + if not self.interface or not self.vrid: + self.module.fail_json( + msg='Error: interface, vrid must be config at the same time.') + + if self.interface and self.vrid: + if self.virtual_ip: + if self.state == "present": + self.create_virtual_ip() + else: + self.delete_virtual_ip() + else: + if not self.vrrp_group_info: + self.module.fail_json( + msg='Error: The VRRP group does not exist.') + if self.admin_ignore_if_down == "true": + if self.vrrp_type != "admin": + self.module.fail_json( + msg='Error: vrrpType must be admin when admin_ignore_if_down is true.') + if self.admin_interface or self.admin_vrid: + if self.vrrp_type != "member": + self.module.fail_json( + msg='Error: it binds a VRRP group to an mVRRP group, vrrp_type must be "member".') + if not self.vrrp_type or not self.interface or not self.vrid: + self.module.fail_json( + msg='Error: admin_interface admin_vrid vrrp_type interface vrid must ' + 'be config at the same time.') + if self.auth_mode == "md5" and self.is_plain == "true": + self.module.fail_json( + msg='Error: is_plain can not be True when auth_mode is md5.') + + if self.state == "present": + self.set_vrrp_group() + else: + self.delete_vrrp_group() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + interface=dict(type='str'), + vrid=dict(type='str'), + virtual_ip=dict(type='str'), + vrrp_type=dict(type='str', choices=['normal', 'member', 'admin']), + admin_ignore_if_down=dict(type='bool', default=False), + admin_vrid=dict(type='str'), + admin_interface=dict(type='str'), + admin_flowdown=dict(type='bool', default=False), + priority=dict(type='str'), + version=dict(type='str', choices=['v2', 'v3']), + advertise_interval=dict(type='str'), + preempt_timer_delay=dict(type='str'), + gratuitous_arp_interval=dict(type='str'), + recover_delay=dict(type='str'), + holding_multiplier=dict(type='str'), + auth_mode=dict(type='str', choices=['simple', 'md5', 'none']), + is_plain=dict(type='bool', default=False), + auth_key=dict(type='str', no_log=True), + fast_resume=dict(type='str', choices=['enable', 'disable']), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = Vrrp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_arp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_arp.py new file mode 100644 index 00000000..bcb1659b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_arp.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_arp +short_description: Manages ARP attributes of VXLAN on HUAWEI CloudEngine devices. +description: + - Manages ARP attributes of VXLAN on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + evn_bgp: + description: + - Enables EVN BGP. + choices: ['enable', 'disable'] + evn_source_ip: + description: + - Specifies the source address of an EVN BGP peer. + The value is in dotted decimal notation. + evn_peer_ip: + description: + - Specifies the IP address of an EVN BGP peer. + The value is in dotted decimal notation. + evn_server: + description: + - Configures the local device as the router reflector (RR) on the EVN network. + choices: ['enable', 'disable'] + evn_reflect_client: + description: + - Configures the local device as the route reflector (RR) and its peer as the client. + choices: ['enable', 'disable'] + vbdif_name: + description: + - Full name of VBDIF interface, i.e. Vbdif100. + arp_collect_host: + description: + - Enables EVN BGP or BGP EVPN to collect host information. + choices: ['enable', 'disable'] + host_collect_protocol: + description: + - Enables EVN BGP or BGP EVPN to advertise host information. + choices: ['bgp','none'] + bridge_domain_id: + description: + - Specifies a BD(bridge domain) ID. + The value is an integer ranging from 1 to 16777215. + arp_suppress: + description: + - Enables ARP broadcast suppression in a BD. + choices: ['enable', 'disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan arp module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure EVN BGP on Layer 2 and Layer 3 VXLAN gateways to establish EVN BGP peer relationships. + community.network.ce_vxlan_arp: + evn_bgp: enable + evn_source_ip: 6.6.6.6 + evn_peer_ip: 7.7.7.7 + provider: "{{ cli }}" + - name: Configure a Layer 3 VXLAN gateway as a BGP RR. + community.network.ce_vxlan_arp: + evn_bgp: enable + evn_server: enable + provider: "{{ cli }}" + - name: Enable EVN BGP on a Layer 3 VXLAN gateway to collect host information. + community.network.ce_vxlan_arp: + vbdif_name: Vbdif100 + arp_collect_host: enable + provider: "{{ cli }}" + - name: Enable Layer 2 and Layer 3 VXLAN gateways to use EVN BGP to advertise host information. + community.network.ce_vxlan_arp: + host_collect_protocol: bgp + provider: "{{ cli }}" + - name: Enable ARP broadcast suppression on a Layer 2 VXLAN gateway. + community.network.ce_vxlan_arp: + bridge_domain_id: 100 + arp_suppress: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"evn_bgp": "enable", "evn_source_ip": "6.6.6.6", "evn_peer_ip":"7.7.7.7", state: "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"evn_bgp": "disable", "evn_source_ip": null, "evn_peer_ip": []} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"evn_bgp": "enable", "evn_source_ip": "6.6.6.6", "evn_peer_ip": ["7.7.7.7"]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["evn bgp", + "source-address 6.6.6.6", + "peer 7.7.7.7"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if addr.count('.') == 3: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def get_evn_peers(config): + """get evn peer ip list""" + + get = re.findall(r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return list(set(get)) + + +def get_evn_srouce(config): + """get evn peer ip list""" + + get = re.findall( + r"source-address ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return get[0] + + +def get_evn_reflect_client(config): + """get evn reflect client list""" + + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s*reflect-client", config) + if not get: + return None + else: + return list(get) + + +class VxlanArp(object): + """ + Manages arp attributes of VXLAN. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.evn_bgp = self.module.params['evn_bgp'] + self.evn_source_ip = self.module.params['evn_source_ip'] + self.evn_peer_ip = self.module.params['evn_peer_ip'] + self.evn_server = self.module.params['evn_server'] + self.evn_reflect_client = self.module.params['evn_reflect_client'] + self.vbdif_name = self.module.params['vbdif_name'] + self.arp_collect_host = self.module.params['arp_collect_host'] + self.host_collect_protocol = self.module.params[ + 'host_collect_protocol'] + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.arp_suppress = self.module.params['arp_suppress'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" # current config + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + required_together = [("vbdif_name", "arp_collect_host"), ("bridge_domain_id", "arp_suppress")] + self.module = AnsibleModule(argument_spec=self.spec, + required_together=required_together, + supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = r"| ignore-case section include evn bgp|host collect protocol bgp" + if self.vbdif_name: + exp += r"|^#\s+interface %s\s+" % self.vbdif_name.lower().capitalize().replace(" ", "") + + if self.bridge_domain_id: + exp += r"|^#\s+bridge-domain %s\s+" % self.bridge_domain_id + + flags.append(exp) + cfg_str = self.get_config(flags) + config = cfg_str.split("\n") + + exist_config = "" + for cfg in config: + if not cfg.startswith("display"): + exist_config += cfg + return exist_config + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_bridge_domain(self): + """manage bridge domain configuration""" + + if not self.bridge_domain_id: + return + + # bridge-domain bd-id + # [undo] arp broadcast-suppress enable + + cmd = "bridge-domain %s" % self.bridge_domain_id + if not is_config_exist(self.config, cmd): + self.module.fail_json(msg="Error: Bridge domain %s is not exist." % self.bridge_domain_id) + + cmd = "arp broadcast-suppress enable" + exist = is_config_exist(self.config, cmd) + if self.arp_suppress == "enable" and not exist: + self.cli_add_command("bridge-domain %s" % self.bridge_domain_id) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.arp_suppress == "disable" and exist: + self.cli_add_command("bridge-domain %s" % self.bridge_domain_id) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_evn_bgp(self): + """enables EVN BGP and configure evn bgp command""" + + evn_bgp_view = False + evn_bgp_enable = False + + cmd = "evn bgp" + exist = is_config_exist(self.config, cmd) + if self.evn_bgp == "enable" or exist: + evn_bgp_enable = True + + # [undo] evn bgp + if self.evn_bgp: + if self.evn_bgp == "enable" and not exist: + self.cli_add_command(cmd) + evn_bgp_view = True + elif self.evn_bgp == "disable" and exist: + self.cli_add_command(cmd, undo=True) + return + + # [undo] source-address ip-address + if evn_bgp_enable and self.evn_source_ip: + cmd = "source-address %s" % self.evn_source_ip + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] peer ip-address + # [undo] peer ipv4-address reflect-client + if evn_bgp_enable and self.evn_peer_ip: + cmd = "peer %s" % self.evn_peer_ip + exist = is_config_exist(self.config, cmd) + if self.state == "present": + if not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + if self.evn_reflect_client == "enable": + self.cli_add_command( + "peer %s reflect-client" % self.evn_peer_ip) + else: + if self.evn_reflect_client: + cmd = "peer %s reflect-client" % self.evn_peer_ip + exist = is_config_exist(self.config, cmd) + if self.evn_reflect_client == "enable" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.evn_reflect_client == "disable" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + else: + if exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] server enable + if evn_bgp_enable and self.evn_server: + cmd = "server enable" + exist = is_config_exist(self.config, cmd) + if self.evn_server == "enable" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.evn_server == "disable" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + if evn_bgp_view: + self.cli_add_command("quit") + + def config_vbdif(self): + """configure command at the VBDIF interface view""" + + # interface vbdif bd-id + # [undo] arp collect host enable + + cmd = "interface %s" % self.vbdif_name.lower().capitalize() + exist = is_config_exist(self.config, cmd) + + if not exist: + self.module.fail_json( + msg="Error: Interface %s does not exist." % self.vbdif_name) + + cmd = "arp collect host enable" + exist = is_config_exist(self.config, cmd) + if self.arp_collect_host == "enable" and not exist: + self.cli_add_command("interface %s" % + self.vbdif_name.lower().capitalize()) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.arp_collect_host == "disable" and exist: + self.cli_add_command("interface %s" % + self.vbdif_name.lower().capitalize()) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_host_collect_protocal(self): + """Enable EVN BGP or BGP EVPN to advertise host information""" + + # [undo] host collect protocol bgp + cmd = "host collect protocol bgp" + exist = is_config_exist(self.config, cmd) + + if self.state == "present": + if self.host_collect_protocol == "bgp" and not exist: + self.cli_add_command(cmd) + elif self.host_collect_protocol == "none" and exist: + self.cli_add_command(cmd, undo=True) + else: + if self.host_collect_protocol == "bgp" and exist: + self.cli_add_command(cmd, undo=True) + + def is_valid_vbdif(self, ifname): + """check is interface vbdif is valid""" + + if not ifname.upper().startswith('VBDIF'): + return False + bdid = self.vbdif_name.replace(" ", "").upper().replace("VBDIF", "") + if not bdid.isdigit(): + return False + if int(bdid) < 1 or int(bdid) > 16777215: + return False + + return True + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: Bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: Bridge domain id is not in the range from 1 to 16777215.") + + # evn_source_ip check + if self.evn_source_ip: + if not is_valid_v4addr(self.evn_source_ip): + self.module.fail_json(msg="Error: evn_source_ip is invalid.") + + # evn_peer_ip check + if self.evn_peer_ip: + if not is_valid_v4addr(self.evn_peer_ip): + self.module.fail_json(msg="Error: evn_peer_ip is invalid.") + + # vbdif_name check + if self.vbdif_name: + self.vbdif_name = self.vbdif_name.replace( + " ", "").lower().capitalize() + if not self.is_valid_vbdif(self.vbdif_name): + self.module.fail_json(msg="Error: vbdif_name is invalid.") + + # evn_reflect_client and evn_peer_ip must set at the same time + if self.evn_reflect_client and not self.evn_peer_ip: + self.module.fail_json( + msg="Error: evn_reflect_client and evn_peer_ip must set at the same time.") + + # evn_server and evn_reflect_client can not set at the same time + if self.evn_server == "enable" and self.evn_reflect_client == "enable": + self.module.fail_json( + msg="Error: evn_server and evn_reflect_client can not set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.evn_bgp: + self.proposed["evn_bgp"] = self.evn_bgp + if self.evn_source_ip: + self.proposed["evn_source_ip"] = self.evn_source_ip + if self.evn_peer_ip: + self.proposed["evn_peer_ip"] = self.evn_peer_ip + if self.evn_server: + self.proposed["evn_server"] = self.evn_server + if self.evn_reflect_client: + self.proposed["evn_reflect_client"] = self.evn_reflect_client + if self.arp_collect_host: + self.proposed["arp_collect_host"] = self.arp_collect_host + if self.host_collect_protocol: + self.proposed["host_collect_protocol"] = self.host_collect_protocol + if self.arp_suppress: + self.proposed["arp_suppress"] = self.arp_suppress + if self.vbdif_name: + self.proposed["vbdif_name"] = self.evn_peer_ip + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + evn_bgp_exist = is_config_exist(self.config, "evn bgp") + if evn_bgp_exist: + self.existing["evn_bgp"] = "enable" + else: + self.existing["evn_bgp"] = "disable" + + if evn_bgp_exist: + if is_config_exist(self.config, "server enable"): + self.existing["evn_server"] = "enable" + else: + self.existing["evn_server"] = "disable" + + self.existing["evn_source_ip"] = get_evn_srouce(self.config) + self.existing["evn_peer_ip"] = get_evn_peers(self.config) + self.existing["evn_reflect_client"] = get_evn_reflect_client( + self.config) + + if is_config_exist(self.config, "arp collect host enable"): + self.existing["host_collect_protocol"] = "enable" + else: + self.existing["host_collect_protocol"] = "disable" + + if is_config_exist(self.config, "host collect protocol bgp"): + self.existing["host_collect_protocol"] = "bgp" + else: + self.existing["host_collect_protocol"] = None + + if is_config_exist(self.config, "arp broadcast-suppress enable"): + self.existing["arp_suppress"] = "enable" + else: + self.existing["arp_suppress"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + evn_bgp_exist = is_config_exist(config, "evn bgp") + if evn_bgp_exist: + self.end_state["evn_bgp"] = "enable" + else: + self.end_state["evn_bgp"] = "disable" + + if evn_bgp_exist: + if is_config_exist(config, "server enable"): + self.end_state["evn_server"] = "enable" + else: + self.end_state["evn_server"] = "disable" + + self.end_state["evn_source_ip"] = get_evn_srouce(config) + self.end_state["evn_peer_ip"] = get_evn_peers(config) + self.end_state[ + "evn_reflect_client"] = get_evn_reflect_client(config) + + if is_config_exist(config, "arp collect host enable"): + self.end_state["host_collect_protocol"] = "enable" + else: + self.end_state["host_collect_protocol"] = "disable" + + if is_config_exist(config, "host collect protocol bgp"): + self.end_state["host_collect_protocol"] = "bgp" + else: + self.end_state["host_collect_protocol"] = None + + if is_config_exist(config, "arp broadcast-suppress enable"): + self.end_state["arp_suppress"] = "enable" + else: + self.end_state["arp_suppress"] = "disable" + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.evn_bgp or self.evn_server or self.evn_peer_ip or self.evn_source_ip: + self.config_evn_bgp() + + if self.vbdif_name and self.arp_collect_host: + self.config_vbdif() + + if self.host_collect_protocol: + self.config_host_collect_protocal() + + if self.bridge_domain_id and self.arp_suppress: + self.config_bridge_domain() + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + evn_bgp=dict(required=False, type='str', + choices=['enable', 'disable']), + evn_source_ip=dict(required=False, type='str'), + evn_peer_ip=dict(required=False, type='str'), + evn_server=dict(required=False, type='str', + choices=['enable', 'disable']), + evn_reflect_client=dict( + required=False, type='str', choices=['enable', 'disable']), + vbdif_name=dict(required=False, type='str'), + arp_collect_host=dict(required=False, type='str', + choices=['enable', 'disable']), + host_collect_protocol=dict( + required=False, type='str', choices=['bgp', 'none']), + bridge_domain_id=dict(required=False, type='str'), + arp_suppress=dict(required=False, type='str', + choices=['enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanArp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_gateway.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_gateway.py new file mode 100644 index 00000000..8d8ffd8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_gateway.py @@ -0,0 +1,936 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_gateway +short_description: Manages gateway for the VXLAN network on HUAWEI CloudEngine devices. +description: + - Configuring Centralized All-Active Gateways or Distributed Gateway for + the VXLAN Network on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Ensure All-Active Gateways or Distributed Gateway for the VXLAN Network can not configure at the same time. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + dfs_id: + description: + - Specifies the ID of a DFS group. + The value must be 1. + dfs_source_ip: + description: + - Specifies the IPv4 address bound to a DFS group. + The value is in dotted decimal notation. + dfs_source_vpn: + description: + - Specifies the name of a VPN instance bound to a DFS group. + The value is a string of 1 to 31 case-sensitive characters without spaces. + If the character string is quoted by double quotation marks, the character string can contain spaces. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + dfs_udp_port: + description: + - Specifies the UDP port number of the DFS group. + The value is an integer that ranges from 1025 to 65535. + dfs_all_active: + description: + - Creates all-active gateways. + choices: ['enable', 'disable'] + dfs_peer_ip: + description: + - Configure the IP address of an all-active gateway peer. + The value is in dotted decimal notation. + dfs_peer_vpn: + description: + - Specifies the name of the VPN instance that is associated with all-active gateway peer. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vpn_instance: + description: + - Specifies the name of a VPN instance. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vpn_vni: + description: + - Specifies a VNI ID. + Binds a VXLAN network identifier (VNI) to a virtual private network (VPN) instance. + The value is an integer ranging from 1 to 16000000. + vbdif_name: + description: + - Full name of VBDIF interface, i.e. Vbdif100. + vbdif_bind_vpn: + description: + - Specifies the name of the VPN instance that is associated with the interface. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vbdif_mac: + description: + - Specifies a MAC address for a VBDIF interface. + The value is in the format of H-H-H. Each H is a 4-digit hexadecimal number, such as C(00e0) or C(fc01). + If an H contains less than four digits, 0s are added ahead. For example, C(e0) is equal to C(00e0). + A MAC address cannot be all 0s or 1s or a multicast MAC address. + arp_distribute_gateway: + description: + - Enable the distributed gateway function on VBDIF interface. + choices: ['enable','disable'] + arp_direct_route: + description: + - Enable VLINK direct route on VBDIF interface. + choices: ['enable','disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan gateway module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configuring Centralized All-Active Gateways for the VXLAN Network + community.network.ce_vxlan_gateway: + dfs_id: 1 + dfs_source_ip: 6.6.6.6 + dfs_all_active: enable + dfs_peer_ip: 7.7.7.7 + provider: "{{ cli }}" + - name: Bind the VPN instance to a Layer 3 gateway, enable distributed gateway, and configure host route advertisement. + community.network.ce_vxlan_gateway: + vbdif_name: Vbdif100 + vbdif_bind_vpn: vpn1 + arp_distribute_gateway: enable + arp_direct_route: enable + provider: "{{ cli }}" + - name: Assign a VNI to a VPN instance. + community.network.ce_vxlan_gateway: + vpn_instance: vpn1 + vpn_vni: 100 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "dfs_source_ip": "6.6.6.6", "dfs_all_active":"enable", "dfs_peer_ip": "7.7.7.7"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "dfs_source_ip": null, "evn_peer_ip": [], "dfs_all_active": "disable"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "evn_source_ip": "6.6.6.6", "evn_source_vpn": null, + "evn_peers": [{"ip": "7.7.7.7", "vpn": ""}], "dfs_all_active": "enable"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["dfs-group 1", + "source ip 6.6.6.6", + "active-active-gateway", + "peer 7.7.7.7"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_v4addr(addr): + """check is ipv4 addr""" + + if not addr: + return False + + if addr.count('.') == 3: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def mac_format(mac): + """convert mac format to xxxx-xxxx-xxxx""" + + if not mac: + return None + + if mac.count("-") != 2: + return None + + addrs = mac.split("-") + for i in range(3): + if not addrs[i] or not addrs[i].isalnum(): + return None + if len(addrs[i]) < 1 or len(addrs[i]) > 4: + return None + try: + addrs[i] = int(addrs[i], 16) + except ValueError: + return None + + try: + return "%04x-%04x-%04x" % (addrs[0], addrs[1], addrs[2]) + except ValueError: + return None + except TypeError: + return None + + +def get_dfs_source_ip(config): + """get dfs source ip address""" + + get = re.findall(r"source ip ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_source_vpn(config): + """get dfs source ip vpn instance name""" + + get = re.findall( + r"source ip [0-9]+.[0-9]+.[0-9]+.[0-9]+ vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_udp_port(config): + """get dfs udp port""" + + get = re.findall(r"udp port (\d+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_peers(config): + """get evn peer ip list""" + + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?(vpn-instance)?\s?(\S*)", config) + if not get: + return None + else: + peers = list() + for item in get: + peers.append(dict(ip=item[0], vpn=item[2])) + return peers + + +def get_ip_vpn(config): + """get ip vpn instance""" + + get = re.findall(r"ip vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_ip_vpn_vni(config): + """get ip vpn vxlan vni""" + + get = re.findall(r"vxlan vni (\d+)", config) + if not get: + return None + else: + return get[0] + + +def get_vbdif_vpn(config): + """get ip vpn name of interface vbdif""" + + get = re.findall(r"ip binding vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_vbdif_mac(config): + """get mac address of interface vbdif""" + + get = re.findall( + r" mac-address ([0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4})", config) + if not get: + return None + else: + return get[0] + + +class VxlanGateway(object): + """ + Manages Gateway for the VXLAN Network. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.dfs_id = self.module.params['dfs_id'] + self.dfs_source_ip = self.module.params['dfs_source_ip'] + self.dfs_source_vpn = self.module.params['dfs_source_vpn'] + self.dfs_udp_port = self.module.params['dfs_udp_port'] + self.dfs_all_active = self.module.params['dfs_all_active'] + self.dfs_peer_ip = self.module.params['dfs_peer_ip'] + self.dfs_peer_vpn = self.module.params['dfs_peer_vpn'] + self.vpn_instance = self.module.params['vpn_instance'] + self.vpn_vni = self.module.params['vpn_vni'] + self.vbdif_name = self.module.params['vbdif_name'] + self.vbdif_mac = self.module.params['vbdif_mac'] + self.vbdif_bind_vpn = self.module.params['vbdif_bind_vpn'] + self.arp_distribute_gateway = self.module.params['arp_distribute_gateway'] + self.arp_direct_route = self.module.params['arp_direct_route'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" # current config + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = r" | ignore-case section include ^#\s+dfs-group" + if self.vpn_instance: + exp += r"|^#\s+ip vpn-instance %s" % self.vpn_instance + if self.vbdif_name: + exp += r"|^#\s+interface %s" % self.vbdif_name + flags.append(exp) + return self.get_config(flags) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_dfs_group(self): + """manage Dynamic Fabric Service (DFS) group configuration""" + + if not self.dfs_id: + return + + dfs_view = False + view_cmd = "dfs-group %s" % self.dfs_id + exist = is_config_exist(self.config, view_cmd) + if self.state == "present" and not exist: + self.cli_add_command(view_cmd) + dfs_view = True + + # undo dfs-group dfs-group-id + if self.state == "absent" and exist: + if not self.dfs_source_ip and not self.dfs_udp_port and not self.dfs_all_active and not self.dfs_peer_ip: + self.cli_add_command(view_cmd, undo=True) + return + + # [undo] source ip ip-address [ vpn-instance vpn-instance-name ] + if self.dfs_source_ip: + cmd = "source ip %s" % self.dfs_source_ip + if self.dfs_source_vpn: + cmd += " vpn-instance %s" % self.dfs_source_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd) + if self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] udp port port-number + if self.dfs_udp_port: + cmd = "udp port %s" % self.dfs_udp_port + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] active-active-gateway + # [undo]peer[ vpn-instance vpn-instance-name ] + aa_cmd = "active-active-gateway" + aa_exist = is_config_exist(self.config, aa_cmd) + aa_view = False + if self.dfs_all_active == "disable": + if aa_exist: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_source_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd, undo=True) + elif self.dfs_all_active == "enable": + if not aa_exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + aa_view = True + + if self.dfs_peer_ip: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_peer_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + if not aa_view: + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + if not aa_view: + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + else: # not input dfs_all_active + if aa_exist and self.dfs_peer_ip: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_peer_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + else: + pass + elif not aa_exist and self.dfs_peer_ip and self.state == "present": + self.module.fail_json( + msg="Error: All-active gateways is not enable.") + else: + pass + + if dfs_view: + self.cli_add_command("quit") + + def config_ip_vpn(self): + """configure command at the ip vpn view""" + + if not self.vpn_instance or not self.vpn_vni: + return + + # ip vpn-instance vpn-instance-name + view_cmd = "ip vpn-instance %s" % self.vpn_instance + exist = is_config_exist(self.config, view_cmd) + if not exist: + self.module.fail_json( + msg="Error: ip vpn instance %s is not exist." % self.vpn_instance) + + # [undo] vxlan vni vni-id + cmd = "vxlan vni %s" % self.vpn_vni + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(view_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + self.cli_add_command(view_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_vbdif(self): + """configure command at the VBDIF interface view""" + + if not self.vbdif_name: + return + + vbdif_cmd = "interface %s" % self.vbdif_name.lower().capitalize() + exist = is_config_exist(self.config, vbdif_cmd) + + if not exist: + self.module.fail_json( + msg="Error: Interface %s is not exist." % self.vbdif_name) + + # interface vbdif bd-id + # [undo] ip binding vpn-instance vpn-instance-name + vbdif_view = False + if self.vbdif_bind_vpn: + cmd = "ip binding vpn-instance %s" % self.vbdif_bind_vpn + exist = is_config_exist(self.config, cmd) + + if self.state == "present" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] arp distribute-gateway enable + if self.arp_distribute_gateway: + cmd = "arp distribute-gateway enable" + exist = is_config_exist(self.config, cmd) + if self.arp_distribute_gateway == "enable" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.arp_distribute_gateway == "disable" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] arp direct-route enable + if self.arp_direct_route: + cmd = "arp direct-route enable" + exist = is_config_exist(self.config, cmd) + if self.arp_direct_route == "enable" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.arp_direct_route == "disable" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # mac-address mac-address + # undo mac-address + if self.vbdif_mac: + cmd = "mac-address %s" % self.vbdif_mac + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command("undo mac-address") + + # quit + if vbdif_view: + self.cli_add_command("quit") + + def is_valid_vbdif(self, ifname): + """check is interface vbdif""" + + if not ifname.upper().startswith('VBDIF'): + return False + bdid = self.vbdif_name.replace(" ", "").upper().replace("VBDIF", "") + if not bdid.isdigit(): + return False + if int(bdid) < 1 or int(bdid) > 16777215: + return False + + return True + + def is_valid_ip_vpn(self, vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + self.module.fail_json( + msg="Error: The value C(_public_) is reserved and cannot be used as the VPN instance name.") + + if len(vpname) < 1 or len(vpname) > 31: + self.module.fail_json( + msg="Error: IP vpn name length is not in the range from 1 to 31.") + + return True + + def check_params(self): + """Check all input params""" + + # dfs id check + if self.dfs_id: + if not self.dfs_id.isdigit(): + self.module.fail_json(msg="Error: DFS id is not digit.") + if int(self.dfs_id) != 1: + self.module.fail_json(msg="Error: DFS is not 1.") + + # dfs_source_ip check + if self.dfs_source_ip: + if not is_valid_v4addr(self.dfs_source_ip): + self.module.fail_json(msg="Error: dfs_source_ip is invalid.") + # dfs_source_vpn check + if self.dfs_source_vpn and not self.is_valid_ip_vpn(self.dfs_source_vpn): + self.module.fail_json(msg="Error: dfs_source_vpn is invalid.") + + # dfs_source_vpn and dfs_source_ip must set at the same time + if self.dfs_source_vpn and not self.dfs_source_ip: + self.module.fail_json( + msg="Error: dfs_source_vpn and dfs_source_ip must set at the same time.") + + # dfs_udp_port check + if self.dfs_udp_port: + if not self.dfs_udp_port.isdigit(): + self.module.fail_json( + msg="Error: dfs_udp_port id is not digit.") + if int(self.dfs_udp_port) < 1025 or int(self.dfs_udp_port) > 65535: + self.module.fail_json( + msg="dfs_udp_port is not ranges from 1025 to 65535.") + + # dfs_peer_ip check + if self.dfs_peer_ip: + if not is_valid_v4addr(self.dfs_peer_ip): + self.module.fail_json(msg="Error: dfs_peer_ip is invalid.") + # dfs_peer_vpn check + if self.dfs_peer_vpn and not self.is_valid_ip_vpn(self.dfs_peer_vpn): + self.module.fail_json(msg="Error: dfs_peer_vpn is invalid.") + + # dfs_peer_vpn and dfs_peer_ip must set at the same time + if self.dfs_peer_vpn and not self.dfs_peer_ip: + self.module.fail_json( + msg="Error: dfs_peer_vpn and dfs_peer_ip must set at the same time.") + + # vpn_instance check + if self.vpn_instance and not self.is_valid_ip_vpn(self.vpn_instance): + self.module.fail_json(msg="Error: vpn_instance is invalid.") + + # vpn_vni check + if self.vpn_vni: + if not self.vpn_vni.isdigit(): + self.module.fail_json(msg="Error: vpn_vni id is not digit.") + if int(self.vpn_vni) < 1 or int(self.vpn_vni) > 16000000: + self.module.fail_json( + msg="vpn_vni is not ranges from 1 to 16000000.") + + # vpn_instance and vpn_vni must set at the same time + if bool(self.vpn_instance) != bool(self.vpn_vni): + self.module.fail_json( + msg="Error: vpn_instance and vpn_vni must set at the same time.") + + # vbdif_name check + if self.vbdif_name: + self.vbdif_name = self.vbdif_name.replace(" ", "").lower().capitalize() + if not self.is_valid_vbdif(self.vbdif_name): + self.module.fail_json(msg="Error: vbdif_name is invalid.") + + # vbdif_mac check + if self.vbdif_mac: + mac = mac_format(self.vbdif_mac) + if not mac: + self.module.fail_json(msg="Error: vbdif_mac is invalid.") + self.vbdif_mac = mac + + # vbdif_bind_vpn check + if self.vbdif_bind_vpn and not self.is_valid_ip_vpn(self.vbdif_bind_vpn): + self.module.fail_json(msg="Error: vbdif_bind_vpn is invalid.") + + # All-Active Gateways or Distributed Gateway config can not set at the + # same time. + if self.dfs_id: + if self.vpn_vni or self.arp_distribute_gateway == "enable": + self.module.fail_json(msg="Error: All-Active Gateways or Distributed Gateway config " + "can not set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.dfs_id: + self.proposed["dfs_id"] = self.dfs_id + self.proposed["dfs_source_ip"] = self.dfs_source_ip + self.proposed["dfs_source_vpn"] = self.dfs_source_vpn + self.proposed["dfs_udp_port"] = self.dfs_udp_port + self.proposed["dfs_all_active"] = self.dfs_all_active + self.proposed["dfs_peer_ip"] = self.dfs_peer_ip + self.proposed["dfs_peer_vpn"] = self.dfs_peer_vpn + + if self.vpn_instance: + self.proposed["vpn_instance"] = self.vpn_instance + self.proposed["vpn_vni"] = self.vpn_vni + + if self.vbdif_name: + self.proposed["vbdif_name"] = self.vbdif_name + self.proposed["vbdif_mac"] = self.vbdif_mac + self.proposed["vbdif_bind_vpn"] = self.vbdif_bind_vpn + self.proposed[ + "arp_distribute_gateway"] = self.arp_distribute_gateway + self.proposed["arp_direct_route"] = self.arp_direct_route + + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.config: + return + + if is_config_exist(self.config, "dfs-group 1"): + self.existing["dfs_id"] = "1" + self.existing["dfs_source_ip"] = get_dfs_source_ip(self.config) + self.existing["dfs_source_vpn"] = get_dfs_source_vpn(self.config) + self.existing["dfs_udp_port"] = get_dfs_udp_port(self.config) + if is_config_exist(self.config, "active-active-gateway"): + self.existing["dfs_all_active"] = "enable" + self.existing["dfs_peers"] = get_dfs_peers(self.config) + else: + self.existing["dfs_all_active"] = "disable" + + if self.vpn_instance: + self.existing["vpn_instance"] = get_ip_vpn(self.config) + self.existing["vpn_vni"] = get_ip_vpn_vni(self.config) + + if self.vbdif_name: + self.existing["vbdif_name"] = self.vbdif_name + self.existing["vbdif_mac"] = get_vbdif_mac(self.config) + self.existing["vbdif_bind_vpn"] = get_vbdif_vpn(self.config) + if is_config_exist(self.config, "arp distribute-gateway enable"): + self.existing["arp_distribute_gateway"] = "enable" + else: + self.existing["arp_distribute_gateway"] = "disable" + if is_config_exist(self.config, "arp direct-route enable"): + self.existing["arp_direct_route"] = "enable" + else: + self.existing["arp_direct_route"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + if not config: + return + + if is_config_exist(config, "dfs-group 1"): + self.end_state["dfs_id"] = "1" + self.end_state["dfs_source_ip"] = get_dfs_source_ip(config) + self.end_state["dfs_source_vpn"] = get_dfs_source_vpn(config) + self.end_state["dfs_udp_port"] = get_dfs_udp_port(config) + if is_config_exist(config, "active-active-gateway"): + self.end_state["dfs_all_active"] = "enable" + self.end_state["dfs_peers"] = get_dfs_peers(config) + else: + self.end_state["dfs_all_active"] = "disable" + + if self.vpn_instance: + self.end_state["vpn_instance"] = get_ip_vpn(config) + self.end_state["vpn_vni"] = get_ip_vpn_vni(config) + + if self.vbdif_name: + self.end_state["vbdif_name"] = self.vbdif_name + self.end_state["vbdif_mac"] = get_vbdif_mac(config) + self.end_state["vbdif_bind_vpn"] = get_vbdif_vpn(config) + if is_config_exist(config, "arp distribute-gateway enable"): + self.end_state["arp_distribute_gateway"] = "enable" + else: + self.end_state["arp_distribute_gateway"] = "disable" + if is_config_exist(config, "arp direct-route enable"): + self.end_state["arp_direct_route"] = "enable" + else: + self.end_state["arp_direct_route"] = "disable" + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.dfs_id: + self.config_dfs_group() + + if self.vpn_instance: + self.config_ip_vpn() + + if self.vbdif_name: + self.config_vbdif() + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + dfs_id=dict(required=False, type='str'), + dfs_source_ip=dict(required=False, type='str'), + dfs_source_vpn=dict(required=False, type='str'), + dfs_udp_port=dict(required=False, type='str'), + dfs_all_active=dict(required=False, type='str', + choices=['enable', 'disable']), + dfs_peer_ip=dict(required=False, type='str'), + dfs_peer_vpn=dict(required=False, type='str'), + vpn_instance=dict(required=False, type='str'), + vpn_vni=dict(required=False, type='str'), + vbdif_name=dict(required=False, type='str'), + vbdif_mac=dict(required=False, type='str'), + vbdif_bind_vpn=dict(required=False, type='str'), + arp_distribute_gateway=dict( + required=False, type='str', choices=['enable', 'disable']), + arp_direct_route=dict(required=False, type='str', + choices=['enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanGateway(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_global.py new file mode 100644 index 00000000..e474c923 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_global.py @@ -0,0 +1,539 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_global +short_description: Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. +description: + - Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. + The value is an integer ranging from 1 to 16777215. + tunnel_mode_vxlan: + description: + - Set the tunnel mode to VXLAN when configuring the VXLAN feature. + choices: ['enable', 'disable'] + nvo3_prevent_loops: + description: + - Loop prevention of VXLAN traffic in non-enhanced mode. + When the device works in non-enhanced mode, + inter-card forwarding of VXLAN traffic may result in loops. + choices: ['enable', 'disable'] + nvo3_acl_extend: + description: + - Enabling or disabling the VXLAN ACL extension function. + choices: ['enable', 'disable'] + nvo3_gw_enhanced: + description: + - Configuring the Layer 3 VXLAN Gateway to Work in Non-loopback Mode. + choices: ['l2', 'l3'] + nvo3_service_extend: + description: + - Enabling or disabling the VXLAN service extension function. + choices: ['enable', 'disable'] + nvo3_eth_trunk_hash: + description: + - Eth-Trunk from load balancing VXLAN packets in optimized mode. + choices: ['enable','disable'] + nvo3_ecmp_hash: + description: + - Load balancing of VXLAN packets through ECMP in optimized mode. + choices: ['enable', 'disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan global module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create bridge domain and set tunnel mode to VXLAN + community.network.ce_vxlan_global: + bridge_domain_id: 100 + nvo3_acl_extend: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "nvo3_acl_extend": "enable", state="present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"bridge_domain": {"80", "90"}, "nvo3_acl_extend": "disable"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"bridge_domain_id": {"80", "90", "100"}, "nvo3_acl_extend": "enable"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["bridge-domain 100", + "ip tunnel mode vxlan"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def get_nvo3_gw_enhanced(cmp_cfg): + """get the Layer 3 VXLAN Gateway to Work in Non-loopback Mode """ + + get = re.findall( + r"assign forward nvo3-gateway enhanced (l[2|3])", cmp_cfg) + if not get: + return None + else: + return get[0] + + +class VxlanGlobal(object): + """ + Manages global attributes of VXLAN and bridge domain. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.tunnel_mode_vxlan = self.module.params['tunnel_mode_vxlan'] + self.nvo3_prevent_loops = self.module.params['nvo3_prevent_loops'] + self.nvo3_acl_extend = self.module.params['nvo3_acl_extend'] + self.nvo3_gw_enhanced = self.module.params['nvo3_gw_enhanced'] + self.nvo3_service_extend = self.module.params['nvo3_service_extend'] + self.nvo3_eth_trunk_hash = self.module.params['nvo3_eth_trunk_hash'] + self.nvo3_ecmp_hash = self.module.params['nvo3_ecmp_hash'] + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.state = self.module.params['state'] + + # state + self.config = "" # current config + self.bd_info = list() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = " include-default | include vxlan|assign | exclude undo" + flags.append(exp) + return self.get_config(flags) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def get_bd_list(self): + """get bridge domain list""" + flags = list() + bd_info = list() + exp = " include-default | include bridge-domain | exclude undo" + flags.append(exp) + bd_str = self.get_config(flags) + if not bd_str: + return bd_info + bd_num = re.findall(r'bridge-domain\s*([0-9]+)', bd_str) + bd_info.extend(bd_num) + return bd_info + + def config_bridge_domain(self): + """manage bridge domain""" + + if not self.bridge_domain_id: + return + + cmd = "bridge-domain %s" % self.bridge_domain_id + exist = self.bridge_domain_id in self.bd_info + if self.state == "present": + if not exist: + self.cli_add_command(cmd) + self.cli_add_command("quit") + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def config_tunnel_mode(self): + """config tunnel mode vxlan""" + + # ip tunnel mode vxlan + if self.tunnel_mode_vxlan: + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(self.config, cmd) + if self.tunnel_mode_vxlan == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def config_assign_forward(self): + """config assign forward command""" + + # [undo] assign forward nvo3-gateway enhanced {l2|l3) + if self.nvo3_gw_enhanced: + cmd = "assign forward nvo3-gateway enhanced %s" % self.nvo3_gw_enhanced + exist = is_config_exist(self.config, cmd) + if self.state == "present": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 f-linecard compatibility enable + if self.nvo3_prevent_loops: + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_prevent_loops == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 acl extend enable + if self.nvo3_acl_extend: + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_acl_extend == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 service extend enable + if self.nvo3_service_extend: + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_service_extend == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # assign forward nvo3 eth-trunk hash {enable|disable} + if self.nvo3_eth_trunk_hash: + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_eth_trunk_hash == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 ecmp hash enable + if self.nvo3_ecmp_hash: + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_ecmp_hash == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: bridge domain id is not in the range from 1 to 16777215.") + + def get_proposed(self): + """get proposed info""" + + if self.tunnel_mode_vxlan: + self.proposed["tunnel_mode_vxlan"] = self.tunnel_mode_vxlan + if self.nvo3_prevent_loops: + self.proposed["nvo3_prevent_loops"] = self.nvo3_prevent_loops + if self.nvo3_acl_extend: + self.proposed["nvo3_acl_extend"] = self.nvo3_acl_extend + if self.nvo3_gw_enhanced: + self.proposed["nvo3_gw_enhanced"] = self.nvo3_gw_enhanced + if self.nvo3_service_extend: + self.proposed["nvo3_service_extend"] = self.nvo3_service_extend + if self.nvo3_eth_trunk_hash: + self.proposed["nvo3_eth_trunk_hash"] = self.nvo3_eth_trunk_hash + if self.nvo3_ecmp_hash: + self.proposed["nvo3_ecmp_hash"] = self.nvo3_ecmp_hash + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + self.existing["bridge_domain"] = self.bd_info + + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["tunnel_mode_vxlan"] = "enable" + else: + self.existing["tunnel_mode_vxlan"] = "disable" + + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_prevent_loops"] = "enable" + else: + self.existing["nvo3_prevent_loops"] = "disable" + + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_acl_extend"] = "enable" + else: + self.existing["nvo3_acl_extend"] = "disable" + + self.existing["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced( + self.config) + + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_service_extend"] = "enable" + else: + self.existing["nvo3_service_extend"] = "disable" + + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_eth_trunk_hash"] = "enable" + else: + self.existing["nvo3_eth_trunk_hash"] = "disable" + + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_ecmp_hash"] = "enable" + else: + self.existing["nvo3_ecmp_hash"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + + self.end_state["bridge_domain"] = self.get_bd_list() + + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["tunnel_mode_vxlan"] = "enable" + else: + self.end_state["tunnel_mode_vxlan"] = "disable" + + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_prevent_loops"] = "enable" + else: + self.end_state["nvo3_prevent_loops"] = "disable" + + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_acl_extend"] = "enable" + else: + self.end_state["nvo3_acl_extend"] = "disable" + + self.end_state["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced(config) + + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_service_extend"] = "enable" + else: + self.end_state["nvo3_service_extend"] = "disable" + + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_eth_trunk_hash"] = "enable" + else: + self.end_state["nvo3_eth_trunk_hash"] = "disable" + + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_ecmp_hash"] = "enable" + else: + self.end_state["nvo3_ecmp_hash"] = "disable" + if self.existing == self.end_state: + self.changed = True + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.bd_info = self.get_bd_list() + self.get_existing() + self.get_proposed() + + # deal present or absent + self.config_bridge_domain() + self.config_tunnel_mode() + self.config_assign_forward() + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + tunnel_mode_vxlan=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_prevent_loops=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_acl_extend=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_gw_enhanced=dict(required=False, type='str', + choices=['l2', 'l3']), + nvo3_service_extend=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_eth_trunk_hash=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_ecmp_hash=dict(required=False, type='str', + choices=['enable', 'disable']), + bridge_domain_id=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_tunnel.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_tunnel.py new file mode 100644 index 00000000..d644b51d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_tunnel.py @@ -0,0 +1,940 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_tunnel +short_description: Manages VXLAN tunnel configuration on HUAWEI CloudEngine devices. +description: + - This module offers the ability to set the VNI and mapped to the BD, + and configure an ingress replication list on HUAWEI CloudEngine devices. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. The value is an integer ranging from 1 to 16777215. + vni_id: + description: + - Specifies a VXLAN network identifier (VNI) ID. The value is an integer ranging from 1 to 16000000. + nve_name: + description: + - Specifies the number of an NVE interface. The value ranges from 1 to 2. + nve_mode: + description: + - Specifies the working mode of an NVE interface. + choices: ['mode-l2','mode-l3'] + peer_list_ip: + description: + - Specifies the IP address of a remote VXLAN tunnel endpoints (VTEP). + The value is in dotted decimal notation. + protocol_type: + description: + - The operation type of routing protocol. + choices: ['bgp','null'] + source_ip: + description: + - Specifies an IP address for a source VTEP. The value is in dotted decimal notation. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Vxlan tunnel module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Make sure nve_name is exist, ensure vni_id and protocol_type is configured on Nve1 interface. + community.network.ce_vxlan_tunnel: + nve_name: Nve1 + vni_id: 100 + protocol_type: bgp + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l2", "source_ip": "0.0.0.0"} +existing: + description: + - k/v pairs of existing rollback + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l3", "source_ip": "0.0.0.0"} + +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Nve1", + "mode l3"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l3", "source_ip": "0.0.0.0"} +''' +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_VNI_BD_INFO = """ + + + + + + + + + + +""" + +CE_NC_GET_NVE_INFO = """ + + + + + %s + + + + +""" + +CE_NC_MERGE_VNI_BD_ID = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_VNI_BD_ID = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_NVE_MODE = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" + +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_MERGE = """ + + + %s + + +""" + +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE = """ + + + %s + + +""" + +CE_NC_DELETE_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" +CE_NC_DELETE_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_MERGE_VNI_PROTOCOL = """ + + + + + %s + + + %s + %s + + + + + + +""" + +CE_NC_DELETE_VNI_PROTOCOL = """ + + + + + %s + + + %s + %s + + + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class VxlanTunnel(object): + """ + Manages vxlan tunnel configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.vni_id = self.module.params['vni_id'] + self.nve_name = self.module.params['nve_name'] + self.nve_mode = self.module.params['nve_mode'] + self.peer_list_ip = self.module.params['peer_list_ip'] + self.protocol_type = self.module.params['protocol_type'] + self.source_ip = self.module.params['source_ip'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # configuration nve info + self.vni2bd_info = None + self.nve_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_vni2bd_dict(self): + """ get vni2bd attributes dict.""" + + vni2bd_info = dict() + # get vni bd info + conf_str = CE_NC_GET_VNI_BD_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vni2bd_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + # get vni to bridge domain id info + root = ElementTree.fromstring(xml_str) + vni2bd_info["vni2BdInfos"] = list() + vni2bds = root.findall("nvo3/nvo3Vni2Bds/nvo3Vni2Bd") + + if vni2bds: + for vni2bd in vni2bds: + vni_dict = dict() + for ele in vni2bd: + if ele.tag in ["vniId", "bdId"]: + vni_dict[ele.tag] = ele.text + vni2bd_info["vni2BdInfos"].append(vni_dict) + + return vni2bd_info + + def check_nve_interface(self, nve_name): + """is nve interface exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name: + return True + return False + + def get_nve_dict(self, nve_name): + """ get nve interface attributes dict.""" + + nve_info = dict() + # get nve info + conf_str = CE_NC_GET_NVE_INFO % nve_name + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return nve_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get nve info + root = ElementTree.fromstring(xml_str) + + nvo3 = root.find("nvo3/nvo3Nves/nvo3Nve") + if nvo3: + for nve in nvo3: + if nve.tag in ["srcAddr", "ifName", "nveType"]: + nve_info[nve.tag] = nve.text + + # get nve vni info + nve_info["vni_peer_protocols"] = list() + + vni_members = root.findall( + "nvo3/nvo3Nves/nvo3Nve/vniMembers/vniMember") + if vni_members: + for member in vni_members: + vni_dict = dict() + for ele in member: + if ele.tag in ["vniId", "protocol"]: + vni_dict[ele.tag] = ele.text + nve_info["vni_peer_protocols"].append(vni_dict) + + # get vni peer address ip info + nve_info["vni_peer_ips"] = list() + + re_find = re.findall(r'(.*?)\s*' + r'(.*?)\s*' + r'(.*?)', xml_str) + + if re_find: + for vni_peers in re_find: + vni_info = dict() + vni_peer = re.findall(r'(.*?)', vni_peers[2]) + if vni_peer: + vni_info["vniId"] = vni_peers[0] + vni_peer_list = list() + for peer in vni_peer: + vni_peer_list.append(peer) + vni_info["peerAddr"] = vni_peer_list + nve_info["vni_peer_ips"].append(vni_info) + + return nve_info + + def check_nve_name(self): + """Gets Nve interface name""" + + if self.nve_name is None: + return False + if self.nve_name in ["Nve1", "Nve2"]: + return True + return False + + def is_vni_bd_exist(self, vni_id, bd_id): + """is vni to bridge-domain-id exist""" + + if not self.vni2bd_info: + return False + + for vni2bd in self.vni2bd_info["vni2BdInfos"]: + if vni2bd["vniId"] == vni_id and vni2bd["bdId"] == bd_id: + return True + return False + + def is_vni_bd_change(self, vni_id, bd_id): + """is vni to bridge-domain-id change""" + + if not self.vni2bd_info: + return True + + for vni2bd in self.vni2bd_info["vni2BdInfos"]: + if vni2bd["vniId"] == vni_id and vni2bd["bdId"] == bd_id: + return False + return True + + def is_nve_mode_exist(self, nve_name, mode): + """is nve interface mode exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name and self.nve_info["nveType"] == mode: + return True + return False + + def is_nve_mode_change(self, nve_name, mode): + """is nve interface mode change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name and self.nve_info["nveType"] == mode: + return False + return True + + def is_nve_source_ip_exist(self, nve_name, source_ip): + """is vni to bridge-domain-id exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name and self.nve_info["srcAddr"] == source_ip: + return True + return False + + def is_nve_source_ip_change(self, nve_name, source_ip): + """is vni to bridge-domain-id change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name and self.nve_info["srcAddr"] == source_ip: + return False + return True + + def is_vni_protocol_exist(self, nve_name, vni_id, protocol_type): + """is vni protocol exist""" + + if not self.nve_info: + return False + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_protocols"]: + if member["vniId"] == vni_id and member["protocol"] == protocol_type: + return True + return False + + def is_vni_protocol_change(self, nve_name, vni_id, protocol_type): + """is vni protocol change""" + + if not self.nve_info: + return True + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_protocols"]: + if member["vniId"] == vni_id and member["protocol"] == protocol_type: + return False + return True + + def is_vni_peer_list_exist(self, nve_name, vni_id, peer_ip): + """is vni peer list exist""" + + if not self.nve_info: + return False + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_ips"]: + if member["vniId"] == vni_id and peer_ip in member["peerAddr"]: + return True + return False + + def is_vni_peer_list_change(self, nve_name, vni_id, peer_ip_list): + """is vni peer list change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name: + if not self.nve_info["vni_peer_ips"]: + return True + + nve_peer_info = list() + for nve_peer in self.nve_info["vni_peer_ips"]: + if nve_peer["vniId"] == vni_id: + nve_peer_info.append(nve_peer) + + if not nve_peer_info: + return True + + nve_peer_list = nve_peer_info[0]["peerAddr"] + for peer in peer_ip_list: + if peer not in nve_peer_list: + return True + + return False + + def config_merge_vni2bd(self, bd_id, vni_id): + """config vni to bd id""" + + if self.is_vni_bd_change(vni_id, bd_id): + cfg_xml = CE_NC_MERGE_VNI_BD_ID % (vni_id, bd_id) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_BD") + self.updates_cmd.append("bridge-domain %s" % bd_id) + self.updates_cmd.append("vxlan vni %s" % vni_id) + self.changed = True + + def config_merge_mode(self, nve_name, mode): + """config nve mode""" + + if self.is_nve_mode_change(nve_name, mode): + cfg_xml = CE_NC_MERGE_NVE_MODE % (nve_name, mode) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_MODE") + self.updates_cmd.append("interface %s" % nve_name) + if mode == "mode-l3": + self.updates_cmd.append("mode l3") + else: + self.updates_cmd.append("undo mode l3") + self.changed = True + + def config_merge_source_ip(self, nve_name, source_ip): + """config nve source ip""" + + if self.is_nve_source_ip_change(nve_name, source_ip): + cfg_xml = CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL % ( + nve_name, source_ip) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_SOURCE_IP") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("source %s" % source_ip) + self.changed = True + + def config_merge_vni_peer_ip(self, nve_name, vni_id, peer_ip_list): + """config vni peer ip""" + + if self.is_vni_peer_list_change(nve_name, vni_id, peer_ip_list): + cfg_xml = CE_NC_MERGE_VNI_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_MERGE_VNI_PEER_ADDRESS_IP_MERGE % peer_ip + cfg_xml += CE_NC_MERGE_VNI_PEER_ADDRESS_IP_END + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_PEER_IP") + self.updates_cmd.append("interface %s" % nve_name) + + for peer_ip in peer_ip_list: + cmd_output = "vni %s head-end peer-list %s" % (vni_id, peer_ip) + self.updates_cmd.append(cmd_output) + self.changed = True + + def config_merge_vni_protocol_type(self, nve_name, vni_id, protocol_type): + """config vni protocol type""" + + if self.is_vni_protocol_change(nve_name, vni_id, protocol_type): + cfg_xml = CE_NC_MERGE_VNI_PROTOCOL % ( + nve_name, vni_id, protocol_type) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_PEER_PROTOCOL") + self.updates_cmd.append("interface %s" % nve_name) + + if protocol_type == "bgp": + self.updates_cmd.append( + "vni %s head-end peer-list protocol %s" % (vni_id, protocol_type)) + else: + self.updates_cmd.append( + "undo vni %s head-end peer-list protocol bgp" % vni_id) + self.changed = True + + def config_delete_vni2bd(self, bd_id, vni_id): + """remove vni to bd id""" + + if not self.is_vni_bd_exist(vni_id, bd_id): + return + cfg_xml = CE_NC_DELETE_VNI_BD_ID % (vni_id, bd_id) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_BD") + self.updates_cmd.append( + "bridge-domain %s" % bd_id) + self.updates_cmd.append( + "undo vxlan vni %s" % vni_id) + + self.changed = True + + def config_delete_mode(self, nve_name, mode): + """nve mode""" + + if mode == "mode-l3": + if not self.is_nve_mode_exist(nve_name, mode): + return + cfg_xml = CE_NC_MERGE_NVE_MODE % (nve_name, "mode-l2") + + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_MODE") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("undo mode l3") + self.changed = True + else: + self.module.fail_json( + msg='Error: Can not configure undo mode l2.') + + def config_delete_source_ip(self, nve_name, source_ip): + """nve source ip""" + + if not self.is_nve_source_ip_exist(nve_name, source_ip): + return + ipaddr = "0.0.0.0" + cfg_xml = CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL % ( + nve_name, ipaddr) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_SOURCE_IP") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("undo source %s" % source_ip) + self.changed = True + + def config_delete_vni_peer_ip(self, nve_name, vni_id, peer_ip_list): + """remove vni peer ip""" + + for peer_ip in peer_ip_list: + if not self.is_vni_peer_list_exist(nve_name, vni_id, peer_ip): + self.module.fail_json(msg='Error: The %s does not exist' % peer_ip) + + config = False + + nve_peer_info = list() + for nve_peer in self.nve_info["vni_peer_ips"]: + if nve_peer["vniId"] == vni_id: + nve_peer_info = nve_peer.get("peerAddr") + for peer in nve_peer_info: + if peer not in peer_ip_list: + config = True + + if not config: + cfg_xml = CE_NC_DELETE_VNI_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE % peer_ip + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_END + else: + cfg_xml = CE_NC_DELETE_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE % peer_ip + cfg_xml += CE_NC_DELETE_PEER_ADDRESS_IP_END + + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_PEER_IP") + self.updates_cmd.append("interface %s" % nve_name) + + for peer_ip in peer_ip_list: + cmd_output = "undo vni %s head-end peer-list %s" % (vni_id, peer_ip) + self.updates_cmd.append(cmd_output) + + self.changed = True + + def config_delete_vni_protocol_type(self, nve_name, vni_id, protocol_type): + """remove vni protocol type""" + + if not self.is_vni_protocol_exist(nve_name, vni_id, protocol_type): + return + + cfg_xml = CE_NC_DELETE_VNI_PROTOCOL % (nve_name, vni_id, protocol_type) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_PEER_PROTOCOL") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append( + "undo vni %s head-end peer-list protocol bgp " % vni_id) + self.changed = True + + def check_params(self): + """Check all input params""" + + # bridge_domain_id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of bridge domain id is invalid.') + if int(self.bridge_domain_id) > 16777215 or int(self.bridge_domain_id) < 1: + self.module.fail_json( + msg='Error: The bridge domain id must be an integer between 1 and 16777215.') + # vni_id check + if self.vni_id: + if not self.vni_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of vni id is invalid.') + if int(self.vni_id) > 16000000 or int(self.vni_id) < 1: + self.module.fail_json( + msg='Error: The vni id must be an integer between 1 and 16000000.') + + # nve_name check + if self.nve_name: + if not self.check_nve_name(): + self.module.fail_json( + msg='Error: Error: NVE interface %s is invalid.' % self.nve_name) + + # peer_list_ip check + if self.peer_list_ip: + for peer_ip in self.peer_list_ip: + if not is_valid_address(peer_ip): + self.module.fail_json( + msg='Error: The ip address %s is invalid.' % self.peer_list_ip) + # source_ip check + if self.source_ip: + if not is_valid_address(self.source_ip): + self.module.fail_json( + msg='Error: The ip address %s is invalid.' % self.source_ip) + + def get_proposed(self): + """get proposed info""" + + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + if self.vni_id: + self.proposed["vni_id"] = self.vni_id + if self.nve_name: + self.proposed["nve_name"] = self.nve_name + if self.nve_mode: + self.proposed["nve_mode"] = self.nve_mode + if self.peer_list_ip: + self.proposed["peer_list_ip"] = self.peer_list_ip + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.vni2bd_info: + self.existing["vni_to_bridge_domain"] = self.vni2bd_info[ + "vni2BdInfos"] + + if self.nve_info: + self.existing["nve_interface_name"] = self.nve_info["ifName"] + self.existing["source_ip"] = self.nve_info["srcAddr"] + self.existing["nve_mode"] = self.nve_info["nveType"] + self.existing["vni_peer_list_ip"] = self.nve_info[ + "vni_peer_ips"] + self.existing["vni_peer_list_protocol"] = self.nve_info[ + "vni_peer_protocols"] + + def get_end_state(self): + """get end state info""" + + vni2bd_info = self.get_vni2bd_dict() + if vni2bd_info: + self.end_state["vni_to_bridge_domain"] = vni2bd_info["vni2BdInfos"] + + nve_info = self.get_nve_dict(self.nve_name) + if nve_info: + self.end_state["nve_interface_name"] = nve_info["ifName"] + self.end_state["source_ip"] = nve_info["srcAddr"] + self.end_state["nve_mode"] = nve_info["nveType"] + self.end_state["vni_peer_list_ip"] = nve_info[ + "vni_peer_ips"] + self.end_state["vni_peer_list_protocol"] = nve_info[ + "vni_peer_protocols"] + + def work(self): + """worker""" + + self.check_params() + self.vni2bd_info = self.get_vni2bd_dict() + if self.nve_name: + self.nve_info = self.get_nve_dict(self.nve_name) + self.get_existing() + self.get_proposed() + # deal present or absent + if self.state == "present": + if self.bridge_domain_id and self.vni_id: + self.config_merge_vni2bd(self.bridge_domain_id, self.vni_id) + if self.nve_name: + if self.check_nve_interface(self.nve_name): + if self.nve_mode: + self.config_merge_mode(self.nve_name, self.nve_mode) + if self.source_ip: + self.config_merge_source_ip( + self.nve_name, self.source_ip) + if self.vni_id and self.peer_list_ip: + self.config_merge_vni_peer_ip( + self.nve_name, self.vni_id, self.peer_list_ip) + if self.vni_id and self.protocol_type: + self.config_merge_vni_protocol_type( + self.nve_name, self.vni_id, self.protocol_type) + else: + self.module.fail_json( + msg='Error: Nve interface %s does not exist.' % self.nve_name) + + else: + if self.bridge_domain_id and self.vni_id: + self.config_delete_vni2bd(self.bridge_domain_id, self.vni_id) + if self.nve_name: + if self.check_nve_interface(self.nve_name): + if self.nve_mode: + self.config_delete_mode(self.nve_name, self.nve_mode) + if self.source_ip: + self.config_delete_source_ip( + self.nve_name, self.source_ip) + if self.vni_id and self.peer_list_ip: + self.config_delete_vni_peer_ip( + self.nve_name, self.vni_id, self.peer_list_ip) + if self.vni_id and self.protocol_type: + self.config_delete_vni_protocol_type( + self.nve_name, self.vni_id, self.protocol_type) + else: + self.module.fail_json( + msg='Error: Nve interface %s does not exist.' % self.nve_name) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bridge_domain_id=dict(required=False), + vni_id=dict(required=False, type='str'), + nve_name=dict(required=False, type='str'), + nve_mode=dict(required=False, choices=['mode-l2', 'mode-l3']), + peer_list_ip=dict(required=False, type='list'), + protocol_type=dict(required=False, type='str', choices=[ + 'bgp', 'null']), + + source_ip=dict(required=False), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanTunnel(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_vap.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_vap.py new file mode 100644 index 00000000..fb1b096c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ce_vxlan_vap.py @@ -0,0 +1,933 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_vap +short_description: Manages VXLAN virtual access point on HUAWEI CloudEngine Devices. +description: + - Manages VXLAN Virtual access point on HUAWEI CloudEngine Devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. + The value is an integer ranging from 1 to 16777215. + bind_vlan_id: + description: + - Specifies the VLAN binding to a BD(Bridge Domain). + The value is an integer ranging ranging from 1 to 4094. + l2_sub_interface: + description: + - Specifies an Sub-Interface full name, i.e. "10GE1/0/41.1". + The value is a string of 1 to 63 case-insensitive characters, spaces supported. + encapsulation: + description: + - Specifies an encapsulation type of packets allowed to pass through a Layer 2 sub-interface. + choices: ['dot1q', 'default', 'untag', 'qinq', 'none'] + ce_vid: + description: + - When I(encapsulation) is 'dot1q', specifies a VLAN ID in the outer VLAN tag. + When I(encapsulation) is 'qinq', specifies an outer VLAN ID for + double-tagged packets to be received by a Layer 2 sub-interface. + The value is an integer ranging from 1 to 4094. + pe_vid: + description: + - When I(encapsulation) is 'qinq', specifies an inner VLAN ID for + double-tagged packets to be received by a Layer 2 sub-interface. + The value is an integer ranging from 1 to 4094. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan vap module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create a mapping between a VLAN and a BD + community.network.ce_vxlan_vap: + bridge_domain_id: 100 + bind_vlan_id: 99 + provider: "{{ cli }}" + + - name: Bind a Layer 2 sub-interface to a BD + community.network.ce_vxlan_vap: + bridge_domain_id: 100 + l2_sub_interface: 10GE2/0/20.1 + provider: "{{ cli }}" + + - name: Configure an encapsulation type on a Layer 2 sub-interface + community.network.ce_vxlan_vap: + l2_sub_interface: 10GE2/0/20.1 + encapsulation: dot1q + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_vlan_id": "99", state="present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_intf_list": ["10GE2/0/20.1", "10GE2/0/20.2"], + "bind_vlan_list": []} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_intf_list": ["110GE2/0/20.1", "10GE2/0/20.2"], + "bind_vlan_list": ["99"]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["bridge-domain 100", + "l2 binding vlan 99"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_BD_VAP = """ + + + + + %s + + + + + + + + + + + + +""" + +CE_NC_MERGE_BD_VLAN = """ + + + + + %s + + %s:%s + + + + + +""" + +CE_NC_MERGE_BD_INTF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_DELETE_BD_INTF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_GET_ENCAP = """ + + + + + %s + + + + + + + + + + + + + + +""" + +CE_NC_SET_ENCAP = """ + + + + + %s + %s + + + + +""" + +CE_NC_UNSET_ENCAP = """ + + + + + %s + none + + + + +""" + +CE_NC_SET_ENCAP_DOT1Q = """ + + + + + %s + dot1q + + %s:%s + + + + + +""" + +CE_NC_SET_ENCAP_QINQ = """ + + + + + %s + qinq + + + %s + %s:%s + + + + + + +""" + + +def vlan_vid_to_bitmap(vid): + """convert VLAN list to VLAN bitmap""" + + vlan_bit = ['0'] * 1024 + int_vid = int(vid) + j = int_vid // 4 + bit_int = 0x8 >> (int_vid % 4) + vlan_bit[j] = str(hex(bit_int))[2] + + return ''.join(vlan_bit) + + +def bitmap_to_vlan_list(bitmap): + """convert VLAN bitmap to VLAN list""" + + tmp = list() + if not bitmap: + return tmp + + bit_len = len(bitmap) + for i in range(bit_len): + if bitmap[i] == "0": + continue + bit = int(bitmap[i]) + if bit & 0x8: + tmp.append(str(i * 4)) + if bit & 0x4: + tmp.append(str(i * 4 + 1)) + if bit & 0x2: + tmp.append(str(i * 4 + 2)) + if bit & 0x1: + tmp.append(str(i * 4 + 3)) + + return tmp + + +def is_vlan_bitmap_empty(bitmap): + """check VLAN bitmap empty""" + + if not bitmap or len(bitmap) == 0: + return True + + for bit in bitmap: + if bit != '0': + return False + + return True + + +def is_vlan_in_bitmap(vid, bitmap): + """check is VLAN id in bitmap""" + + if is_vlan_bitmap_empty(bitmap): + return False + + i = int(vid) // 4 + if i > len(bitmap): + return False + + if int(bitmap[i]) & (0x8 >> (int(vid) % 4)): + return True + + return False + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class VxlanVap(object): + """ + Manages VXLAN virtual access point. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.bind_vlan_id = self.module.params['bind_vlan_id'] + self.l2_sub_interface = self.module.params['l2_sub_interface'] + self.ce_vid = self.module.params['ce_vid'] + self.pe_vid = self.module.params['pe_vid'] + self.encapsulation = self.module.params['encapsulation'] + self.state = self.module.params['state'] + + # state + self.vap_info = dict() + self.l2sub_info = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [()] + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_bd_vap_dict(self): + """get virtual access point info""" + + vap_info = dict() + conf_str = CE_NC_GET_BD_VAP % self.bridge_domain_id + xml_str = get_nc_config(self.module, conf_str) + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get vap: VLAN + vap_info["bdId"] = self.bridge_domain_id + root = ElementTree.fromstring(xml_str) + vap_info["vlanList"] = "" + vap_vlan = root.find("evc/bds/bd/bdBindVlan") + if vap_vlan: + for ele in vap_vlan: + if ele.tag == "vlanList": + vap_info["vlanList"] = ele.text + + # get vap: l2 su-interface + vap_ifs = root.findall( + "evc/bds/bd/servicePoints/servicePoint/ifName") + if_list = list() + if vap_ifs: + for vap_if in vap_ifs: + if vap_if.tag == "ifName": + if_list.append(vap_if.text) + vap_info["intfList"] = if_list + + return vap_info + + def get_l2_sub_intf_dict(self, ifname): + """get l2 sub-interface info""" + + intf_info = dict() + if not ifname: + return intf_info + + conf_str = CE_NC_GET_ENCAP % ifname + xml_str = get_nc_config(self.module, conf_str) + + if "" in xml_str: + return intf_info + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get l2 sub interface encapsulation info + root = ElementTree.fromstring(xml_str) + bds = root.find("ethernet/servicePoints/servicePoint") + if not bds: + return intf_info + + for ele in bds: + if ele.tag in ["ifName", "flowType"]: + intf_info[ele.tag] = ele.text.lower() + + if intf_info.get("flowType") == "dot1q": + ce_vid = root.find( + "ethernet/servicePoints/servicePoint/flowDot1qs") + intf_info["dot1qVids"] = "" + if ce_vid: + for ele in ce_vid: + if ele.tag == "dot1qVids": + intf_info["dot1qVids"] = ele.text + elif intf_info.get("flowType") == "qinq": + vids = root.find( + "ethernet/servicePoints/servicePoint/flowQinqs/flowQinq") + if vids: + for ele in vids: + if ele.tag in ["peVlanId", "ceVids"]: + intf_info[ele.tag] = ele.text + + return intf_info + + def config_traffic_encap_dot1q(self): + """configure traffic encapsulation type dot1q""" + + xml_str = "" + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + if self.ce_vid: + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_DOT1Q % ( + self.l2_sub_interface, vlan_bitmap, vlan_bitmap) + self.updates_cmd.append("encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.ce_vid and not is_vlan_in_bitmap( + self.ce_vid, self.l2sub_info.get("dot1qVids")): + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_DOT1Q % ( + self.l2_sub_interface, vlan_bitmap, vlan_bitmap) + self.updates_cmd.append("encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + if self.ce_vid: + if is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("dot1qVids")): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append("undo encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + + if not xml_str: + self.updates_cmd.pop() + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP_DOT1Q") + self.changed = True + + def config_traffic_encap_qinq(self): + """configure traffic encapsulation type qinq""" + + xml_str = "" + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + if self.ce_vid: + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_QINQ % (self.l2_sub_interface, + self.pe_vid, + vlan_bitmap, + vlan_bitmap) + self.updates_cmd.append( + "encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.ce_vid: + if not is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("ceVids")) \ + or self.pe_vid != self.l2sub_info.get("peVlanId"): + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_QINQ % (self.l2_sub_interface, + self.pe_vid, + vlan_bitmap, + vlan_bitmap) + self.updates_cmd.append( + "encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + if self.ce_vid: + if is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("ceVids")) \ + and self.pe_vid == self.l2sub_info.get("peVlanId"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + + if not xml_str: + self.updates_cmd.pop() + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP_QINQ") + self.changed = True + + def config_traffic_encap(self): + """configure traffic encapsulation types""" + + if not self.l2sub_info: + self.module.fail_json(msg="Error: Interface %s does not exist." % self.l2_sub_interface) + + if not self.encapsulation: + return + + xml_str = "" + if self.encapsulation in ["default", "untag"]: + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + elif self.encapsulation == "none": + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo encapsulation %s" % self.l2sub_info.get("flowType")) + elif self.encapsulation == "dot1q": + self.config_traffic_encap_dot1q() + return + elif self.encapsulation == "qinq": + self.config_traffic_encap_qinq() + return + else: + pass + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP") + self.changed = True + + def config_vap_sub_intf(self): + """configure a Layer 2 sub-interface as a service access point""" + + if not self.vap_info: + self.module.fail_json(msg="Error: Bridge domain %s does not exist." % self.bridge_domain_id) + + xml_str = "" + if self.state == "present": + if self.l2_sub_interface not in self.vap_info["intfList"]: + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + xml_str = CE_NC_MERGE_BD_INTF % ( + self.bridge_domain_id, self.l2_sub_interface) + else: + if self.l2_sub_interface in self.vap_info["intfList"]: + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo bridge-domain %s" % self.bridge_domain_id) + xml_str = CE_NC_DELETE_BD_INTF % ( + self.bridge_domain_id, self.l2_sub_interface) + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_VAP_SUB_INTERFACE") + self.changed = True + + def config_vap_vlan(self): + """configure a VLAN as a service access point""" + + xml_str = "" + if self.state == "present": + if not is_vlan_in_bitmap(self.bind_vlan_id, self.vap_info["vlanList"]): + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + self.updates_cmd.append( + "l2 binding vlan %s" % self.bind_vlan_id) + vlan_bitmap = vlan_vid_to_bitmap(self.bind_vlan_id) + xml_str = CE_NC_MERGE_BD_VLAN % ( + self.bridge_domain_id, vlan_bitmap, vlan_bitmap) + else: + if is_vlan_in_bitmap(self.bind_vlan_id, self.vap_info["vlanList"]): + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + self.updates_cmd.append( + "undo l2 binding vlan %s" % self.bind_vlan_id) + vlan_bitmap = vlan_vid_to_bitmap(self.bind_vlan_id) + xml_str = CE_NC_MERGE_BD_VLAN % ( + self.bridge_domain_id, "0" * 1024, vlan_bitmap) + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_VAP_VLAN") + self.changed = True + + def is_vlan_valid(self, vid, name): + """check VLAN id""" + + if not vid: + return + + if not vid.isdigit(): + self.module.fail_json(msg="Error: %s is not digit." % name) + return + + if int(vid) < 1 or int(vid) > 4094: + self.module.fail_json( + msg="Error: %s is not in the range from 1 to 4094." % name) + + def is_l2_sub_intf_valid(self, ifname): + """check l2 sub interface valid""" + + if ifname.count('.') != 1: + return False + + if_num = ifname.split('.')[1] + if not if_num.isdigit(): + return False + + if int(if_num) < 1 or int(if_num) > 4096: + self.module.fail_json( + msg="Error: Sub-interface number is not in the range from 1 to 4096.") + return False + + if not get_interface_type(ifname): + return False + + return True + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: Bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: Bridge domain id is not in the range from 1 to 16777215.") + + # check bind_vlan_id + if self.bind_vlan_id: + self.is_vlan_valid(self.bind_vlan_id, "bind_vlan_id") + + # check l2_sub_interface + if self.l2_sub_interface and not self.is_l2_sub_intf_valid(self.l2_sub_interface): + self.module.fail_json(msg="Error: l2_sub_interface is invalid.") + + # check ce_vid + if self.ce_vid: + self.is_vlan_valid(self.ce_vid, "ce_vid") + if not self.encapsulation or self.encapsulation not in ["dot1q", "qinq"]: + self.module.fail_json(msg="Error: ce_vid can not be set " + "when encapsulation is '%s'." % self.encapsulation) + if self.encapsulation == "qinq" and not self.pe_vid: + self.module.fail_json(msg="Error: ce_vid and pe_vid must be set at the same time " + "when encapsulation is '%s'." % self.encapsulation) + # check pe_vid + if self.pe_vid: + self.is_vlan_valid(self.pe_vid, "pe_vid") + if not self.encapsulation or self.encapsulation != "qinq": + self.module.fail_json(msg="Error: pe_vid can not be set " + "when encapsulation is '%s'." % self.encapsulation) + if not self.ce_vid: + self.module.fail_json(msg="Error: ce_vid and pe_vid must be set at the same time " + "when encapsulation is '%s'." % self.encapsulation) + + def get_proposed(self): + """get proposed info""" + + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + if self.bind_vlan_id: + self.proposed["bind_vlan_id"] = self.bind_vlan_id + if self.l2_sub_interface: + self.proposed["l2_sub_interface"] = self.l2_sub_interface + if self.encapsulation: + self.proposed["encapsulation"] = self.encapsulation + if self.ce_vid: + self.proposed["ce_vid"] = self.ce_vid + if self.pe_vid: + self.proposed["pe_vid"] = self.pe_vid + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + self.existing["bridge_domain_id"] = self.bridge_domain_id + self.existing["bind_vlan_list"] = bitmap_to_vlan_list( + self.vap_info.get("vlanList")) + self.existing["bind_intf_list"] = self.vap_info.get("intfList") + + if self.encapsulation and self.l2_sub_interface: + self.existing["l2_sub_interface"] = self.l2_sub_interface + self.existing["encapsulation"] = self.l2sub_info.get("flowType") + if self.existing["encapsulation"] == "dot1q": + self.existing["ce_vid"] = bitmap_to_vlan_list( + self.l2sub_info.get("dot1qVids")) + if self.existing["encapsulation"] == "qinq": + self.existing["ce_vid"] = bitmap_to_vlan_list( + self.l2sub_info.get("ceVids")) + self.existing["pe_vid"] = self.l2sub_info.get("peVlanId") + + def get_end_state(self): + """get end state info""" + + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + vap_info = self.get_bd_vap_dict() + self.end_state["bridge_domain_id"] = self.bridge_domain_id + self.end_state["bind_vlan_list"] = bitmap_to_vlan_list( + vap_info.get("vlanList")) + self.end_state["bind_intf_list"] = vap_info.get("intfList") + + if self.encapsulation and self.l2_sub_interface: + l2sub_info = self.get_l2_sub_intf_dict(self.l2_sub_interface) + self.end_state["l2_sub_interface"] = self.l2_sub_interface + self.end_state["encapsulation"] = l2sub_info.get("flowType") + if self.end_state["encapsulation"] == "dot1q": + self.end_state["ce_vid"] = bitmap_to_vlan_list( + l2sub_info.get("dot1qVids")) + if self.end_state["encapsulation"] == "qinq": + self.end_state["ce_vid"] = bitmap_to_vlan_list( + l2sub_info.get("ceVids")) + self.end_state["pe_vid"] = l2sub_info.get("peVlanId") + + def data_init(self): + """data init""" + if self.l2_sub_interface: + self.l2_sub_interface = self.l2_sub_interface.replace( + " ", "").upper() + if self.encapsulation and self.l2_sub_interface: + self.l2sub_info = self.get_l2_sub_intf_dict(self.l2_sub_interface) + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + self.vap_info = self.get_bd_vap_dict() + + def work(self): + """worker""" + + self.check_params() + self.data_init() + self.get_existing() + self.get_proposed() + + # Traffic encapsulation types + if self.encapsulation and self.l2_sub_interface: + self.config_traffic_encap() + + # A VXLAN service access point can be a Layer 2 sub-interface or VLAN + if self.bridge_domain_id: + if self.l2_sub_interface: + # configure a Layer 2 sub-interface as a service access point + self.config_vap_sub_intf() + + if self.bind_vlan_id: + # configure a VLAN as a service access point + self.config_vap_vlan() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bridge_domain_id=dict(required=False, type='str'), + bind_vlan_id=dict(required=False, type='str'), + l2_sub_interface=dict(required=False, type='str'), + encapsulation=dict(required=False, type='str', + choices=['dot1q', 'default', 'untag', 'qinq', 'none']), + ce_vid=dict(required=False, type='str'), + pe_vid=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanVap(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_backup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_backup.py new file mode 100644 index 00000000..5105e759 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_backup.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Backup Config to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_backup +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Backup the current running or startup configuration to a + remote server on devices running Lenovo CNOS +description: + - This module allows you to work with switch configurations. It provides a + way to back up the running or startup configurations of a switch to a + remote server. This is achieved by periodically saving a copy of the + startup or running configuration of the network device to a remote server + using FTP, SFTP, TFTP, or SCP. The first step is to create a directory from + where the remote server can be reached. The next step is to provide the + full file path of the location where the configuration will be backed up. + Authentication details required by the remote server must be provided as + well. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + configType: + description: + - This specifies what type of configuration will be backed up. The + choices are the running or startup configurations. There is no + default value, so it will result in an error if the input is + incorrect. + required: Yes + default: Null + choices: [running-config, startup-config] + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server to where to upload the backup + configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is + not specified, there is no default value to be used. + required: Yes + default: Null + choices: [SFTP, SCP, FTP, TFTP] + rcserverip: + description: + -This specifies the IP Address of the remote server to where the + configuration will be backed up. + required: Yes + default: Null + rcpath: + description: + - This specifies the full file path where the configuration file + will be copied on the remote server. In case the relative path is + used as the variable value, the root folder for the user of the + server needs to be specified. + required: Yes + default: Null + serverusername: + description: + - Specify the username for the server relating to the protocol + used. + required: Yes + default: Null + serverpassword: + description: + - Specify the password for the server relating to the protocol + used. + required: Yes + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_backup. + These are written in the main.yml file of the tasks directory. +--- +- name: Test Running Config Backup + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Startup Config Backup + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Running Config Backup -TFTP + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Startup Config Backup - TFTP + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Config file transferred to server" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +# Utility Method to back up the running config or start up config +# This method supports only SCP or SFTP or FTP or TFTP +# Tuning of timeout parameter is pending +def doConfigBackUp(module, prompt, answer): + host = module.params['host'] + server = module.params['serverip'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + protocol = module.params['protocol'].lower() + rcPath = module.params['rcpath'] + configType = module.params['configType'] + confPath = rcPath + host + '_' + configType + '.txt' + + retVal = '' + + # config backup command happens here + command = "copy " + configType + " " + protocol + " " + protocol + "://" + command = command + username + "@" + server + "/" + confPath + command = command + " vrf management\n" + cnos.debugOutput(command + "\n") + # cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes') + cmd = [] + if(protocol == "scp"): + scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}] + scp_cmd2 = [{'command': '\n', 'prompt': 'Password:', + 'answer': password}] + cmd.extend(scp_cmd1) + cmd.extend(scp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + sftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(sftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + ftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(ftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + configType + " " + protocol + " " + protocol + command = command + "://" + server + "/" + confPath + command = command + " vrf management\n" + # cnos.debugOutput(command) + tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + configType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + rcpath=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + if(protocol == "tftp" or protocol == "ftp" or + protocol == "sftp" or protocol == "scp"): + transfer_status = doConfigBackUp(module, None, None) + else: + transfer_status = "Invalid Protocol option" + + output = output + "\n Config Back Up status \n" + transfer_status + + # Save it into the file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Config file transferred to server") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_banner.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_banner.py new file mode 100644 index 00000000..09622ffe --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_banner.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send banner commands to Lenovo Switches +# Two types of banners are supported login and motd +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_banner +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage multiline banners on Lenovo CNOS devices +description: + - This will configure both login and motd banners on remote devices + running Lenovo CNOS. It allows playbooks to add or remote + banner text from the active running configuration. +notes: + - Tested against CNOS 10.8.1 +options: + banner: + description: + - Specifies which banner should be configured on the remote device. + In Ansible 2.8 and earlier only I(login) and I(motd) were supported. + required: true + choices: ['login', 'motd'] + text: + description: + - The banner text that should be + present in the remote device running configuration. This argument + accepts a multiline string, with no empty lines. Requires + I(state=present). + state: + description: + - Specifies whether or not the configuration is + present in the current devices active running configuration. + default: present + choices: ['present', 'absent'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Configure the login banner + community.network.cnos_banner: + banner: login + text: | + this is my login banner + that contains a multiline + string + state: present + +- name: Remove the motd banner + community.network.cnos_banner: + banner: motd + state: absent + +- name: Configure banner from file + community.network.cnos_banner: + banner: motd + text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}" + state: present + +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner login + - this is my login banner + - that contains a multiline + - string +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils._text import to_text +import re + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + + if state == 'absent' and 'text' in have.keys() and have['text']: + commands.append('no banner %s' % module.params['banner']) + + elif state == 'present': + if want['text'] and (want['text'] != have.get('text')): + banner_cmd = 'banner %s ' % module.params['banner'] + for bline in want['text'].strip().splitlines(): + final_cmd = banner_cmd + bline.strip() + commands.append(final_cmd) + + return commands + + +def map_config_to_obj(module): + rc, out, err = exec_command(module, + 'show banner %s' % module.params['banner']) + if rc == 0: + output = out + else: + rc, out, err = exec_command(module, + 'show running-config | include banner %s' + % module.params['banner']) + if out: + output = re.search(r'\^C(.*)\^C', out, re.S).group(1).strip() + else: + output = None + obj = {'banner': module.params['banner'], 'state': 'absent'} + if output: + obj['text'] = output + obj['state'] = 'present' + return obj + + +def map_params_to_obj(module): + text = module.params['text'] + if text: + text = to_text(text).strip() + + return { + 'banner': module.params['banner'], + 'text': text, + 'state': module.params['state'] + } + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + banner=dict(required=True, choices=['login', 'motd']), + text=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(cnos_argument_spec) + + required_if = [('state', 'present', ('text',))] + + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + response = load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_bgp.py new file mode 100644 index 00000000..597a4abd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_bgp.py @@ -0,0 +1,1176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send BGP commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_bgp +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage BGP resources and attributes on devices running CNOS +description: + - This module allows you to work with Border Gateway Protocol (BGP) related + configurations. The operators used are overloaded to ensure control over + switch BGP configurations. This module is invoked using method with + asNumber as one of its arguments. The first level of the BGP configuration + allows to set up an AS number, with the following attributes going + into various configuration operations under the context of BGP. + After passing this level, there are eight BGP arguments that will perform + further configurations. They are bgpArg1, bgpArg2, bgpArg3, bgpArg4, + bgpArg5, bgpArg6, bgpArg7, and bgpArg8. For more details on how to use + these arguments, see [Overloaded Variables]. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + asNum: + description: + - AS number + required: Yes + default: Null + bgpArg1: + description: + - This is an overloaded bgp first argument. Usage of this argument + can be found is the User Guide referenced above. + required: Yes + default: Null + choices: [address-family,bestpath,bgp,cluster-id,confederation, + enforce-first-as,fast-external-failover,graceful-restart, + graceful-restart-helper,log-neighbor-changes, + maxas-limit,neighbor,router-id,shutdown,synchronization, + timers,vrf] + bgpArg2: + description: + - This is an overloaded bgp second argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [ipv4 or ipv6, always-compare-med,compare-confed-aspath, + compare-routerid,dont-compare-originator-id,tie-break-on-age, + as-path,med,identifier,peers] + bgpArg3: + description: + - This is an overloaded bgp third argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [aggregate-address,client-to-client,dampening,distance, + maximum-paths,network,nexthop,redistribute,save, + synchronization,ignore or multipath-relax, + confed or missing-as-worst or non-deterministic or + remove-recv-med or remove-send-med] + bgpArg4: + description: + - This is an overloaded bgp fourth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Aggregate prefix, Reachability Half-life time,route-map, + Distance for routes ext,ebgp or ibgp,IP prefix , + IP prefix /, synchronization, + Delay value, direct, ospf, static, memory] + bgpArg5: + description: + - This is an overloaded bgp fifth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [as-set, summary-only, Value to start reusing a route, + Distance for routes internal, Supported multipath numbers, + backdoor, map, route-map ] + bgpArg6: + description: + - This is an overloaded bgp sixth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [summary-only,as-set, route-map name, + Value to start suppressing a route, Distance local routes, + Network mask, Pointer to route-map entries] + bgpArg7: + description: + - This is an overloaded bgp seventh argument. Use of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Maximum duration to suppress a stable route(minutes), + backdoor,route-map, Name of the route map ] + bgpArg8: + description: + - This is an overloaded bgp eight argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Un-reachability Half-life time for the penalty(minutes), + backdoor] +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module cnos_bgp. These are + written in the main.yml file of the tasks directory. +--- +- name: Test BGP - neighbor + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "address-family" + bgpArg5: "ipv4" + bgpArg6: "next-hop-self" + +- name: Test BGP - BFD + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "bfd" + +- name: Test BGP - address-family - dampening + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "address-family" + bgpArg2: "ipv4" + bgpArg3: "dampening" + bgpArg4: 13 + bgpArg5: 233 + bgpArg6: 333 + bgpArg7: 15 + bgpArg8: 33 + +- name: Test BGP - address-family - network + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "address-family" + bgpArg2: "ipv4" + bgpArg3: "network" + bgpArg4: "1.2.3.4/5" + bgpArg5: "backdoor" + +- name: Test BGP - bestpath - always-compare-med + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bestpath" + bgpArg2: "always-compare-med" + +- name: Test BGP - bestpath-compare-confed-aspat + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bestpath" + bgpArg2: "compare-confed-aspath" + +- name: Test BGP - bgp + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bgp" + bgpArg2: 33 + +- name: Test BGP - cluster-id + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "cluster-id" + bgpArg2: "1.2.3.4" + +- name: Test BGP - confederation-identifier + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "confederation" + bgpArg2: "identifier" + bgpArg3: 333 + +- name: Test BGP - enforce-first-as + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "enforce-first-as" + +- name: Test BGP - fast-external-failover + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "fast-external-failover" + +- name: Test BGP - graceful-restart + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "graceful-restart" + bgpArg2: 333 + +- name: Test BGP - graceful-restart-helper + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "graceful-restart-helper" + +- name: Test BGP - maxas-limit + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "maxas-limit" + bgpArg2: 333 + +- name: Test BGP - neighbor + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "address-family" + bgpArg5: "ipv4" + bgpArg6: "next-hop-self" + +- name: Test BGP - router-id + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "router-id" + bgpArg2: "1.2.3.4" + +- name: Test BGP - synchronization + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "synchronization" + +- name: Test BGP - timers + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "timers" + bgpArg2: 333 + bgpArg3: 3333 + +- name: Test BGP - vrf + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "vrf" + +''' +RETURN = ''' +msg: + description: Success or failure message. Upon any failure, the method returns + an error display string. + returned: always + type: str +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def bgpNeighborConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpNeighborArg1 = module.params['bgpArg4'] + bgpNeighborArg2 = module.params['bgpArg5'] + bgpNeighborArg3 = module.params['bgpArg6'] + bgpNeighborArg4 = module.params['bgpArg7'] + bgpNeighborArg5 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + + if(bgpNeighborArg1 == "address-family"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_address_family", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " unicast" + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpNeighborAFConfig(module, cmd, '(config-router-neighbor-af)#', answer) + return retVal + else: + retVal = "Error-316" + return retVal + + elif(bgpNeighborArg1 == "advertisement-interval"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "bfd"): + command = command + bgpNeighborArg1 + " " + if(bgpNeighborArg2 is not None and bgpNeighborArg2 == "mutihop"): + command = command + bgpNeighborArg2 + + elif(bgpNeighborArg1 == "connection-retry-time"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_connection_retrytime", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-315" + return retVal + + elif(bgpNeighborArg1 == "description"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_description", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-314" + return retVal + + elif(bgpNeighborArg1 == "disallow-infinite-holdtime"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "dont-capability-negotiate"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "dynamic-capability"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "ebgp-multihop"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_maxhopcount", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-313" + return retVal + + elif(bgpNeighborArg1 == "interface"): + command = command + bgpNeighborArg1 + " " + # TBD + + elif(bgpNeighborArg1 == "local-as"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_local_as", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + if(bgpNeighborArg3 is not None and + bgpNeighborArg3 == "no-prepend"): + command = command + bgpNeighborArg3 + " " + if(bgpNeighborArg4 is not None and + bgpNeighborArg4 == "replace-as"): + command = command + bgpNeighborArg4 + " " + if(bgpNeighborArg5 is not None and + bgpNeighborArg5 == "dual-as"): + command = command + bgpNeighborArg5 + else: + command = command.strip() + else: + command = command.strip() + else: + command = command.strip() + else: + retVal = "Error-312" + return retVal + + elif(bgpNeighborArg1 == "maximum-peers"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_maxpeers", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-311" + return retVal + + elif(bgpNeighborArg1 == "password"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_password", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-310" + return retVal + + elif(bgpNeighborArg1 == "remove-private-AS"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "timers"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_timers_Keepalive", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_timers_holdtime", bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-309" + return retVal + else: + retVal = "Error-308" + return retVal + + elif(bgpNeighborArg1 == "transport"): + command = command + bgpNeighborArg1 + " connection-mode passive " + + elif(bgpNeighborArg1 == "ttl-security"): + command = command + bgpNeighborArg1 + " hops " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_ttl_hops", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-307" + return retVal + + elif(bgpNeighborArg1 == "update-source"): + command = command + bgpNeighborArg1 + " " + if(bgpNeighborArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_options", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + if(bgpNeighborArg2 == "ethernet"): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_ethernet", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-304" + return retVal + elif(bgpNeighborArg2 == "loopback"): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_loopback", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-305" + return retVal + else: + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_vlan", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-306" + return retVal + else: + command = command + bgpNeighborArg2 + else: + retVal = "Error-303" + return retVal + + elif(bgpNeighborArg1 == "weight"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_weight", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-302" + return retVal + + else: + retVal = "Error-301" + return retVal + + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + return retVal +# EOM + + +def bgpNeighborAFConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpNeighborAFArg1 = module.params['bgpArg6'] + bgpNeighborAFArg2 = module.params['bgpArg7'] + bgpNeighborAFArg3 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + if(bgpNeighborAFArg1 == "allowas-in"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_occurances", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-325" + return retVal + + elif(bgpNeighborAFArg1 == "default-originate"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None and bgpNeighborAFArg2 == "route-map"): + command = command + bgpNeighborAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-324" + return retVal + + elif(bgpNeighborAFArg1 == "filter-list"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_filtername", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 == "in" or bgpNeighborAFArg3 == "out"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-323" + return retVal + else: + retVal = "Error-322" + return retVal + + elif(bgpNeighborAFArg1 == "maximum-prefix"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_maxprefix", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 is not None): + command = command + bgpNeighborAFArg3 + else: + command = command.strip() + else: + retVal = "Error-326" + return retVal + + elif(bgpNeighborAFArg1 == "next-hop-self"): + command = command + bgpNeighborAFArg1 + + elif(bgpNeighborAFArg1 == "prefix-list"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_prefixname", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 == "in" or bgpNeighborAFArg3 == "out"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-321" + return retVal + else: + retVal = "Error-320" + return retVal + + elif(bgpNeighborAFArg1 == "route-map"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-319" + return retVal + elif(bgpNeighborAFArg1 == "route-reflector-client"): + command = command + bgpNeighborAFArg1 + + elif(bgpNeighborAFArg1 == "send-community"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None and bgpNeighborAFArg2 == "extended"): + command = command + bgpNeighborAFArg2 + + elif(bgpNeighborAFArg1 == "soft-reconfiguration"): + command = command + bgpNeighborAFArg1 + " inbound" + + elif(bgpNeighborAFArg1 == "unsuppress-map"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-318" + return retVal + + else: + retVal = "Error-317" + return retVal + + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + return retVal +# EOM + + +def bgpAFConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpAFArg1 = module.params['bgpArg3'] + bgpAFArg2 = module.params['bgpArg4'] + bgpAFArg3 = module.params['bgpArg5'] + bgpAFArg4 = module.params['bgpArg6'] + bgpAFArg5 = module.params['bgpArg7'] + bgpAFArg6 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + if(bgpAFArg1 == "aggregate-address"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_aggregate_prefix", bgpAFArg2) + if(value == "ok"): + if(bgpAFArg2 is None): + command = command.strip() + elif(bgpAFArg2 == "as-set" or bgpAFArg2 == "summary-only"): + command = command + bgpAFArg2 + " " + if((bgpAFArg3 is not None) and (bgpAFArg2 == "as-set")): + command = command + "summary-only" + else: + retVal = "Error-297" + return retVal + else: + retVal = "Error-296" + return retVal + + elif(bgpAFArg1 == "client-to-client"): + command = command + bgpAFArg1 + " reflection " + + elif(bgpAFArg1 == "dampening"): + command = command + bgpAFArg1 + " " + if(bgpAFArg2 == "route-map"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-196" + return retVal + elif(bgpAFArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "reachability_half_life", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None): + value1 = cnos.checkSanityofVariable( + deviceType, "start_reuse_route_value", bgpAFArg3) + value2 = cnos.checkSanityofVariable( + deviceType, "start_suppress_route_value", bgpAFArg4) + value3 = cnos.checkSanityofVariable( + deviceType, "max_duration_to_suppress_route", + bgpAFArg5) + if(value1 == "ok" and value2 == "ok" and value3 == "ok"): + command = command + bgpAFArg3 + " " + bgpAFArg4 + \ + " " + bgpAFArg5 + " " + if(bgpAFArg6 is not None): + value = cnos.checkSanityofVariable( + deviceType, + "unreachability_halftime_for_penalty", + bgpAFArg6) + if(value == "ok"): + command = command + bgpAFArg6 + else: + retVal = "Error-295" + return retVal + else: + command = command.strip() + else: + retVal = "Error-294" + return retVal + + elif(bgpAFArg1 == "distance"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_external_AS", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_internal_AS", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_local_routes", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + else: + retVal = "Error-291" + return retVal + else: + retVal = "Error-292" + return retVal + else: + retVal = "Error-293" + return retVal + + elif(bgpAFArg1 == "maximum-paths"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "maxpath_option", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "maxpath_numbers", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-199" + return retVal + else: + retVal = "Error-290" + return retVal + + elif(bgpAFArg1 == "network"): + command = command + bgpAFArg1 + " " + if(bgpAFArg2 == "synchronization"): + command = command + bgpAFArg2 + else: + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_with_mask", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None and bgpAFArg3 == "backdoor"): + command = command + bgpAFArg3 + elif(bgpAFArg3 is not None and bgpAFArg3 == "route-map"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + if(bgpAFArg5 is not None and bgpAFArg5 == "backdoor"): + command = command + bgpAFArg5 + else: + retVal = "Error-298" + return retVal + else: + retVal = "Error-196" + return retVal + else: + command = command.strip() + else: + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_value", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None and bgpAFArg3 == "backdoor"): + command = command + bgpAFArg3 + elif(bgpAFArg3 is not None and bgpAFArg3 == "route-map"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + if(bgpAFArg5 is not None and + bgpAFArg5 == "backdoor"): + command = command + bgpAFArg5 + else: + retVal = "Error-298" + return retVal + else: + retVal = "Error-196" + return retVal + elif(bgpAFArg3 is not None and bgpAFArg3 == "mask"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_mask", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + else: + retVal = "Error-299" + return retVal + else: + command = command.strip() + else: + retVal = "Error-300" + return retVal + + elif(bgpAFArg1 == "nexthop"): + command = command + bgpAFArg1 + " trigger-delay critical " + value = cnos.checkSanityofVariable( + deviceType, "nexthop_crtitical_delay", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "nexthop_noncrtitical_delay", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + " " + else: + retVal = "Error-198" + return retVal + else: + retVal = "Error-197" + return retVal + + elif(bgpAFArg1 == "redistribute"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_redistribute_option", bgpAFArg2) + if(value == "ok"): + if(bgpAFArg2 is not None): + command = command + bgpAFArg2 + " " + "route-map " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-196" + return retVal + else: + retVal = "Error-195" + return retVal + + elif(bgpAFArg1 == "save" or bgpAFArg1 == "synchronization"): + command = command + bgpAFArg1 + + else: + retVal = "Error-194" + return retVal + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + return retVal +# EOM + + +def bgpConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpArg1 = module.params['bgpArg1'] + bgpArg2 = module.params['bgpArg2'] + bgpArg3 = module.params['bgpArg3'] + bgpArg4 = module.params['bgpArg4'] + bgpArg5 = module.params['bgpArg5'] + bgpArg6 = module.params['bgpArg6'] + bgpArg7 = module.params['bgpArg7'] + bgpArg8 = module.params['bgpArg8'] + asNum = module.params['asNum'] + deviceType = module.params['deviceType'] + # cnos.debugOutput(bgpArg1) + if(bgpArg1 == "address-family"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_address_family", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + " " + "unicast \n" + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpAFConfig(module, cmd, prompt, answer) + return retVal + else: + retVal = "Error-178" + return retVal + + elif(bgpArg1 == "bestpath"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + if(bgpArg2 == "always-compare-med"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "compare-confed-aspath"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "compare-routerid"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "dont-compare-originator-id"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "tie-break-on-age"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "as-path"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + " " + if(bgpArg3 == "ignore" or bgpArg3 == "multipath-relax"): + command = command + bgpArg3 + else: + retVal = "Error-179" + return retVal + elif(bgpArg2 == "med"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + " " + if(bgpArg3 == "confed" or + bgpArg3 == "missing-as-worst" or + bgpArg3 == "non-deterministic" or + bgpArg3 == "remove-recv-med" or + bgpArg3 == "remove-send-med"): + command = command + bgpArg3 + else: + retVal = "Error-180" + return retVal + else: + retVal = "Error-181" + return retVal + + elif(bgpArg1 == "bgp"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " as-local-count " + value = cnos.checkSanityofVariable( + deviceType, "bgp_bgp_local_count", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-182" + return retVal + + elif(bgpArg1 == "cluster-id"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "cluster_id_as_ip", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + value = cnos.checkSanityofVariable( + deviceType, "cluster_id_as_number", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-183" + return retVal + + elif(bgpArg1 == "confederation"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + if(bgpArg2 == "identifier"): + value = cnos.checkSanityofVariable( + deviceType, "confederation_identifier", bgpArg3) + if(value == "ok"): + command = command + bgpArg2 + " " + bgpArg3 + "\n" + else: + retVal = "Error-184" + return retVal + elif(bgpArg2 == "peers"): + value = cnos.checkSanityofVariable( + deviceType, "confederation_peers_as", bgpArg3) + if(value == "ok"): + command = command + bgpArg2 + " " + bgpArg3 + else: + retVal = "Error-185" + return retVal + else: + retVal = "Error-186" + return retVal + + elif(bgpArg1 == "enforce-first-as"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "fast-external-failover"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "graceful-restart"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " stalepath-time " + value = cnos.checkSanityofVariable( + deviceType, "stalepath_delay_value", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-187" + return retVal + + elif(bgpArg1 == "graceful-restart-helper"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "log-neighbor-changes"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "maxas-limit"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "maxas_limit_as", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-188" + return retVal + + elif(bgpArg1 == "neighbor"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "neighbor_ipaddress", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + if(bgpArg3 is not None): + command = command + " remote-as " + value = cnos.checkSanityofVariable( + deviceType, "neighbor_as", bgpArg3) + if(value == "ok"): + # debugOutput(command) + command = command + bgpArg3 + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpNeighborConfig(module, cmd, prompt, answer) + return retVal + else: + retVal = "Error-189" + return retVal + + elif(bgpArg1 == "router-id"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "router_id", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-190" + return retVal + + elif(bgpArg1 == "shutdown"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "synchronization"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "timers"): + # cnos.debugOutput(bgpArg3) + command = command + bgpArg1 + " bgp " + value = cnos.checkSanityofVariable( + deviceType, "bgp_keepalive_interval", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-191" + return retVal + if(bgpArg3 is not None): + value = cnos.checkSanityofVariable(deviceType, "bgp_holdtime", bgpArg3) + if(value == "ok"): + command = command + " " + bgpArg3 + else: + retVal = "Error-192" + return retVal + else: + retVal = "Error-192" + return retVal + + elif(bgpArg1 == "vrf"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " default" + else: + # debugOutput(bgpArg1) + retVal = "Error-192" + return retVal + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + # debugOutput(command) + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + bgpArg1=dict(required=True), + bgpArg2=dict(required=False), + bgpArg3=dict(required=False), + bgpArg4=dict(required=False), + bgpArg5=dict(required=False), + bgpArg6=dict(required=False), + bgpArg7=dict(required=False), + bgpArg8=dict(required=False), + asNum=dict(required=True),), + supports_check_mode=False) + + asNum = module.params['asNum'] + outputfile = module.params['outputfile'] + deviceType = module.params['deviceType'] + output = '' + command = 'router bgp ' + value = cnos.checkSanityofVariable(deviceType, "bgp_as_number", asNum) + if(value == "ok"): + # BGP command happens here. It creates if not present + command = command + asNum + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + bgpConfig(module, cmd, '(config)#', None) + else: + output = "Error-176" + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="BGP configurations accomplished") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_command.py new file mode 100644 index 00000000..d38c8ef7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_command.py @@ -0,0 +1,203 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute CNOS Commands on Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Run arbitrary commands on Lenovo CNOS devices +description: + - Sends arbitrary commands to an CNOS node and returns the results + read from the device. The C(cnos_command) module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +options: + commands: + description: + - List of commands to send to the remote device. + The resulting output from the command is returned. + If the I(wait_for) argument is provided, the module is not + returned until the condition is satisfied or the number of + retires is expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +--- +- name: Test contains operator + community.network.cnos_command: + commands: + - show version + - show system memory + wait_for: + - "result[0] contains 'Lenovo'" + - "result[1] contains 'MemFree'" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for single command + community.network.cnos_command: + commands: ['show version'] + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for multiple commands + community.network.cnos_command: + commands: + - show version + - show interface information + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + - "result.stdout | length == 2" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands, check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_command.py new file mode 100644 index 00000000..029a90b1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_command.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send Conditional CLI commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_conditional_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Execute a single command based on condition on devices + running Lenovo CNOS +description: + - This module allows you to modify the running configuration of a switch. It + provides a way to execute a single CNOS command on a network device by + evaluating the current running configuration and executing the command only + if the specific settings have not been already configured. + The CNOS command is passed as an argument of the method. + This module functions the same as the cnos_command module. + The only exception is that following inventory variable can be specified + ["condition = "] + When this inventory variable is specified as the variable of a task, the + command is executed for the network element that matches the flag string. + Usually, commands are executed across a group of network devices. When + there is a requirement to skip the execution of the command on one or + more devices, it is recommended to use this module. This module uses SSH to + manage network device configuration. +extends_documentation_fragment: +- community.network.cnos + +options: + clicommand: + description: + - This specifies the CLI command as an attribute to this method. + The command is passed using double quotes. The variables can be + placed directly on to the CLI commands or can be invoked + from the vars directory. + required: true + default: Null + condition: + description: + - If you specify condition=false in the inventory file against any + device, the command execution is skipped for that device. + required: true + default: Null + flag: + description: + - If a task needs to be executed, you have to set the flag the same + as it is specified in the inventory for that device. + required: true + default: Null + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module + cnos_conditional_command. These are written in the main.yml file of the tasks + directory. +--- +- name: Applying CLI template on VLAG Tier1 Leaf Switch1 + community.network.cnos_conditional_command: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_conditional_command_ + {{ inventory_hostname }}_output.txt" + condition: "{{ hostvars[inventory_hostname]['condition']}}" + flag: leaf_switch2 + command: "spanning-tree mode enable" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Command Applied" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + clicommand=dict(required=True), + outputfile=dict(required=True), + condition=dict(required=True), + flag=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, + no_log=True), ), supports_check_mode=False) + + condition = module.params['condition'] + flag = module.params['flag'] + cliCommand = module.params['clicommand'] + outputfile = module.params['outputfile'] + output = '' + if (condition is None or condition != flag): + module.exit_json(changed=True, msg="Command Skipped for this switch") + return '' + # Send the CLi command + cmd = [{'command': cliCommand, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="CLI Command executed and results saved in file ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_template.py new file mode 100644 index 00000000..f9d675ce --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_conditional_template.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send conditional template to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_conditional_template +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage switch configuration using templates based on + condition on devices running Lenovo CNOS +description: + - This module allows you to work with the running configuration of a + switch. It provides a way to execute a set of CNOS commands on a switch by + evaluating the current running configuration and executing the commands + only if the specific settings have not been already configured. + The configuration source can be a set of commands or a template written in + the Jinja2 templating language. This module functions the same as the + cnos_template module. The only exception is that the following inventory + variable can be specified. + ["condition = "] + When this inventory variable is specified as the variable of a task, the + template is executed for the network element that matches the flag string. + Usually, templates are used when commands are the same across a group of + network devices. When there is a requirement to skip the execution of the + template on one or more devices, it is recommended to use this module. + This module uses SSH to manage network device configuration. +extends_documentation_fragment: +- community.network.cnos + +options: + commandfile: + description: + - This specifies the path to the CNOS command file which needs to + be applied. This usually comes from the commands folder. Generally + this file is the output of the variables applied on a template + file. So this command is preceded by a template module. The + command file must contain the Ansible keyword + {{ inventory_hostname }} and the condition flag in its filename to + ensure that the command file is unique for each switch and + condition. If this is omitted, the command file will be + overwritten during iteration. For example, + commandfile=./commands/clos_leaf_bgp_ + {{ inventory_hostname }}_LP21_commands.txt + required: true + default: Null + condition: + description: + - If you specify condition= in the inventory file + against any device, the template execution is done for that device + in case it matches the flag setting for that task. + required: true + default: Null + flag: + description: + - If a task needs to be executed, you have to set the flag the same + as it is specified in the inventory for that device. + required: true + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module + cnos_conditional_template. These are written in the main.yml file of the + tasks directory. +--- +- name: Applying CLI template on VLAG Tier1 Leaf Switch1 + community.network.cnos_conditional_template: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/vlag_1tier_leaf_switch1_ + {{ inventory_hostname }}_output.txt" + condition: "{{ hostvars[inventory_hostname]['condition']}}" + flag: "leaf_switch1" + commandfile: "./commands/vlag_1tier_leaf_switch1_ + {{ inventory_hostname }}_commands.txt" + stp_mode1: "disable" + port_range1: "17,18,29,30" + portchannel_interface_number1: 1001 + portchannel_mode1: active + slot_chassis_number1: 1/48 + switchport_mode1: trunk +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Template Applied." +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + commandfile=dict(required=True), + outputfile=dict(required=True), + condition=dict(required=True), + flag=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + condition = module.params['condition'] + flag = module.params['flag'] + commandfile = module.params['commandfile'] + outputfile = module.params['outputfile'] + + output = '' + if (condition is None or condition != flag): + module.exit_json(changed=True, msg="Template Skipped for this switch") + return " " + # Send commands one by one + f = open(commandfile, "r") + cmd = [] + for line in f: + # Omit the comment lines in template file + if not line.startswith("#"): + # cnos.debugOutput(line) + command = line.strip() + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write output to file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Template Applied") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_config.py new file mode 100644 index 00000000..e32deb7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_config.py @@ -0,0 +1,302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to configure Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_config +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Lenovo CNOS configuration sections +description: + - Lenovo CNOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with CNOS configuration sections in + a deterministic way. +notes: + - Tested against CNOS 10.9.1 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is + mutually exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block', 'config'] + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by cnos_config' + admin: + description: + - Enters into administration configuration mode for making config + changes to the device. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +Tasks: The following are examples of using the module cnos_config. +--- +- name: Configure top level configuration + community.network.cnos_config: + "lines: hostname {{ inventory_hostname }}" + +- name: Configure interface settings + community.network.cnos_config: + lines: + - enable + - ip ospf enable + parents: interface ip 13 + +- name: Load a config from disk and replace the current config + community.network.cnos_config: + src: config.cfg + backup: yes + +- name: Configurable backup path + community.network.cnos_config: + src: config.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/cnos01.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, get_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +DEFAULT_COMMIT_COMMENT = 'configured by cnos_config' + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + replace_config = replace == 'config' + path = module.params['parents'] + comment = module.params['comment'] + admin = module.params['admin'] + check_mode = module.check_mode + + candidate = get_candidate(module) + + if match != 'none' and replace != 'config': + contents = get_running_config(module) + configobj = NetworkConfig(contents=contents, indent=1) + commands = candidate.difference(configobj, path=path, match=match, + replace=replace) + else: + commands = candidate.items + + if commands: + commands = dumps(commands, 'commands').split('\n') + + if any((module.params['lines'], module.params['src'])): + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + + diff = load_config(module, commands) + if diff: + result['diff'] = dict(prepared=diff) + result['changed'] = True + + +def main(): + """main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', + 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block', 'config']), + + config=dict(), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + comment=dict(default=DEFAULT_COMMIT_COMMENT), + admin=dict(type='bool', default=False) + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('replace', 'config', ['src'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_factory.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_factory.py new file mode 100644 index 00000000..606bd8fb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_factory.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Reset to factory settings of Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_factory +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Reset the switch startup configuration to default (factory) + on devices running Lenovo CNOS. +description: + - This module allows you to reset a switch's startup configuration. The + method provides a way to reset the startup configuration to its factory + settings. This is helpful when you want to move the switch to another + topology as a new network device. This module uses SSH to manage network + device configuration. The result of the operation can be viewed in results + directory. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_reload. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Reset to factory + community.network.cnos_factory: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_factory_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Switch Startup Config is Reset to factory settings" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'write erase' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': '[n]', 'answer': 'y'}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Switch Startup Config is Reset to Factory settings") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_facts.py new file mode 100644 index 00000000..a036b63b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_facts.py @@ -0,0 +1,535 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2019 Red Hat Inc. +# Copyright (C) 2019 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Module to Collect facts from Lenovo Switches running Lenovo CNOS commands +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_facts +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect facts from remote devices running Lenovo CNOS +description: + - Collects a base set of device facts from a remote Lenovo device + running on CNOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against CNOS 10.8.1 +options: + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module cnos_facts. +--- +- name: Test cnos Facts + community.network.cnos_facts: + +--- +# Collect all facts from the device +- community.network.cnos_facts: + gather_subset: all + +# Collect only the config and default facts +- community.network.cnos_facts: + gather_subset: + - config + +# Do not collect hardware facts +- community.network.cnos_facts: + gather_subset: + - "!hardware" +''' +RETURN = ''' + ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +# default + ansible_net_model: + description: The model name returned from the Lenovo CNOS device + returned: always + type: str + ansible_net_serialnum: + description: The serial number of the Lenovo CNOS device + returned: always + type: str + ansible_net_version: + description: The CNOS operating system version running on the remote device + returned: always + type: str + ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + ansible_net_image: + description: Indicates the active image for the device + returned: always + type: str +# hardware + ansible_net_memfree_mb: + description: The available free memory on the remote device in MB + returned: when hardware is configured + type: int +# config + ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces + ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_interfaces: + description: A hash of all interfaces running on the system. + This gives information on description, mac address, mtu, speed, + duplex and operstatus + returned: when interfaces is configured + type: dict + ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +''' + +import re + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self.PERSISTENT_COMMAND_TIMEOUT = 60 + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, + check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show sys-info', 'show running-config'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + data_run = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + if data_run: + self.facts['hostname'] = self.parse_hostname(data_run) + + def parse_version(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Software Revision (.*?)', + line, re.M | re.I) + if match: + vers = line.split(':') + ver = vers[1].strip() + return ver + return "NA" + + def parse_hostname(self, data_run): + for line in data_run.split('\n'): + line = line.strip() + match = re.match(r'hostname (.*?)', line, re.M | re.I) + if match: + hosts = line.split() + hostname = hosts[1].strip('\"') + return hostname + return "NA" + + def parse_model(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Model (.*?)', line, re.M | re.I) + if match: + mdls = line.split(':') + mdl = mdls[1].strip() + return mdl + return "NA" + + def parse_image(self, data): + match = re.search(r'(.*) image(.*)', data, re.M | re.I) + if match: + return "Image1" + else: + return "Image2" + + def parse_serialnum(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Serial Number (.*?)', line, re.M | re.I) + if match: + serNums = line.split(':') + ser = serNums[1].strip() + return ser + return "NA" + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show running-config' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.run(['show process memory']) + data = to_text(data, errors='surrogate_or_strict').strip() + data = data.replace(r"\n", "\n") + if data: + for line in data.split('\n'): + line = line.strip() + match = re.match(r'Mem: (.*?)', line, re.M | re.I) + if match: + memline = line.split(':') + mems = memline[1].strip().split() + self.facts['memtotal_mb'] = int(mems[0]) / 1024 + self.facts['memused_mb'] = int(mems[1]) / 1024 + self.facts['memfree_mb'] = int(mems[2]) / 1024 + self.facts['memshared_mb'] = int(mems[3]) / 1024 + self.facts['memavailable_mb'] = int(mems[5]) / 1024 + + def parse_memtotal(self, data): + match = re.search(r'^MemTotal:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + def parse_memfree(self, data): + match = re.search(r'^MemFree:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interface brief'] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data1 = self.run(['show interface status']) + data1 = to_text(data1, errors='surrogate_or_strict').strip() + data1 = data1.replace(r"\n", "\n") + data2 = self.run(['show interface mac-address']) + data2 = to_text(data2, errors='surrogate_or_strict').strip() + data2 = data2.replace(r"\n", "\n") + lines1 = None + lines2 = None + if data1: + lines1 = self.parse_interfaces(data1) + if data2: + lines2 = self.parse_interfaces(data2) + if lines1 is not None and lines2 is not None: + self.facts['interfaces'] = self.populate_interfaces(lines1, lines2) + data3 = self.run(['show lldp neighbors']) + data3 = to_text(data3, errors='surrogate_or_strict').strip() + data3 = data3.replace(r"\n", "\n") + if data3: + lines3 = self.parse_neighbors(data3) + if lines3 is not None: + self.facts['neighbors'] = self.populate_neighbors(lines3) + + data4 = self.run(['show ip interface brief vrf all']) + data5 = self.run(['show ipv6 interface brief vrf all']) + data4 = to_text(data4, errors='surrogate_or_strict').strip() + data4 = data4.replace(r"\n", "\n") + data5 = to_text(data5, errors='surrogate_or_strict').strip() + data5 = data5.replace(r"\n", "\n") + lines4 = None + lines5 = None + if data4: + lines4 = self.parse_ipaddresses(data4) + ipv4_interfaces = self.set_ip_interfaces(lines4) + self.facts['all_ipv4_addresses'] = ipv4_interfaces + if data5: + lines5 = self.parse_ipaddresses(data5) + ipv6_interfaces = self.set_ipv6_interfaces(lines5) + self.facts['all_ipv6_addresses'] = ipv6_interfaces + + def parse_ipaddresses(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^(Ethernet+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(po+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(mgmt+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(loopback+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def populate_interfaces(self, lines1, lines2): + interfaces = dict() + for line1, line2 in zip(lines1, lines2): + line = line1 + " " + line2 + intfSplit = line.split() + innerData = dict() + innerData['description'] = intfSplit[1].strip() + innerData['macaddress'] = intfSplit[8].strip() + innerData['type'] = intfSplit[6].strip() + innerData['speed'] = intfSplit[5].strip() + innerData['duplex'] = intfSplit[4].strip() + innerData['operstatus'] = intfSplit[2].strip() + interfaces[intfSplit[0].strip()] = innerData + return interfaces + + def parse_interfaces(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^(Ethernet+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(po+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(mgmt+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def set_ip_interfaces(self, line4): + ipv4_addresses = list() + for line in line4: + ipv4Split = line.split() + if 'Ethernet' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'mgmt' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'po' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'loopback' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + return ipv4_addresses + + def set_ipv6_interfaces(self, line4): + ipv6_addresses = list() + for line in line4: + ipv6Split = line.split() + if 'Ethernet' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'mgmt' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'po' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'loopback' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + return ipv6_addresses + + def populate_neighbors(self, lines3): + neighbors = dict() + device_name = '' + for line in lines3: + neighborSplit = line.split() + innerData = dict() + count = len(neighborSplit) + if count == 5: + local_interface = neighborSplit[1].strip() + innerData['Device Name'] = neighborSplit[0].strip() + innerData['Hold Time'] = neighborSplit[2].strip() + innerData['Capability'] = neighborSplit[3].strip() + innerData['Remote Port'] = neighborSplit[4].strip() + neighbors[local_interface] = innerData + elif count == 4: + local_interface = neighborSplit[0].strip() + innerData['Hold Time'] = neighborSplit[1].strip() + innerData['Capability'] = neighborSplit[2].strip() + innerData['Remote Port'] = neighborSplit[3].strip() + neighbors[local_interface] = innerData + return neighbors + + def parse_neighbors(self, neighbors): + parsed = list() + for line in neighbors.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + if 'Ethernet' in line: + parsed.append(line) + if 'mgmt' in line: + parsed.append(line) + if 'po' in line: + parsed.append(line) + if 'loopback' in line: + parsed.append(line) + return parsed + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +PERSISTENT_COMMAND_TIMEOUT = 60 + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + check_args(module, warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_image.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_image.py new file mode 100644 index 00000000..ee9c2cda --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_image.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to download new image to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_image +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Perform firmware upgrade/download from a remote server on + devices running Lenovo CNOS +description: + - This module allows you to work with switch firmware images. It provides a + way to download a firmware image to a network device from a remote server + using FTP, SFTP, TFTP, or SCP. The first step is to create a directory + from where the remote server can be reached. The next step is to provide + the full file path of the image's location. Authentication details + required by the remote server must be provided as well. By default, this + method makes the newly downloaded firmware image the active image, which + will be used by the switch during the next restart. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server from where to download the + firmware image. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is not specified + there is no default value to be used. + required: true + choices: [SFTP, SCP, FTP, TFTP] + serverip: + description: + - This specifies the IP Address of the remote server from where the + software image will be downloaded. + required: true + imgpath: + description: + - This specifies the full file path of the image located on the + remote server. In case the relative path is used as the variable + value, the root folder for the user of the server needs to be + specified. + required: true + imgtype: + description: + - This specifies the firmware image type to be downloaded + required: true + choices: [all, boot, os, onie] + serverusername: + description: + - Specify the username for the server relating to the protocol used + required: true + serverpassword: + description: + - Specify the password for the server relating to the protocol used +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_image. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Image transfer + community.network.cnos_image: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt" + protocol: "sftp" + serverip: "10.241.106.118" + imgpath: "/root/cnos_images/G8272-10.1.0.112.img" + imgtype: "os" + serverusername: "root" + serverpassword: "root123" + +- name: Test Image tftp + community.network.cnos_image: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt" + protocol: "tftp" + serverip: "10.241.106.118" + imgpath: "/anil/G8272-10.2.0.34.img" + imgtype: "os" + serverusername: "root" + serverpassword: "root123" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Image file transferred to device" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def doImageDownload(module, prompt, answer): + protocol = module.params['protocol'].lower() + server = module.params['serverip'] + imgPath = module.params['imgpath'] + imgType = module.params['imgtype'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + retVal = '' + command = "copy " + protocol + " " + protocol + "://" + username + "@" + command = command + server + "/" + imgPath + " system-image " + command = command + imgType + " vrf management" + cmd = [] + if(protocol == "scp"): + prompt = ['timeout', 'Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['240', 'y', password, 'y'] + scp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(scp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + prompt = ['Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['y', password, 'y'] + sftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(sftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + prompt = ['Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['y', password, 'y'] + ftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(ftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + protocol + " " + protocol + "://" + server + command = command + "/" + imgPath + " system-image " + imgType + command = command + " vrf management" + prompt = ['Confirm download operation', + 'Do you want to change that to the standby image'] + answer = ['y', 'y'] + tftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(tftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + imgpath=dict(required=True), + imgtype=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + + # Invoke method for image transfer from server + if(protocol == "tftp" or protocol == "ftp" or protocol == "sftp" or + protocol == "scp"): + transfer_status = doImageDownload(module, None, None) + else: + transfer_status = "Invalid Protocol option" + + output = output + "\n Image Transfer status \n" + transfer_status + + # Save it into the file + path = outputfile.rsplit('/', 1) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Image file transferred to device") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_interface.py new file mode 100644 index 00000000..57e07dde --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_interface.py @@ -0,0 +1,550 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Interfaces with Lenovo Switches +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_interface +author: "Anil Kumar Muraleedharan(@amuraleedhar)" +short_description: Manage Interface on Lenovo CNOS network devices +description: + - This module provides declarative management of Interfaces + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the Interface. + required: true + description: + description: + - Description of Interface. + enabled: + description: + - Interface link status. + type: bool + default: True + speed: + description: + - Interface link speed. + mtu: + description: + - Maximum size of transmit packet. + duplex: + description: + - Interface link status + default: auto + choices: ['full', 'half', 'auto'] + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules, + ../network/user_guide/network_working_with_command_output.html) + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules, + ../network/user_guide/network_working_with_command_output.html) + neighbors: + description: + - Check operational state of given interface C(name) for LLDP neighbor. + - The following suboptions are available. + suboptions: + host: + description: + - "LLDP neighbor host for given interface C(name)." + port: + description: + - "LLDP neighbor port to which interface C(name) is connected." + aggregate: + description: List of Interfaces definitions. + delay: + description: + - Time in seconds to wait before checking for the operational state on + remote device. This wait is applicable for operational state argument + which are I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate) + default: 20 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + choices: ['present', 'absent', 'up', 'down'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Configure interface + community.network.cnos_interface: + name: Ethernet1/33 + description: test-interface + speed: 100 + duplex: half + mtu: 999 + +- name: Remove interface + community.network.cnos_interface: + name: loopback3 + state: absent + +- name: Make interface up + community.network.cnos_interface: + name: Ethernet1/33 + enabled: True + +- name: Make interface down + community.network.cnos_interface: + name: Ethernet1/33 + enabled: False + +- name: Check intent arguments + community.network.cnos_interface: + name: Ethernet1/33 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.cnos_interface: + name: Ethernet1/33 + neighbors: + - port: eth0 + host: netdev + +- name: Config + intent + community.network.cnos_interface: + name: Ethernet1/33 + enabled: False + state: down + +- name: Add interface using aggregate + community.network.cnos_interface: + aggregate: + - { name: Ethernet1/33, mtu: 256, description: test-interface-1 } + - { name: Ethernet1/44, mtu: 516, description: test-interface-2 } + duplex: full + speed: 100 + state: present + +- name: Delete interface using aggregate + community.network.cnos_interface: + aggregate: + - name: loopback3 + - name: loopback6 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/33 + - description test-interface + - duplex half + - mtu 512 +""" +import re + +from copy import deepcopy +from time import sleep + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec + + +def validate_mtu(value, module): + if value and not 64 <= int(value) <= 9216: + module.fail_json(msg='mtu must be between 64 and 9216') + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_shutdown(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^shutdown', cfg, re.M) + if match: + return True + else: + return False + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'description': parse_config_argument(configobj, item, 'description'), + 'speed': parse_config_argument(configobj, item, 'speed'), + 'duplex': parse_config_argument(configobj, item, 'duplex'), + 'mtu': parse_config_argument(configobj, item, 'mtu'), + 'disable': True if parse_shutdown(configobj, item) else False, + 'state': 'present' + } + instances.append(obj) + return instances + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'description': module.params['description'], + 'speed': module.params['speed'], + 'mtu': module.params['mtu'], + 'duplex': module.params['duplex'], + 'state': module.params['state'], + 'delay': module.params['delay'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + + validate_param_values(module, params) + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + + args = ('speed', 'description', 'duplex', 'mtu') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + if state == 'absent' and obj_in_have: + commands.append('no ' + interface) + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate != running: + if candidate: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'shutdown', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'no shutdown', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('no shutdown') + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors_lldp = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interface %s brief' % w['name'] + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + if want_state in ('up', 'down'): + state_data = out.strip().lower().split(w['name']) + have_state = None + have_state = state_data[1].split()[3] + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + command = 'show interface %s' % w['name'] + rc, out, err = exec_command(module, command) + have_tx_rate = None + have_rx_rate = None + rates = out.splitlines() + for s in rates: + s = s.strip() + if 'output rate' in s and 'input rate' in s: + sub = s.split() + if want_tx_rate: + have_tx_rate = sub[8] + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + if want_rx_rate: + have_rx_rate = sub[2] + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + if want_neighbors: + have_host = [] + have_port = [] + + # 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_lldp: + lines = have_neighbors_lldp.strip().split('Local Port ID: ') + for line in lines: + field = line.split('\n') + if field[0].strip() == w['name']: + for item in field: + if item.startswith('System Name:'): + have_host.append(item.split(':')[1].strip()) + if item.startswith('Port Description:'): + have_port.append(item.split(':')[1].strip()) + + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + neighbors_spec = dict( + host=dict(), + port=dict() + ) + + element_spec = dict( + name=dict(), + description=dict(), + speed=dict(), + mtu=dict(), + duplex=dict(default='auto', choices=['full', 'half', 'auto']), + enabled=dict(default=True, type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=20, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l2_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l2_interface.py new file mode 100644 index 00000000..9693685f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l2_interface.py @@ -0,0 +1,593 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send banner commands to Lenovo Switches +# Two types of banners are supported login and motd +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_l2_interface +short_description: Manage Layer-2 interface on Lenovo CNOS devices. +description: + - This module provides declarative management of Layer-2 interfaces on + Lenovo CNOS devices. +author: + - Anil Kumar Muraleedharan (@amuraleedhar) +options: + name: + description: + - Full name of the interface excluding any logical + unit number, i.e. Ethernet1/3. + required: true + aliases: ['interface'] + mode: + description: + - Mode in which interface needs to be configured. + default: access + choices: ['access', 'trunk'] + access_vlan: + description: + - Configure given VLAN in access port. + If C(mode=access), used as the access VLAN ID. + trunk_vlans: + description: + - List of VLANs to be configured in trunk port. + If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk. + native_vlan: + description: + - Native VLAN to be configured in trunk port. + If C(mode=trunk), used as the trunk native VLAN ID. + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. + If C(mode=trunk), these are the only VLANs that will be + configured on the trunk, i.e. "2-10,15". + aggregate: + description: + - List of Layer-2 interface definitions. + state: + description: + - Manage the state of the Layer-2 Interface configuration. + default: present + choices: ['present','absent', 'unconfigured'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Ensure Ethernet1/5 is in its default l2 interface state + community.network.cnos_l2_interface: + name: Ethernet1/5 + state: unconfigured + +- name: Ensure Ethernet1/5 is configured for access vlan 20 + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: access + access_vlan: 20 + +- name: Ensure Ethernet1/5 only has vlans 5-10 as trunk vlans + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 5-10 + +- name: Ensure Ethernet1/5 is a trunk port and ensure 2-50 are being tagged + (doesn't mean others aren't also being tagged) + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 2-50 + +- name: Ensure these VLANs are not being tagged on the trunk + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + trunk_vlans: 51-4094 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/5 + - switchport access vlan 20 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def interface_is_portchannel(name, module): + if get_interface_type(name) == 'ethernet': + config = run_commands(module, ['show run interface {0}'.format(name)])[0] + if any(c in config for c in ['channel group', 'channel-group']): + return True + return False + + +def get_switchport(name, module): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + mode = re.search(r'Switchport mode : (?:.* )?(\w+)$', config, re.M) + access = re.search(r'Configured Vlans : (\d+)', config) + native = re.search(r'Default/Native Vlan : (\d+)', config) + trunk = re.search(r'Enabled Vlans : (.+)$', config, re.M) + if mode: + mode = mode.group(1) + if access: + access = access.group(1) + if native: + native = native.group(1) + if trunk: + trunk = trunk.group(1) + if trunk == 'ALL': + trunk = '1-4094' + + switchport_config = { + "interface": name, + "mode": mode, + "access_vlan": access, + "native_vlan": native, + "trunk_vlans": trunk, + } + + return switchport_config + + +def remove_switchport_config_commands(name, existing, proposed, module): + mode = proposed.get('mode') + commands = [] + command = None + + if mode == 'access': + av_check = existing.get('access_vlan') == proposed.get('access_vlan') + if av_check: + command = 'no switchport access vlan' + commands.append(command) + + elif mode == 'trunk': + # 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}' + command = command.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'): + command = 'no switchport trunk native vlan' + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def get_switchport_config_commands(name, existing, proposed, module): + """Gets commands required to config a given switchport interface + """ + + proposed_mode = proposed.get('mode') + existing_mode = existing.get('mode') + commands = [] + command = None + + if proposed_mode != existing_mode: + if proposed_mode == 'trunk': + command = 'switchport mode trunk' + elif proposed_mode == 'access': + command = 'switchport mode access' + + if command: + commands.append(command) + + if proposed_mode == 'access': + av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan')) + if not av_check: + command = 'switchport access vlan {0}'.format(proposed.get('access_vlan')) + commands.append(command) + + elif proposed_mode == 'trunk': + tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list') + + if not tv_check: + if proposed.get('allowed'): + command = 'switchport trunk allowed vlan {0}' + command = command.format(proposed.get('trunk_allowed_vlans')) + commands.append(command) + + else: + existing_vlans = existing.get('trunk_vlans_list') + proposed_vlans = proposed.get('trunk_vlans_list') + vlans_to_add = set(proposed_vlans).difference(existing_vlans) + if vlans_to_add: + command = 'switchport trunk allowed vlan add {0}' + command = command.format(proposed.get('trunk_vlans')) + commands.append(command) + + native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan')) + if not native_check and proposed.get('native_vlan'): + command = 'switchport trunk native vlan {0}' + command = command.format(proposed.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def is_switchport_default(existing): + """Determines if switchport has a default config based on mode + Args: + existing (dict): existing switchport configuration from Ansible mod + Returns: + boolean: True if switchport has OOB Layer 2 config, i.e. + vlan 1 and trunk all and mode is access + """ + + c1 = str(existing['access_vlan']) == '1' + c2 = str(existing['native_vlan']) == '1' + c3 = existing['trunk_vlans'] == '1-4094' + c4 = existing['mode'] == 'access' + + default = c1 and c2 and c3 and c4 + + return default + + +def default_switchport_config(name): + commands = [] + commands.append('interface ' + name) + commands.append('switchport mode access') + commands.append('switch access vlan 1') + commands.append('switchport trunk native vlan 1') + commands.append('switchport trunk allowed vlan all') + return commands + + +def vlan_range_to_list(vlans): + result = [] + if vlans: + for part in vlans.split(','): + if part.lower() == 'none': + break + if part: + if '-' in part: + start, stop = (int(i) for i in part.split('-')) + result.extend(range(start, stop + 1)) + else: + result.append(int(part)) + return sorted(result) + + +def get_list_of_vlans(module): + config = run_commands(module, ['show vlan'])[0] + vlans = set() + + lines = config.strip().splitlines() + for line in lines: + line_parts = line.split() + if line_parts: + try: + int(line_parts[0]) + except ValueError: + continue + vlans.add(line_parts[0]) + + return list(vlans) + + +def flatten_list(commands): + flat_list = [] + for command in commands: + if isinstance(command, list): + flat_list.extend(command) + else: + flat_list.append(command) + return flat_list + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'mode': module.params['mode'], + 'access_vlan': module.params['access_vlan'], + 'native_vlan': module.params['native_vlan'], + 'trunk_vlans': module.params['trunk_vlans'], + 'trunk_allowed_vlans': module.params['trunk_allowed_vlans'], + 'state': module.params['state'] + }) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(type='str', aliases=['interface']), + mode=dict(choices=['access', 'trunk'], default='access'), + access_vlan=dict(type='str'), + native_vlan=dict(type='str'), + trunk_vlans=dict(type='str'), + trunk_allowed_vlans=dict(type='str'), + state=dict(choices=['absent', 'present', 'unconfigured'], + default='present') + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=[['access_vlan', 'trunk_vlans'], + ['access_vlan', 'native_vlan'], + ['access_vlan', 'trunk_allowed_vlans']], + supports_check_mode=True) + + warnings = list() + commands = [] + result = {'changed': False, 'warnings': warnings} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + mode = w['mode'] + access_vlan = w['access_vlan'] + state = w['state'] + trunk_vlans = w['trunk_vlans'] + native_vlan = w['native_vlan'] + trunk_allowed_vlans = w['trunk_allowed_vlans'] + + args = dict(name=name, mode=mode, access_vlan=access_vlan, + native_vlan=native_vlan, trunk_vlans=trunk_vlans, + trunk_allowed_vlans=trunk_allowed_vlans) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + name = name.lower() + + if mode == 'access' and state == 'present' and not access_vlan: + msg = 'access_vlan param required for mode=access && state=present' + module.fail_json(msg=msg) + + if mode == 'trunk' and access_vlan: + msg = 'access_vlan param not supported when using mode=trunk' + module.fail_json(msg=msg) + + if not is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L2' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + + if interface_is_portchannel(name, module): + module.fail_json(msg='Cannot change L2 config on physical ' + '\nport because it is in a portchannel. ' + '\nYou should update the portchannel config.') + + # existing will never be null for Eth intfs as there is always a default + existing = get_switchport(name, module) + + # Safeguard check + # If there isn't an existing, something is wrong per previous comment + if not existing: + msg = 'Make sure you are using the FULL interface name' + module.fail_json(msg=msg) + + if trunk_vlans or trunk_allowed_vlans: + if trunk_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_vlans) + elif trunk_allowed_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans) + proposed['allowed'] = True + + existing_trunks_list = vlan_range_to_list((existing['trunk_vlans'])) + + existing['trunk_vlans_list'] = existing_trunks_list + proposed['trunk_vlans_list'] = trunk_vlans_list + + current_vlans = get_list_of_vlans(module) + + if state == 'present': + if access_vlan and access_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=access_vlan) + elif native_vlan and native_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN on' + ' an interface that\ndoes not exist on the ' + ' switch yet!', vlan=native_vlan) + else: + command = get_switchport_config_commands(name, existing, + proposed, module) + commands.append(command) + elif state == 'unconfigured': + is_default = is_switchport_default(existing) + if not is_default: + command = default_switchport_config(name) + commands.append(command) + elif state == 'absent': + command = remove_switchport_config_commands(name, existing, + proposed, module) + commands.append(command) + + if trunk_vlans or trunk_allowed_vlans: + existing.pop('trunk_vlans_list') + proposed.pop('trunk_vlans_list') + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + result['changed'] = True + load_config(module, cmds) + if 'configure' in cmds: + cmds.pop(0) + + result['commands'] = cmds + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l3_interface.py new file mode 100644 index 00000000..76296ad7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_l3_interface.py @@ -0,0 +1,457 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2019, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_l3_interface +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Layer-3 interfaces on Lenovo CNOS network devices. +description: + - This module provides declarative management of Layer-3 interfaces + on CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the Layer-3 interface to be configured eg. Ethernet1/2 + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) + option. The address format is /, the mask is number + in range 0-32 eg. 10.241.107.1/24 + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) + option. The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64 + aggregate: + description: + - List of Layer-3 interfaces definitions. Each of the entry in aggregate + list should define name of interface C(name) and a optional C(ipv4) or + C(ipv6) address. + state: + description: + - State of the Layer-3 interface configuration. It indicates if the + configuration should be present or absent on remote device. + default: present + choices: ['present', 'absent'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Remove Ethernet1/33 IPv4 and IPv6 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + state: absent + +- name: Set Ethernet1/33 IPv4 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv4: 10.241.107.1/24 + +- name: Set Ethernet1/33 IPv6 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set Ethernet1/33 in dhcp + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv4: dhcp + ipv6: dhcp + +- name: Set interface Vlan1 (SVI) IPv4 address + community.network.cnos_l3_interface: + name: Vlan1 + ipv4: 192.168.0.5/24 + +- name: Set IP addresses on aggregate + community.network.cnos_l3_interface: + aggregate: + - { name: Ethernet1/33, ipv4: 10.241.107.1/24 } + - { name: Ethernet1/44, ipv4: 10.240.106.1/24, + ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.cnos_l3_interface: + aggregate: + - { name: Ethernet1/33, ipv4: 10.241.107.1/24 } + - { name: Ethernet1/44, ipv4: 10.240.106.1/24, + ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/33 + - ip address 10.241.107.1 255.255.255.0 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" +import re + +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json( + msg='address format is /,got invalid format %s' % value) + if not is_masklen(address[1]): + module.fail_json( + msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json( + msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json( + msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + + values = [] + matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M) + for match in matches: + match_str = match.group(1).strip() + if arg == 'ipv6 address': + values.append(match_str) + else: + values = match_str + break + + return values or None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'].lower() == name.lower(): + return o + + return None + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format( + address[0], to_netmask(address[1])) + commands.append('no ip address %s' % ipv4) + else: + commands.append('no ip address') + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address %s' % ipv6) + else: + commands.append('no ipv6 address') + if 'dhcp' in obj_in_have['ipv6']: + commands.append('no ipv6 address dhcp') + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format( + address[0], to_netmask(address[1])) + commands.append('ip address %s' % ipv4) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]: + commands.append('ipv6 address %s' % ipv6) + if commands[-1] == interface: + commands.pop(-1) + + return commands + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + # eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24 + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1]))) + + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'] + }) + + validate_param_values(module, obj) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + name = name.lower() + if is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L3' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + resp = load_config(module, commands) + if resp is not None: + warnings.extend((out for out in resp if out)) + + result['changed'] = True + + if warnings: + result['warnings'] = warnings + if 'overlaps with address configured on' in warnings[0]: + result['failed'] = True + result['msg'] = warnings[0] + if 'Cannot set overlapping address' in warnings[0]: + result['failed'] = True + result['msg'] = warnings[0] + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_linkagg.py new file mode 100644 index 00000000..0e1c1513 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_linkagg.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_linkagg +author: "Anil Kumar Muraleedharan (@auraleedhar)" +short_description: Manage link aggregation groups on Lenovo CNOS devices +description: + - This module provides declarative management of link aggregation groups + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255. + mode: + description: + - Mode of the link aggregation group. + choices: ['active', 'on', 'passive'] + members: + description: + - List of members of the link aggregation group. + aggregate: + description: List of link aggregation definitions. + state: + description: + - State of the link aggregation group. + default: present + choices: ['present', 'absent'] + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: no + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Create link aggregation group + community.network.cnos_linkagg: + group: 10 + state: present + +- name: Delete link aggregation group + community.network.cnos_linkagg: + group: 10 + state: absent + +- name: Set link aggregation group to members + community.network.cnos_linkagg: + group: 200 + mode: active + members: + - Ethernet1/33 + - Ethernet1/44 + +- name: Remove link aggregation group from GigabitEthernet0/0 + community.network.cnos_linkagg: + group: 200 + mode: active + members: + - Ethernet1/33 + +- name: Create aggregate of linkagg definitions + community.network.cnos_linkagg: + aggregate: + - { group: 3, mode: on, members: [Ethernet1/33] } + - { group: 100, mode: passive, members: [Ethernet1/44] } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface port-channel 30 + - interface Ethernet1/33 + - channel-group 30 mode on + - no interface port-channel 30 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + group = w['group'] + mode = w['mode'] + members = w.get('members') or [] + state = w['state'] + del w['state'] + + obj_in_have = search_obj_in_list(group, have) + + if state == 'absent': + if obj_in_have: + commands.append('no interface port-channel {0}'.format(group)) + + elif state == 'present': + cmd = ['interface port-channel {0}'.format(group), + 'exit'] + if not obj_in_have: + if not group: + module.fail_json(msg='group is a required option') + commands.extend(cmd) + + if members: + for m in members: + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + else: + if members: + if 'members' not in obj_in_have.keys(): + for m in members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + elif set(members) != set(obj_in_have['members']): + missing_members = list(set(members) - set(obj_in_have['members'])) + for m in missing_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + superfluous_members = list(set(obj_in_have['members']) - set(members)) + for m in superfluous_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('no channel-group') + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['group'], want) + if not obj_in_want: + commands.append('no interface port-channel {0}'.format(h['group'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['group'] = str(d['group']) + + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'] + }) + + return obj + + +def parse_mode(module, config, group, member): + mode = None + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = ['interface {0}'.format(member)] + body = netcfg.get_section(parents) + + match_int = re.findall(r'interface {0}\n'.format(member), body, re.M) + if match_int: + match = re.search(r'channel-group {0} mode (\S+)'.format(group), + body, re.M) + if match: + mode = match.group(1) + + return mode + + +def parse_members(module, config, group): + members = [] + + for line in config.strip().split('!'): + l = line.strip() + if l.startswith('interface'): + match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M) + if match_group: + match = re.search(r'interface (\S+)', l, re.M) + if match: + members.append(match.group(1)) + + return members + + +def get_channel(module, config, group): + match = re.findall(r'^interface (\S+)', config, re.M) + + if not match: + return {} + + channel = {} + for item in set(match): + member = item + channel['mode'] = parse_mode(module, config, group, member) + channel['members'] = parse_members(module, config, group) + + return channel + + +def map_config_to_obj(module): + objs = list() + config = get_config(module) + + for line in config.split('\n'): + l = line.strip() + match = re.search(r'interface port-channel(\S+)', l, re.M) + if match: + obj = {} + group = match.group(1) + obj['group'] = group + obj.update(get_channel(module, config, group)) + objs.append(obj) + + return objs + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + group=dict(type='int'), + mode=dict(choices=['active', 'on', 'passive']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True) + + required_one_of = [['group', 'aggregate']] + required_together = [['members', 'mode']] + mutually_exclusive = [['group', 'aggregate']] + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, + required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_lldp.py new file mode 100644 index 00000000..521b9331 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_lldp.py @@ -0,0 +1,135 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_lldp +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage LLDP configuration on Lenovo CNOS network devices. +description: + - This module provides declarative management of LLDP service + on Lenovc CNOS network devices. +notes: + - Tested against CNOS 10.9.1 +options: + state: + description: + - State of the LLDP configuration. If value is I(present) lldp will be + enabled else if it is I(absent) it will be disabled. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Enable LLDP service + community.network.cnos_lldp: + state: present + +- name: Disable LLDP service + community.network.cnos_lldp: + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - lldp timer 1024 + - lldp trap-interval 330 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, run_commands +from ansible.module_utils.connection import exec_command + + +def get_ethernet_range(module): + output = run_commands(module, ['show interface brief'])[0].split('\n') + maxport = None + last_interface = None + for line in output: + if line.startswith('Ethernet1/'): + last_interface = line.split(' ')[0] + if last_interface is not None: + eths = last_interface.split('/') + maxport = eths[1] + return maxport + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + state=dict(default='present', + choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + maxport = get_ethernet_range(module) + commands = [] + prime_cmd = 'interface ethernet 1/1-' + maxport + + if module.params['state'] == 'absent': + commands.append(prime_cmd) + commands.append('no lldp receive') + commands.append('no lldp transmit') + commands.append('exit') + commands.append('interface mgmt 0') + commands.append('no lldp receive') + commands.append('no lldp transmit') + commands.append('exit') + elif module.params['state'] == 'present': + commands.append(prime_cmd) + commands.append('lldp receive') + commands.append('lldp transmit') + commands.append('exit') + commands.append('interface mgmt 0') + commands.append('lldp receive') + commands.append('lldp transmit') + commands.append('exit') + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_logging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_logging.py new file mode 100644 index 00000000..6aa7cbf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_logging.py @@ -0,0 +1,421 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_logging +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage logging on network devices +description: + - This module provides declarative management of logging + on Cisco Cnos devices. +notes: + - Tested against CNOS 10.9.1 +options: + dest: + description: + - Destination of the logs. Lenovo uses the term server instead of host in + its CLI. + choices: ['server', 'console', 'monitor', 'logfile'] + name: + description: + - If value of C(dest) is I(file) it indicates file-name + and for I(server) indicates the server name to be notified. + size: + description: + - Size of buffer. The acceptable value is in range from 4096 to + 4294967295 bytes. + default: 10485760 + facility: + description: + - Set logging facility. This is applicable only for server logging + level: + description: + - Set logging severity levels. 0-emerg;1-alert;2-crit;3-err;4-warn; + 5-notif;6-inform;7-debug + default: 5 + aggregate: + description: List of logging definitions. + state: + description: + - State of the logging configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure server logging + community.network.cnos_logging: + dest: server + name: 10.241.107.224 + facility: local7 + state: present + +- name: Remove server logging configuration + community.network.cnos_logging: + dest: server + name: 10.241.107.224 + state: absent + +- name: Configure console logging level and facility + community.network.cnos_logging: + dest: console + level: 7 + state: present + +- name: Configure buffer size + community.network.cnos_logging: + dest: logfile + level: 5 + name: testfile + size: 5000 + +- name: Configure logging using aggregate + community.network.cnos_logging: + aggregate: + - { dest: console, level: 6 } + - { dest: logfile, size: 9000 } + +- name: Remove logging using aggregate + community.network.cnos_logging: + aggregate: + - { dest: console, level: 6 } + - { dest: logfile, name: anil, size: 9000 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging console 7 + - logging server 10.241.107.224 +""" + +import re + +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_capabilities +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def validate_size(value, module): + if value: + if not int(4096) <= int(value) <= int(4294967295): + module.fail_json(msg='size must be between 4096 and 4294967295') + else: + return value + + +def map_obj_to_commands(updates, module): + dest_group = ('console', 'monitor', 'logfile', 'server') + commands = list() + want, have = updates + for w in want: + dest = w['dest'] + name = w['name'] + size = w['size'] + facility = w['facility'] + level = w['level'] + state = w['state'] + del w['state'] + + if state == 'absent': + if dest: + + if dest == 'server': + commands.append('no logging server {0}'.format(name)) + + elif dest in dest_group: + commands.append('no logging {0}'.format(dest)) + + else: + module.fail_json(msg='dest must be among console, monitor, logfile, server') + + if state == 'present' and w not in have: + if dest == 'server': + cmd_str = 'logging server {0}'.format(name) + if level is not None and level > 0 and level < 8: + cmd_str = cmd_str + ' ' + level + if facility is not None: + cmd_str = cmd_str + ' facility ' + facility + commands.append(cmd_str) + + elif dest == 'logfile' and size: + present = False + + for entry in have: + if entry['dest'] == 'logfile' and entry['size'] == size and entry['level'] == level: + present = True + + if not present: + cmd_str = 'logging logfile ' + if name is not None: + cmd_str = cmd_str + name + if level and level != '7': + cmd_str = cmd_str + ' ' + level + else: + cmd_str = cmd_str + ' 7' + if size is not None: + cmd_str = cmd_str + ' size ' + size + commands.append(cmd_str) + else: + module.fail_json(msg='Name of the logfile is a mandatory parameter') + + else: + if dest: + dest_cmd = 'logging {0}'.format(dest) + if level: + dest_cmd += ' {0}'.format(level) + commands.append(dest_cmd) + return commands + + +def parse_facility(line, dest): + facility = None + if dest == 'server': + result = line.split() + i = 0 + for x in result: + if x == 'facility': + return result[i + 1] + i = i + 1 + return facility + + +def parse_size(line, dest): + size = None + if dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + i = 0 + for x in result: + if x == 'size': + return result[i + 1] + i = i + 1 + return '10485760' + return size + + +def parse_name(line, dest): + name = None + if dest == 'server': + if 'logging server' in line: + result = line.split() + i = 0 + for x in result: + if x == 'server': + name = result[i + 1] + elif dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + i = 0 + for x in result: + if x == 'logfile': + name = result[i + 1] + else: + name = None + return name + + +def parse_level(line, dest): + level_group = ('0', '1', '2', '3', '4', '5', '6', '7') + level = '7' + if dest == 'server': + if 'logging server' in line: + result = line.split() + if(len(result) > 3): + if result[3].isdigit(): + level = result[3] + else: + if dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + if result[3].isdigit(): + level = result[3] + else: + match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M) + + return level + + +def map_config_to_obj(module): + obj = [] + dest_group = ('console', 'server', 'monitor', 'logfile') + data = get_config(module, flags=['| include logging']) + index = 0 + for line in data.split('\n'): + logs = line.split() + index = len(logs) + if index == 0 or index == 1: + continue + if logs[0] != 'logging': + continue + if logs[1] == 'monitor' or logs[1] == 'console': + obj.append({'dest': logs[1], 'level': logs[2]}) + elif logs[1] == 'logfile': + level = '5' + if index > 3 and logs[3].isdigit(): + level = logs[3] + size = '10485760' + if len(logs) > 4: + size = logs[5] + obj.append({'dest': logs[1], 'name': logs[2], 'size': size, 'level': level}) + elif logs[1] == 'server': + level = '5' + facility = None + + if index > 3 and logs[3].isdigit(): + level = logs[3] + if index > 3 and logs[3] == 'facility': + facility = logs[4] + if index > 4 and logs[4] == 'facility': + facility = logs[5] + obj.append({'dest': logs[1], 'name': logs[2], 'facility': facility, 'level': level}) + else: + continue + return obj + + +def map_params_to_obj(module, required_if=None): + obj = [] + aggregate = module.params.get('aggregate') + + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + module._check_required_if(required_if, item) + + d = item.copy() + if d['dest'] != 'server' and d['dest'] != 'logfile': + d['name'] = None + + if d['dest'] == 'logfile': + if 'size' in d: + d['size'] = str(validate_size(d['size'], module)) + elif 'size' not in d: + d['size'] = str(10485760) + else: + pass + + if d['dest'] != 'logfile': + d['size'] = None + + obj.append(d) + + else: + if module.params['dest'] != 'server' and module.params['dest'] != 'logfile': + module.params['name'] = None + + if module.params['dest'] == 'logfile': + if not module.params['size']: + module.params['size'] = str(10485760) + else: + module.params['size'] = None + + if module.params['size'] is None: + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'size': module.params['size'], + 'facility': module.params['facility'], + 'level': module.params['level'], + 'state': module.params['state'] + }) + + else: + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'size': str(validate_size(module.params['size'], module)), + 'facility': module.params['facility'], + 'level': module.params['level'], + 'state': module.params['state'] + }) + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + dest=dict(type='str', + choices=['server', 'console', 'monitor', 'logfile']), + name=dict(type='str'), + size=dict(type='int', default=10485760), + facility=dict(type='str'), + level=dict(type='str', default='5'), + state=dict(default='present', choices=['present', 'absent']), + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_if = [('dest', 'server', ['name'])] + + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_if=required_if) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_reload.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_reload.py new file mode 100644 index 00000000..61931293 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_reload.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to reload Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_reload +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Perform switch restart on devices running Lenovo CNOS +description: + - This module allows you to restart the switch using the current startup + configuration. The module is usually invoked after the running + configuration has been saved over the startup configuration. + This module uses SSH to manage network device configuration. + The results of the operation can be viewed in results directory. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_reload. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Reload + community.network.cnos_reload: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_reload_{{ inventory_hostname }}_output.txt" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Device is Reloading. Please wait..." +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'reload' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': 'reboot system? (y/n): ', + 'answer': 'y'}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg in "Device Response Timed out"): + module.exit_json(changed=True, + msg="Device is Reloading. Please wait...") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_rollback.py new file mode 100644 index 00000000..9051fdb0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_rollback.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Rollback Config back to Lenovo Switches +# +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_rollback +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Roll back the running or startup configuration from a remote + server on devices running Lenovo CNOS +description: + - This module allows you to work with switch configurations. It provides a + way to roll back configurations of a switch from a remote server. This is + achieved by using startup or running configurations of the target device + that were previously backed up to a remote server using FTP, SFTP, TFTP, + or SCP. The first step is to create a directory from where the remote + server can be reached. The next step is to provide the full file path of + he backup configuration's location. Authentication details required by the + remote server must be provided as well. + By default, this method overwrites the switch's configuration file with + the newly downloaded file. This module uses SSH to manage network device + configuration. The results of the operation will be placed in a directory + named 'results' that must be created by the user in their local directory + to where the playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + configType: + description: + - This refers to the type of configuration which will be used for + the rolling back process. The choices are the running or startup + configurations. There is no default value, so it will result + in an error if the input is incorrect. + required: Yes + default: Null + choices: [running-config, startup-config] + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server from where to download the backup + configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is not + specified, there is no default value to be used. + required: Yes + default: Null + choices: [SFTP, SCP, FTP, TFTP] + rcserverip: + description: + - This specifies the IP Address of the remote server from where the + backup configuration will be downloaded. + required: Yes + default: Null + rcpath: + description: + - This specifies the full file path of the configuration file + located on the remote server. In case the relative path is used as + the variable value, the root folder for the user of the server + needs to be specified. + required: Yes + default: Null + serverusername: + description: + - Specify username for the server relating to the protocol used. + required: Yes + default: Null + serverpassword: + description: + - Specify password for the server relating to the protocol used. + required: Yes + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_rollback. + These are written in the main.yml file of the tasks directory. +--- + +- name: Test Rollback of config - Running config + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Startup config + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Running config - TFTP + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Startup config - TFTP + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Config file transferred to Device" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +# Utility Method to rollback the running config or start up config +# This method supports only SCP or SFTP or FTP or TFTP +def doConfigRollBack(module, prompt, answer): + host = module.params['host'] + server = module.params['serverip'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + protocol = module.params['protocol'].lower() + rcPath = module.params['rcpath'] + configType = module.params['configType'] + confPath = rcPath + retVal = '' + + command = "copy " + protocol + " " + protocol + "://" + command = command + username + "@" + server + "/" + confPath + command = command + " " + configType + " vrf management\n" + cnos.debugOutput(command + "\n") + # cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes') + cmd = [] + if(protocol == "scp"): + scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}] + scp_cmd2 = [{'command': '\n', 'prompt': 'Password:', + 'answer': password}] + cmd.extend(scp_cmd1) + cmd.extend(scp_cmd2) + if(configType == 'startup-config'): + scp_cmd3 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(scp_cmd3) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + sftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(sftp_cmd) + # cnos.debugOutput(configType + "\n") + if(configType == 'startup-config'): + sftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(sftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + ftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(ftp_cmd) + if(configType == 'startup-config'): + ftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(ftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + protocol + " " + protocol + command = command + "://" + server + "/" + confPath + command = command + " " + configType + " vrf management\n" + cnos.debugOutput(command) + tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd) + if(configType == 'startup-config'): + tftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + configType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + rcpath=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + if protocol in ('tftp', 'ftp', 'sftp', 'scp'): + transfer_status = doConfigRollBack(module, None, None) + else: + transfer_status = 'Invalid Protocol option' + output = output + "\n Config Transfer status \n" + transfer_status + + # Save it into the file + if '/' in outputfile: + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # need to add logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Config file transferred to Device") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_save.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_save.py new file mode 100644 index 00000000..9def4a94 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_save.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to save running config to start up config to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_save +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Save the running configuration as the startup configuration + on devices running Lenovo CNOS +description: + - This module allows you to copy the running configuration of a switch over + its startup configuration. It is recommended to use this module shortly + after any major configuration changes so they persist after a switch + restart. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_save. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Save + community.network.cnos_save: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_save_{{ inventory_hostname }}_output.txt" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Switch Running Config is Saved to Startup Config" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'write memory' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Switch Running Config is Saved to Startup Config ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_showrun.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_showrun.py new file mode 100644 index 00000000..87757c8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_showrun.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to display running config of Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_showrun +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect the current running configuration on devices running on CNOS +description: + - This module allows you to view the switch running configuration. It + executes the display running-config CLI command on a switch and returns a + file containing the current running configuration of the target network + device. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_showrun. These are + written in the main.yml file of the tasks directory. +--- +- name: Run show running-config + community.network.cnos_showrun: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_showrun_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Running Configuration saved in file" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + command = 'show running-config' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Running Configuration saved in file ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_static_route.py new file mode 100644 index 00000000..53f0d9f4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_static_route.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_static_route +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage static IP routes on Lenovo CNOS network devices +description: + - This module provides declarative management of static + IP routes on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.10.1 +options: + prefix: + description: + - Network prefix of the static route. + mask: + description: + - Network prefix mask of the static route. + next_hop: + description: + - Next hop IP of the static route. + interface: + description: + - Interface of the static route. + description: + description: + - Name of the static route + aliases: ['description'] + admin_distance: + description: + - Admin distance of the static route. + default: 1 + tag: + description: + - Set tag of the static route. + aggregate: + description: List of static route definitions. + state: + description: + - State of the static route configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure static route + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + next_hop: 10.241.106.1 + +- name: Configure ultimate route with name and tag + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + interface: Ethernet1/13 + description: hello world + tag: 100 + +- name: Remove configuration + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + next_hop: 10.241.106.0 + state: absent + +- name: Add static route aggregates + community.network.cnos_static_route: + aggregate: + - { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 } + - { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 } + +- name: Remove static route aggregates + community.network.cnos_static_route: + aggregate: + - { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 } + - { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - ip route 10.241.107.0 255.255.255.0 10.241.106.0 +""" +from copy import deepcopy +from re import findall +from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def map_obj_to_commands(want, have): + commands = list() + + for w in want: + state = w['state'] + command = 'ip route' + prefix = w['prefix'] + mask = w['mask'] + command = ' '.join((command, prefix, mask)) + + for key in ['interface', 'next_hop', 'admin_distance', 'tag', + 'description']: + if w.get(key): + if key == 'description' and len(w.get(key).split()) > 1: + # name with multiple words needs to be quoted + command = ' '.join((command, key, '"%s"' % w.get(key))) + elif key in ('description', 'tag'): + command = ' '.join((command, key, w.get(key))) + else: + command = ' '.join((command, w.get(key))) + + if state == 'absent': + commands.append('no %s' % command) + elif state == 'present': + commands.append(command) + + return commands + + +def map_config_to_obj(module): + obj = [] + + out = get_config(module, flags='| include ip route') + for line in out.splitlines(): + # Split by whitespace but do not split quotes, needed for description + splitted_line = findall(r'[^"\s]\S*|".+?"', line) + route = {} + prefix_with_mask = splitted_line[2] + prefix = None + mask = None + iface = None + nhop = None + if validate_ip_address(prefix_with_mask) is True: + my_net = ipaddress.ip_network(prefix_with_mask) + prefix = str(my_net.network_address) + mask = str(my_net.netmask) + route.update({'prefix': prefix, + 'mask': mask, 'admin_distance': '1'}) + if splitted_line[3] is not None: + if validate_ip_address(splitted_line[3]) is False: + iface = str(splitted_line[3]) + route.update(interface=iface) + if validate_ip_address(splitted_line[4]) is True: + nhop = str(splitted_line[4]) + route.update(next_hop=nhop) + if splitted_line[5].isdigit(): + route.update(admin_distance=str(splitted_line[5])) + elif splitted_line[4].isdigit(): + route.update(admin_distance=str(splitted_line[4])) + else: + if splitted_line[6] is not None and splitted_line[6].isdigit(): + route.update(admin_distance=str(splitted_line[6])) + else: + nhop = str(splitted_line[3]) + route.update(next_hop=nhop) + if splitted_line[4].isdigit(): + route.update(admin_distance=str(splitted_line[4])) + + index = 0 + for word in splitted_line: + if word in ('tag', 'description'): + route.update(word=splitted_line[index + 1]) + index = index + 1 + obj.append(route) + + return obj + + +def map_params_to_obj(module, required_together=None): + keys = ['prefix', 'mask', 'state', 'next_hop', 'interface', 'description', + 'admin_distance', 'tag'] + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + route = item.copy() + for key in keys: + if route.get(key) is None: + route[key] = module.params.get(key) + + route = dict((k, v) for k, v in route.items() if v is not None) + module._check_required_together(required_together, route) + obj.append(route) + else: + module._check_required_together(required_together, module.params) + route = dict() + for key in keys: + if module.params.get(key) is not None: + route[key] = module.params.get(key) + obj.append(route) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + next_hop=dict(type='str'), + interface=dict(type='str'), + description=dict(type='str'), + admin_distance=dict(type='str', default='1'), + tag=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['prefix'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + argument_spec.update(element_spec) + + required_one_of = [['aggregate', 'prefix']] + required_together = [['prefix', 'mask']] + mutually_exclusive = [['aggregate', 'prefix']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + want = map_params_to_obj(module, required_together=required_together) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_system.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_system.py new file mode 100644 index 00000000..eced8ca5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_system.py @@ -0,0 +1,383 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on System Configuration with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_system +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage the system attributes on Lenovo CNOS devices +description: + - This module provides declarative management of node system attributes + on Lenovo CNOS devices. It provides an option to configure host system + parameters or remove those parameters from the device active + configuration. +options: + hostname: + description: + - Configure the device hostname parameter. This option takes an + ASCII string value or keyword 'default' + domain_name: + description: + - Configures the default domain + name suffix to be used when referencing this node by its + FQDN. This argument accepts either a list of domain names or + a list of dicts that configure the domain name and VRF name or + keyword 'default'. See examples. + lookup_enabled: + description: + - Administrative control for enabling or disabling DNS lookups. + When this argument is set to True, lookups are performed and + when it is set to False, lookups are not performed. + type: bool + domain_search: + description: + - Configures a list of domain + name suffixes to search when performing DNS name resolution. + This argument accepts either a list of domain names or + a list of dicts that configure the domain name and VRF name or + keyword 'default'. See examples. + name_servers: + description: + - List of DNS name servers by IP address to use to perform name resolution + lookups. This argument accepts either a list of DNS servers or + a list of hashes that configure the name server and VRF name or + keyword 'default'. See examples. + lookup_source: + description: + - Provides one or more source interfaces to use for performing DNS + lookups. The interface must be a valid interface configured. + on the device. + state: + description: + - State of the configuration + values in the device's current active configuration. When set + to I(present), the values should be configured in the device active + configuration and when set to I(absent) the values should not be + in the device active configuration + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure hostname and domain-name + community.network.cnos_system: + hostname: cnos01 + domain_name: test.example.com + +- name: Remove configuration + community.network.cnos_system: + state: absent + +- name: Configure name servers + community.network.cnos_system: + name_servers: + - 8.8.8.8 + - 8.8.4.4 + +- name: Configure DNS Lookup sources + community.network.cnos_system: + lookup_source: MgmtEth0/0/CPU0/0 + lookup_enabled: yes + +- name: Configure name servers with VRF support + nxos_system: + name_servers: + - { server: 8.8.8.8, vrf: mgmt } + - { server: 8.8.4.4, vrf: mgmt } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - hostname cnos01 + - ip domain-name test.example.com vrf default +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args, debugOutput +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList + +_CONFIGURED_VRFS = None + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params['state'] + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def difference(x, y, z): + return [item for item in x[z] if item not in y[z]] + + if state == 'absent': + if have['hostname']: + commands.append('no hostname') + + for item in have['domain_name']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + for item in have['domain_search']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + for item in have['name_servers']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + + if state == 'present': + if needs_update('hostname'): + if want['hostname'] == 'default': + if have['hostname']: + commands.append('no hostname') + else: + commands.append('hostname %s' % want['hostname']) + + if want.get('lookup_enabled') is not None: + if have.get('lookup_enabled') != want.get('lookup_enabled'): + cmd = 'ip domain-lookup' + if want['lookup_enabled'] is False: + cmd = 'no %s' % cmd + commands.append(cmd) + + if want['domain_name']: + if want.get('domain_name')[0]['name'] == 'default': + if have['domain_name']: + for item in have['domain_name']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'domain_name'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'domain_name'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + if want['domain_search']: + if want.get('domain_search')[0]['name'] == 'default': + if have['domain_search']: + for item in have['domain_search']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'domain_search'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'domain_search'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + if want['name_servers']: + if want.get('name_servers')[0]['server'] == 'default': + if have['name_servers']: + for item in have['name_servers']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'name_servers'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'name_servers'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + + return commands + + +def parse_hostname(config): + match = re.search(r'^hostname (\S+)', config, re.M) + if match: + return match.group(1) + + +def parse_domain_name(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip domain-name' in line: + datas = line.split() + objects.append({'name': datas[2], 'vrf': datas[4]}) + + return objects + + +def parse_domain_search(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip domain-list' in line: + datas = line.split() + objects.append({'name': datas[2], 'vrf': datas[4]}) + + return objects + + +def parse_name_servers(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip name-server' in line: + datas = line.split() + objects.append({'server': datas[2], 'vrf': datas[4]}) + + return objects + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=2, contents=config) + + return { + 'hostname': parse_hostname(config), + 'lookup_enabled': 'no ip domain-lookup' not in config, + 'domain_name': parse_domain_name(config), + 'domain_search': parse_domain_search(config), + 'name_servers': parse_name_servers(config), + } + + +def map_params_to_obj(module): + obj = { + 'hostname': module.params['hostname'], + 'lookup_enabled': module.params['lookup_enabled'], + } + + domain_name = ComplexList(dict( + name=dict(key=True), + vrf=dict() + ), module) + + domain_search = ComplexList(dict( + name=dict(key=True), + vrf=dict() + ), module) + + name_servers = ComplexList(dict( + server=dict(key=True), + vrf=dict() + ), module) + + for arg, cast in [('domain_name', domain_name), + ('domain_search', domain_search), + ('name_servers', name_servers)]: + if module.params[arg] is not None: + obj[arg] = cast(module.params[arg]) + else: + obj[arg] = None + + return obj + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + hostname=dict(), + lookup_enabled=dict(type='bool'), + + # { name: , vrf: } + domain_name=dict(type='list'), + + # {name: , vrf: } + domain_search=dict(type='list'), + + # { server: ; vrf: } + name_servers=dict(type='list'), + + lookup_source=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_template.py new file mode 100644 index 00000000..07d61116 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_template.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send CLI templates to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_template +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage switch configuration using templates on devices running Lenovo CNOS +description: + - This module allows you to work with the running configuration of a switch. It provides a way + to execute a set of CNOS commands on a switch by evaluating the current running configuration + and executing the commands only if the specific settings have not been already configured. + The configuration source can be a set of commands or a template written in the Jinja2 templating language. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + commandfile: + description: + - This specifies the path to the CNOS command file which needs to be applied. This usually + comes from the commands folder. Generally this file is the output of the variables applied + on a template file. So this command is preceded by a template module. + Note The command file must contain the Ansible keyword {{ inventory_hostname }} in its + filename to ensure that the command file is unique for each switch and condition. + If this is omitted, the command file will be overwritten during iteration. For example, + commandfile=./commands/clos_leaf_bgp_{{ inventory_hostname }}_commands.txt + required: true + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_template. These are written in the main.yml file of the tasks directory. +--- +- name: Replace Config CLI command template with values + template: + src: demo_template.j2 + dest: "./commands/demo_template_{{ inventory_hostname }}_commands.txt" + vlanid1: 13 + slot_chassis_number1: "1/2" + portchannel_interface_number1: 100 + portchannel_mode1: "active" + +- name: Applying CLI commands on Switches + community.network.cnos_template: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + commandfile: "./commands/demo_template_{{ inventory_hostname }}_commands.txt" + outputfile: "./results/demo_template_command_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Template Applied." +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + commandfile=dict(required=True), + outputfile=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + commandfile = module.params['commandfile'] + outputfile = module.params['outputfile'] + output = '' + + # Send commands one by one to the device + f = open(commandfile, "r") + cmd = [] + for line in f: + # Omit the comment lines in template file + if not line.startswith("#"): + command = line.strip() + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write output to file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Template Applied") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_user.py new file mode 100644 index 00000000..a32aecb7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_user.py @@ -0,0 +1,386 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on management of local users on Lenovo CNOS Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_user +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage the collection of local users on Lenovo CNOS devices +description: + - This module provides declarative management of the local usernames + configured on Lenovo CNOS devices. It allows playbooks to manage + either individual usernames or the collection of usernames in the + current running config. It also supports purging usernames from the + configuration that are not explicitly defined. +options: + aggregate: + description: + - The set of username objects to be configured on the remote + Lenovo CNOS device. The list entries can either be the username + or a hash of username and properties. This argument is mutually + exclusive with the C(name) argument. + aliases: ['users', 'collection'] + name: + description: + - The username to be configured on the remote Lenovo CNOS + device. This argument accepts a string value and is mutually + exclusive with the C(aggregate) argument. + configured_password: + description: + - The password to be configured on the network device. The + password needs to be provided in cleartext and it will be encrypted + on the device. + Please note that this option is not same as C(provider password). + update_password: + description: + - Since passwords are encrypted in the device running config, this + argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + default: always + choices: ['on_create', 'always'] + role: + description: + - The C(role) argument configures the role for the username in the + device running configuration. The argument accepts a string value + defining the role name. This argument does not check if the role + has been configured on the device. + aliases: ['roles'] + sshkey: + description: + - The C(sshkey) argument defines the SSH public key to configure + for the username. This argument accepts a valid SSH key value. + purge: + description: + - The C(purge) argument instructs the module to consider the + resource definition absolute. It will remove any previously + configured usernames on the device with the exception of the + `admin` user which cannot be deleted per cnos constraints. + type: bool + default: 'no' + state: + description: + - The C(state) argument configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create a new user + community.network.cnos_user: + name: ansible + sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + state: present + +- name: Remove all users except admin + community.network.cnos_user: + purge: yes + +- name: Set multiple users role + aggregate: + - name: Netop + - name: Netend + role: network-operator + state: present +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - name ansible + - name ansible password password +start: + description: The time the job started + returned: always + type: str + sample: "2016-11-16 10:38:15.126146" +end: + description: The time the job ended + returned: always + type: str + sample: "2016-11-16 10:38:25.595612" +delta: + description: The time elapsed to perform all operations + returned: always + type: str + sample: "0:00:10.469466" +""" +import re + +from copy import deepcopy +from functools import partial + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types, iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_user_roles + + +def validate_roles(value, module): + for item in value: + if item not in get_user_roles(): + module.fail_json(msg='invalid role specified') + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + update_password = module.params['update_password'] + + for update in updates: + want, have = update + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def add(x): + return commands.append('username %s %s' % (want['name'], x)) + + def remove(x): + return commands.append('no username %s %s' % (want['name'], x)) + + if want['state'] == 'absent': + commands.append('no username %s' % want['name']) + continue + + if want['state'] == 'present' and not have: + commands.append('username %s' % want['name']) + + if needs_update('configured_password'): + if update_password == 'always' or not have: + add('password %s' % want['configured_password']) + + if needs_update('sshkey'): + add('sshkey %s' % want['sshkey']) + + if want['roles']: + if have: + for item in set(have['roles']).difference(want['roles']): + remove('role %s' % item) + + for item in set(want['roles']).difference(have['roles']): + add('role %s' % item) + else: + for item in want['roles']: + add('role %s' % item) + + return commands + + +def parse_password(data): + if 'no password set' in data: + return None + return '' + + +def parse_roles(data): + roles = list() + if 'role:' in data: + items = data.split() + my_item = items[items.index('role:') + 1] + roles.append(my_item) + return roles + + +def parse_username(data): + name = data.split(' ', 1)[0] + username = name[1:] + return username + + +def parse_sshkey(data): + key = None + if 'sskkey:' in data: + items = data.split() + key = items[items.index('sshkey:') + 1] + return key + + +def map_config_to_obj(module): + out = run_commands(module, ['show user-account']) + data = out[0] + objects = list() + datum = data.split('User') + + for item in datum: + objects.append({ + 'name': parse_username(item), + 'configured_password': parse_password(item), + 'sshkey': parse_sshkey(item), + 'roles': parse_roles(item), + 'state': 'present' + }) + return objects + + +def get_param_value(key, item, module): + # if key doesn't exist in the item, get it from module.params + if not item.get(key): + value = module.params[key] + + # if key does exist, do a type check on it to validate it + else: + value_type = module.argument_spec[key].get('type', 'str') + type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] + type_checker(item[key]) + value = item[key] + + return value + + +def map_params_to_obj(module): + aggregate = module.params['aggregate'] + if not aggregate: + if not module.params['name'] and module.params['purge']: + return list() + elif not module.params['name']: + module.fail_json(msg='username is required') + else: + collection = [{'name': module.params['name']}] + else: + collection = list() + for item in aggregate: + if not isinstance(item, dict): + collection.append({'name': item}) + elif 'name' not in item: + module.fail_json(msg='name is required') + else: + collection.append(item) + + objects = list() + + for item in collection: + get_value = partial(get_param_value, item=item, module=module) + item.update({ + 'configured_password': get_value('configured_password'), + 'sshkey': get_value('sshkey'), + 'roles': get_value('roles'), + 'state': get_value('state') + }) + + for key, value in iteritems(item): + if value: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if all((value, validator)): + validator(value, module) + + objects.append(item) + + return objects + + +def update_objects(want, have): + updates = list() + for entry in want: + item = next((i for i in have if i['name'] == entry['name']), None) + if all((item is None, entry['state'] == 'present')): + updates.append((entry, {})) + elif item: + for key, value in iteritems(entry): + if value and value != item[key]: + updates.append((entry, item)) + return updates + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + configured_password=dict(no_log=True), + update_password=dict(default='always', choices=['on_create', 'always']), + roles=dict(type='list', aliases=['role']), + sshkey=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', + options=aggregate_spec, aliases=['collection', 'users']), + purge=dict(type='bool', default=False) + ) + + argument_spec.update(element_spec) + + mutually_exclusive = [('name', 'aggregate')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(update_objects(want, have), module) + + if module.params['purge']: + want_users = [x['name'] for x in want] + have_users = [x['name'] for x in have] + for item in set(have_users).difference(want_users): + if item != 'admin': + if not item.strip(): + continue + item = item.replace("\\", "\\\\") + commands.append('no username %s' % item) + + result['commands'] = commands + + # the cnos cli prevents this by rule but still + if 'no username admin' in commands: + module.fail_json(msg='Cannot delete the `admin` account') + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlag.py new file mode 100644 index 00000000..aa3ffa20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlag.py @@ -0,0 +1,441 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send VLAG commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_vlag +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage VLAG resources and attributes on devices running + Lenovo CNOS +description: + - This module allows you to work with virtual Link Aggregation Groups + (vLAG) related configurations. The operators used are overloaded to ensure + control over switch vLAG configurations. Apart from the regular device + connection related attributes, there are four vLAG arguments which are + overloaded variables that will perform further configurations. They are + vlagArg1, vlagArg2, vlagArg3, and vlagArg4. For more details on how to use + these arguments, see [Overloaded Variables]. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + vlagArg1: + description: + - This is an overloaded vlag first argument. Usage of this argument can + be found is the User Guide referenced above. + required: Yes + default: Null + choices: [enable, auto-recovery,config-consistency,isl,mac-address-table, + peer-gateway,priority,startup-delay,tier-id,vrrp,instance,hlthchk] + vlagArg2: + description: + - This is an overloaded vlag second argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [Interval in seconds,disable or strict,Port Aggregation Number, + VLAG priority,Delay time in seconds,VLAG tier-id value, + VLAG instance number,keepalive-attempts,keepalive-interval, + retry-interval,peer-ip] + vlagArg3: + description: + - This is an overloaded vlag third argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [enable or port-aggregation,Number of keepalive attempts, + Interval in seconds,Interval in seconds, + VLAG health check peer IP4 address] + vlagArg4: + description: + - This is an overloaded vlag fourth argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [Port Aggregation Number,default or management] + +''' +EXAMPLES = ''' + +Tasks : The following are examples of using the module cnos_vlag. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Vlag - enable + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "enable" + +- name: Test Vlag - autorecovery + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "auto-recovery" + vlagArg2: 266 + +- name: Test Vlag - config-consistency + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "config-consistency" + vlagArg2: "strict" + +- name: Test Vlag - isl + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "isl" + vlagArg2: 23 + +- name: Test Vlag - mac-address-table + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "mac-address-table" + +- name: Test Vlag - peer-gateway + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "peer-gateway" + +- name: Test Vlag - priority + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "priority" + vlagArg2: 1313 + +- name: Test Vlag - startup-delay + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "startup-delay" + vlagArg2: 323 + +- name: Test Vlag - tier-id + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "tier-id" + vlagArg2: 313 + +- name: Test Vlag - vrrp + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "vrrp" + +- name: Test Vlag - instance + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "instance" + vlagArg2: 33 + vlagArg3: 333 + +- name: Test Vlag - instance2 + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "instance" + vlagArg2: "33" + +- name: Test Vlag - keepalive-attempts + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "keepalive-attempts" + vlagArg3: 13 + +- name: Test Vlag - keepalive-interval + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "keepalive-interval" + vlagArg3: 131 + +- name: Test Vlag - retry-interval + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "retry-interval" + vlagArg3: 133 + +- name: Test Vlag - peer ip + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "peer-ip" + vlagArg3: "1.2.3.4" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "vLAG configurations accomplished" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def vlagConfig(module, prompt, answer): + + retVal = '' + # vlag config command happens here. + command = 'vlag ' + + vlagArg1 = module.params['vlagArg1'] + vlagArg2 = module.params['vlagArg2'] + vlagArg3 = module.params['vlagArg3'] + vlagArg4 = module.params['vlagArg4'] + deviceType = module.params['deviceType'] + + if(vlagArg1 == "enable"): + # debugOutput("enable") + command = command + vlagArg1 + " " + + elif(vlagArg1 == "auto-recovery"): + # debugOutput("auto-recovery") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_auto_recovery", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-160" + return retVal + + elif(vlagArg1 == "config-consistency"): + # debugOutput("config-consistency") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_config_consistency", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-161" + return retVal + + elif(vlagArg1 == "isl"): + # debugOutput("isl") + command = command + vlagArg1 + " port-channel " + value = cnos.checkSanityofVariable( + deviceType, "vlag_port_aggregation", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-162" + return retVal + + elif(vlagArg1 == "mac-address-table"): + # debugOutput("mac-address-table") + command = command + vlagArg1 + " refresh" + + elif(vlagArg1 == "peer-gateway"): + # debugOutput("peer-gateway") + command = command + vlagArg1 + " " + + elif(vlagArg1 == "priority"): + # debugOutput("priority") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_priority", + vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-163" + return retVal + + elif(vlagArg1 == "startup-delay"): + # debugOutput("startup-delay") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_startup_delay", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-164" + return retVal + + elif(vlagArg1 == "tier-id"): + # debugOutput("tier-id") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_tier_id", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-165" + return retVal + + elif(vlagArg1 == "vrrp"): + # debugOutput("vrrp") + command = command + vlagArg1 + " active" + + elif(vlagArg1 == "instance"): + # debugOutput("instance") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_instance", + vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + if(vlagArg3 is not None): + command = command + " port-channel " + value = cnos.checkSanityofVariable( + deviceType, "vlag_port_aggregation", vlagArg3) + if(value == "ok"): + command = command + vlagArg3 + else: + retVal = "Error-162" + return retVal + else: + command = command + " enable " + else: + retVal = "Error-166" + return retVal + + elif(vlagArg1 == "hlthchk"): + # debugOutput("hlthchk") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_hlthchk_options", vlagArg2) + if(value == "ok"): + if(vlagArg2 == "keepalive-attempts"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_keepalive_attempts", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-167" + return retVal + elif(vlagArg2 == "keepalive-interval"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_keepalive_interval", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-168" + return retVal + elif(vlagArg2 == "retry-interval"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_retry_interval", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-169" + return retVal + elif(vlagArg2 == "peer-ip"): + # Here I am not taking care of IPV6 option. + value = cnos.checkSanityofVariable( + deviceType, "vlag_peerip", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + if(vlagArg4 is not None): + value = cnos.checkSanityofVariable( + deviceType, "vlag_peerip_vrf", vlagArg4) + if(value == "ok"): + command = command + " vrf " + vlagArg4 + else: + retVal = "Error-170" + return retVal + else: + retVal = "Error-171" + return retVal + + else: + retVal = "Error-172" + return retVal + + # debugOutput(command) + cmd = [{'command': command, 'prompt': None, 'answer': None}] + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + return retVal +# EOM + + +def main(): + # + # Define parameters for vlag creation entry + # + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + vlagArg1=dict(required=True), + vlagArg2=dict(required=False), + vlagArg3=dict(required=False), + vlagArg4=dict(required=False),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + output = "" + + # Send the CLi command + output = output + str(vlagConfig(module, '(config)#', None)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + # need to add logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="VLAG configurations accomplished") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlan.py new file mode 100644 index 00000000..d150b2e9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vlan.py @@ -0,0 +1,405 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send VLAN commands to Lenovo Switches +# Overloading aspect of vlan creation in a range is pending +# Lenovo Networking + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_vlan +author: "Anil Kumar Mureleedharan(@amuraleedhar)" +short_description: Manage VLANs on CNOS network devices +description: + - This module provides declarative management of VLANs + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + interfaces: + description: + - List of interfaces that should be associated to the VLAN. + required: true + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for + given vlan C(name) for associated interfaces. If the value in the + C(associated_interfaces) does not match with the operational state of + vlan interfaces on device it will result in failure. + delay: + description: + - Delay the play should wait to check for declarative intent params + values. + default: 10 + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the VLAN configuration. + default: present + choices: ['present', 'absent', 'active', 'suspend'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Create vlan + community.network.cnos_vlan: + vlan_id: 100 + name: test-vlan + state: present + +- name: Add interfaces to VLAN + community.network.cnos_vlan: + vlan_id: 100 + interfaces: + - Ethernet1/33 + - Ethernet1/44 + +- name: Check if interfaces is assigned to VLAN + community.network.cnos_vlan: + vlan_id: 100 + associated_interfaces: + - Ethernet1/33 + - Ethernet1/44 + +- name: Delete vlan + community.network.cnos_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils._text import to_text + + +def search_obj_in_list(vlan_id, lst): + obj = list() + for o in lst: + if o['vlan_id'] == vlan_id: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + interfaces = w['interfaces'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + commands.append('no vlan {0}'.format(vlan_id)) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('name {0}'.format(name)) + + if interfaces: + for i in interfaces: + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + else: + if name: + if name != obj_in_have['name']: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('name {0}'.format(name)) + + if interfaces: + if not obj_in_have['interfaces']: + for i in interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + elif set(interfaces) != set(obj_in_have['interfaces']): + missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces'])) + for i in missing_interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) + for i in superfluous_interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('no switchport access vlan') + else: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('name {0}'.format(name)) + commands.append('state {0}'.format(state)) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + if not obj_in_want and h['vlan_id'] != '1': + commands.append('no vlan {0}'.format(h['vlan_id'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['vlan_id'] = str(d['vlan_id']) + + obj.append(d) + else: + obj.append({ + 'vlan_id': str(module.params['vlan_id']), + 'name': module.params['name'], + 'interfaces': module.params['interfaces'], + # 'associated_interfaces': module.params['associated_interfaces'], + 'state': module.params['state'] + }) + + return obj + + +def parse_to_logical_rows(out): + relevant_data = False + cur_row = [] + for line in out.splitlines(): + if not line: + """Skip empty lines.""" + continue + if '0' < line[0] <= '9': + """Line starting with a number.""" + if len(cur_row) > 0: + yield cur_row + cur_row = [] # Reset it to hold a next chunk + relevant_data = True + if relevant_data: + data = line.strip().split('(') + cur_row.append(data[0]) + yield cur_row + + +def parse_to_obj(logical_rows): + first_row = logical_rows[0] + rest_rows = logical_rows[1:] + vlan_data = first_row.split() + obj = {} + obj['vlan_id'] = vlan_data[0] + obj['name'] = vlan_data[1] + obj['state'] = vlan_data[2] + obj['interfaces'] = rest_rows + return obj + + +def parse_vlan_brief(vlan_out): + return [parse_to_obj(r) for r in parse_to_logical_rows(vlan_out)] + + +def map_config_to_obj(module): + return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0]) + + +def check_declarative_intent_params(want, module, result): + + have = None + is_delay = False + + for w in want: + if w.get('associated_interfaces') is None: + continue + + if result['changed'] and not is_delay: + time.sleep(module.params['delay']) + is_delay = True + + if have is None: + have = map_config_to_obj(module) + + for i in w['associated_interfaces']: + obj_in_have = search_obj_in_list(w['vlan_id'], have) + if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']: + module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='list'), + associated_interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'active', 'suspend']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + check_declarative_intent_params(want, module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vrf.py new file mode 100644 index 00000000..60bb23dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cnos_vrf.py @@ -0,0 +1,365 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on management of local users on Lenovo CNOS Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_vrf +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage VRFs on Lenovo CNOS network devices +description: + - This module provides declarative management of VRFs + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.9.1 +options: + name: + description: + - Name of the VRF. + required: true + rd: + description: + - Route distinguisher of the VRF + interfaces: + description: + - Identifies the set of interfaces that + should be configured in the VRF. Interfaces must be routed + interfaces in order to be placed into a VRF. The name of interface + should be in expanded format and not abbreviated. + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for + given vrf C(name) for associated interfaces. If the value in the + C(associated_interfaces) does not match with the operational state of + vrf interfaces on device it will result in failure. + aggregate: + description: List of VRFs contexts + purge: + description: + - Purge VRFs not defined in the I(aggregate) parameter. + default: no + type: bool + delay: + description: + - Time in seconds to wait before checking for the operational state on + remote device. This wait is applicable for operational state arguments. + default: 10 + state: + description: + - State of the VRF configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vrf + community.network.cnos_vrf: + name: test + rd: 1:200 + interfaces: + - Ethernet1/33 + state: present + +- name: Delete VRFs + community.network.cnos_vrf: + name: test + state: absent + +- name: Create aggregate of VRFs with purge + community.network.cnos_vrf: + aggregate: + - { name: test4, rd: "1:204" } + - { name: test5, rd: "1:205" } + state: present + purge: yes + +- name: Delete aggregate of VRFs + community.network.cnos_vrf: + aggregate: + - name: test2 + - name: test3 + - name: test4 + - name: test5 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vrf context test + - rd 1:100 + - interface Ethernet1/44 + - vrf member test +""" +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec, check_args + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + purge = module.params['purge'] + + for w in want: + name = w['name'] + rd = w['rd'] + interfaces = w['interfaces'] + + obj_in_have = search_obj_in_list(name, have) + + if name == 'default': + module.fail_json(msg='VRF context default is reserved') + elif len(name) > 63: + module.fail_json(msg='VRF name is too long') + if state == 'absent': + if name == 'management': + module.fail_json(msg='Management VRF context cannot be deleted') + if obj_in_have: + commands.append('no vrf context %s' % name) + elif state == 'present': + if not obj_in_have: + commands.append('vrf context %s' % name) + + if rd is not None: + commands.append('rd %s' % rd) + + if w['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + else: + if w['rd'] is not None and w['rd'] != obj_in_have['rd']: + commands.append('vrf context %s' % w['name']) + commands.append('rd %s' % w['rd']) + + if w['interfaces']: + if not obj_in_have['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + elif set(w['interfaces']) != obj_in_have['interfaces']: + missing_interfaces = list(set(w['interfaces']) - set(obj_in_have['interfaces'])) + + for i in missing_interfaces: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['name'], want) + if not obj_in_want: + commands.append('no vrf context %s' % h['name']) + + return commands + + +def map_config_to_obj(module): + objs = [] + output = run_commands(module, {'command': 'show vrf'}) + if output is not None: + vrfText = output[0].strip() + vrfList = vrfText.split('VRF') + for vrfItem in vrfList: + if 'FIB ID' in vrfItem: + obj = dict() + list_of_words = vrfItem.split() + vrfName = list_of_words[0] + obj['name'] = vrfName[:-1] + obj['rd'] = list_of_words[list_of_words.index('RD') + 1] + start = False + obj['interfaces'] = [] + for intName in list_of_words: + if 'Interfaces' in intName: + start = True + if start is True: + if '!' not in intName and 'Interfaces' not in intName: + obj['interfaces'].append(intName.strip().lower()) + objs.append(obj) + else: + module.fail_json(msg='Could not fetch VRF details from device') + return objs + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + if item.get('interfaces'): + item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf] + + if item.get('associated_interfaces'): + item['associated_interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('associated_interfaces') if intf] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'state': module.params['state'], + 'rd': module.params['rd'], + 'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else [], + 'associated_interfaces': [intf.replace(" ", "").lower() for intf in + module.params['associated_interfaces']] if module.params['associated_interfaces'] else [] + + }) + + return obj + + +def check_declarative_intent_params(want, module, result): + have = None + is_delay = False + + for w in want: + if w.get('associated_interfaces') is None: + continue + + if result['changed'] and not is_delay: + time.sleep(module.params['delay']) + is_delay = True + + if have is None: + have = map_config_to_obj(module) + + for i in w['associated_interfaces']: + obj_in_have = search_obj_in_list(w['name'], have) + + if obj_in_have: + interfaces = obj_in_have.get('interfaces') + if interfaces is not None and i not in interfaces: + module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w['name'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + interfaces=dict(type='list'), + associated_interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + rd=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + name = name.lower() + if is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L3' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + check_declarative_intent_params(want, module, result) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cp_publish.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cp_publish.py new file mode 100644 index 00000000..27a0c7ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cp_publish.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage Check Point Firewall (c) 2019 +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: cp_publish +short_description: All the changes done by this user will be seen by all users only after publish is called. +description: + - All the changes done by this user will be seen by all users only after publish is called. + All operations are performed over Web Services API. +author: "Or Soffer (@chkp-orso)" +options: + uid: + description: + - Session unique identifier. Specify it to publish a different session than the one you currently use. + type: str +extends_documentation_fragment: +- check_point.mgmt.checkpoint_commands + +''' + +EXAMPLES = """ +- name: Publish + community.network.cp_publish: +""" + +RETURN = """ +cp_publish: + description: The checkpoint publish output. + returned: always. + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.check_point.mgmt.plugins.module_utils.checkpoint import checkpoint_argument_spec_for_commands, api_command + + +def main(): + argument_spec = dict( + uid=dict(type='str') + ) + argument_spec.update(checkpoint_argument_spec_for_commands) + + module = AnsibleModule(argument_spec=argument_spec) + + command = "publish" + + result = api_command(module, command) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/cv_server_provision.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cv_server_provision.py new file mode 100644 index 00000000..50d6e1ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/cv_server_provision.py @@ -0,0 +1,638 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cv_server_provision +author: "EOS+ CS (ansible-dev@arista.com) (@mharista)" +short_description: + Provision server port by applying or removing template configuration to an + Arista CloudVision Portal configlet that is applied to a switch. +description: + - This module allows a server team to provision server network ports for + new servers without having to access Arista CVP or asking the network team + to do it for them. Provide the information for connecting to CVP, switch + rack, port the new server is connected to, optional vlan, and an action + and the module will apply the configuration to the switch port via CVP. + Actions are add (applies template config to port), + remove (defaults the interface config) and + show (returns the current port config). +options: + host: + description: + - The hostname or IP address of the CVP node being connected to. + required: true + port: + description: + - The port number to use when making API calls to the CVP node. This + will default to the default port for the specified protocol. Port 80 + for http and port 443 for https. + protocol: + description: + - The protocol to use when making API calls to CVP. CVP defaults to https + and newer versions of CVP no longer support http. + default: https + choices: [https, http] + username: + description: + - The user that will be used to connect to CVP for making API calls. + required: true + password: + description: + - The password of the user that will be used to connect to CVP for API + calls. + required: true + server_name: + description: + - The hostname or identifier for the server that is having it's switch + port provisioned. + required: true + switch_name: + description: + - The hostname of the switch is being configured for the server being + provisioned. + required: true + switch_port: + description: + - The physical port number on the switch that the new server is + connected to. + required: true + port_vlan: + description: + - The vlan that should be applied to the port for this server. + This parameter is dependent on a proper template that supports single + vlan provisioning with it. If a port vlan is specified by the template + specified does not support this the module will exit out with no + changes. If a template is specified that requires a port vlan but no + port vlan is specified the module will exit out with no changes. + template: + description: + - A path to a Jinja formatted template file that contains the + configuration block that will be applied to the specified switch port. + This template will have variable fields replaced by the module before + being applied to the switch configuration. + required: true + action: + description: + - The action for the module to take. The actions are add, which applies + the specified template config to port, remove, which defaults the + specified interface configuration, and show, which will return the + current port configuration with no changes. + default: show + choices: [show, add, remove] + auto_run: + description: + - Flag that determines whether or not the module will execute the CVP + task spawned as a result of changes to a switch configlet. When an + add or remove action is taken which results in a change to a switch + configlet, CVP will spawn a task that needs to be executed for the + configuration to be applied to the switch. If this option is True then + the module will determined the task number created by the configuration + change, execute it and wait for the task to complete. If the option + is False then the task will remain in the Pending state in CVP for + a network administrator to review and execute. + type: bool + default: 'no' +requirements: [Jinja2, cvprac >= 0.7.0] +''' + +EXAMPLES = ''' +- name: Get current configuration for interface Ethernet2 + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: template_file.j2 + action: show + +- name: Remove existing configuration from interface Ethernet2. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: template_file.j2 + action: remove + auto_run: True + +- name: Add template configuration to interface Ethernet2. No VLAN. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: single_attached_trunk.j2 + action: add + auto_run: True + +- name: Add template with VLAN configuration to interface Ethernet2. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + port_vlan: 22 + template: single_attached_vlan.j2 + action: add + auto_run: True +''' + +RETURN = ''' +changed: + description: Signifies if a change was made to the configlet + returned: success + type: bool + sample: true +currentConfigBlock: + description: The current config block for the user specified interface + returned: when action = show + type: str + sample: | + interface Ethernet4 + ! +newConfigBlock: + description: The new config block for the user specified interface + returned: when action = add or remove + type: str + sample: | + interface Ethernet3 + description example + no switchport + ! +oldConfigBlock: + description: The current config block for the user specified interface + before any changes are made + returned: when action = add or remove + type: str + sample: | + interface Ethernet3 + ! +fullConfig: + description: The full config of the configlet after being updated + returned: when action = add or remove + type: str + sample: | + ! + interface Ethernet3 + ! + interface Ethernet4 + ! +updateConfigletResponse: + description: Response returned from CVP when configlet update is triggered + returned: when action = add or remove and configuration changes + type: str + sample: "Configlet veos1-server successfully updated and task initiated." +portConfigurable: + description: Signifies if the user specified port has an entry in the + configlet that Ansible has access to + returned: success + type: bool + sample: true +switchConfigurable: + description: Signifies if the user specified switch has a configlet + applied to it that CVP is allowed to edit + returned: success + type: bool + sample: true +switchInfo: + description: Information from CVP describing the switch being configured + returned: success + type: dict + sample: {"architecture": "i386", + "bootupTimeStamp": 1491264298.21, + "complianceCode": "0000", + "complianceIndication": "NONE", + "deviceInfo": "Registered", + "deviceStatus": "Registered", + "fqdn": "veos1", + "hardwareRevision": "", + "internalBuildId": "12-12", + "internalVersion": "4.17.1F-11111.4171F", + "ipAddress": "192.168.1.20", + "isDANZEnabled": "no", + "isMLAGEnabled": "no", + "key": "00:50:56:5d:e5:e0", + "lastSyncUp": 1496432895799, + "memFree": 472976, + "memTotal": 1893460, + "modelName": "vEOS", + "parentContainerId": "container_13_5776759195930", + "serialNumber": "", + "systemMacAddress": "00:50:56:5d:e5:e0", + "taskIdList": [], + "tempAction": null, + "type": "netelement", + "unAuthorized": false, + "version": "4.17.1F", + "ztpMode": "false"} +taskCompleted: + description: Signifies if the task created and executed has completed successfully + returned: when action = add or remove, and auto_run = true, + and configuration changes + type: bool + sample: true +taskCreated: + description: Signifies if a task was created due to configlet changes + returned: when action = add or remove, and auto_run = true or false, + and configuration changes + type: bool + sample: true +taskExecuted: + description: Signifies if the automation executed the spawned task + returned: when action = add or remove, and auto_run = true, + and configuration changes + type: bool + sample: true +taskId: + description: The task ID created by CVP because of changes to configlet + returned: when action = add or remove, and auto_run = true or false, + and configuration changes + type: str + sample: "500" +''' + +import re +import time +from ansible.module_utils.basic import AnsibleModule +try: + import jinja2 + from jinja2 import meta + HAS_JINJA2 = True +except ImportError: + HAS_JINJA2 = False +try: + from cvprac.cvp_client import CvpClient + from cvprac.cvp_client_errors import CvpLoginError, CvpApiError + HAS_CVPRAC = True +except ImportError: + HAS_CVPRAC = False + + +def connect(module): + ''' Connects to CVP device using user provided credentials from playbook. + + :param module: Ansible module with parameters and client connection. + :return: CvpClient object with connection instantiated. + ''' + client = CvpClient() + try: + client.connect([module.params['host']], + module.params['username'], + module.params['password'], + protocol=module.params['protocol'], + port=module.params['port']) + except CvpLoginError as e: + module.fail_json(msg=str(e)) + return client + + +def switch_info(module): + ''' Get dictionary of switch info from CVP. + + :param module: Ansible module with parameters and client connection. + :return: Dict of switch info from CVP or exit with failure if no + info for device is found. + ''' + switch_name = module.params['switch_name'] + switch_info = module.client.api.get_device_by_name(switch_name) + if not switch_info: + module.fail_json(msg=str("Device with name '%s' does not exist." + % switch_name)) + return switch_info + + +def switch_in_compliance(module, sw_info): + ''' Check if switch is currently in compliance. + + :param module: Ansible module with parameters and client connection. + :param sw_info: Dict of switch info. + :return: Nothing or exit with failure if device is not in compliance. + ''' + compliance = module.client.api.check_compliance(sw_info['key'], + sw_info['type']) + if compliance['complianceCode'] != '0000': + module.fail_json(msg=str('Switch %s is not in compliance. Returned' + ' compliance code %s.' + % (sw_info['fqdn'], + compliance['complianceCode']))) + + +def server_configurable_configlet(module, sw_info): + ''' Check CVP that the user specified switch has a configlet assigned to + it that Ansible is allowed to edit. + + :param module: Ansible module with parameters and client connection. + :param sw_info: Dict of switch info. + :return: Dict of configlet information or None. + ''' + configurable_configlet = None + configlet_name = module.params['switch_name'] + '-server' + switch_configlets = module.client.api.get_configlets_by_device_id( + sw_info['key']) + for configlet in switch_configlets: + if configlet['name'] == configlet_name: + configurable_configlet = configlet + return configurable_configlet + + +def port_configurable(module, configlet): + ''' Check configlet if the user specified port has a configuration entry + in the configlet to determine if Ansible is allowed to configure the + port on this switch. + + :param module: Ansible module with parameters and client connection. + :param configlet: Dict of configlet info. + :return: True or False. + ''' + configurable = False + regex = r'^interface Ethernet%s' % module.params['switch_port'] + for config_line in configlet['config'].split('\n'): + if re.match(regex, config_line): + configurable = True + return configurable + + +def configlet_action(module, configlet): + ''' Take appropriate action based on current state of device and user + requested action. + + Return current config block for specified port if action is show. + + If action is add or remove make the appropriate changes to the + configlet and return the associated information. + + :param module: Ansible module with parameters and client connection. + :param configlet: Dict of configlet info. + :return: Dict of information to updated results with. + ''' + result = dict() + existing_config = current_config(module, configlet['config']) + if module.params['action'] == 'show': + result['currentConfigBlock'] = existing_config + return result + elif module.params['action'] == 'add': + result['newConfigBlock'] = config_from_template(module) + elif module.params['action'] == 'remove': + result['newConfigBlock'] = ('interface Ethernet%s\n!' + % module.params['switch_port']) + result['oldConfigBlock'] = existing_config + result['fullConfig'] = updated_configlet_content(module, + configlet['config'], + result['newConfigBlock']) + resp = module.client.api.update_configlet(result['fullConfig'], + configlet['key'], + configlet['name']) + if 'data' in resp: + result['updateConfigletResponse'] = resp['data'] + if 'task' in resp['data']: + result['changed'] = True + result['taskCreated'] = True + return result + + +def current_config(module, config): + ''' Parse the full port configuration for the user specified port out of + the full configlet configuration and return as a string. + + :param module: Ansible module with parameters and client connection. + :param config: Full config to parse specific port config from. + :return: String of current config block for user specified port. + ''' + regex = r'^interface Ethernet%s' % module.params['switch_port'] + match = re.search(regex, config, re.M) + if not match: + module.fail_json(msg=str('interface section not found - %s' + % config)) + block_start, line_end = match.regs[0] + + match = re.search(r'!', config[line_end:], re.M) + if not match: + return config[block_start:] + _, block_end = match.regs[0] + + block_end = line_end + block_end + return config[block_start:block_end] + + +def valid_template(port, template): + ''' Test if the user provided Jinja template is valid. + + :param port: User specified port. + :param template: Contents of Jinja template. + :return: True or False + ''' + valid = True + regex = r'^interface Ethernet%s' % port + match = re.match(regex, template, re.M) + if not match: + valid = False + return valid + + +def config_from_template(module): + ''' Load the Jinja template and apply user provided parameters in necessary + places. Fail if template is not found. Fail if rendered template does + not reference the correct port. Fail if the template requires a VLAN + but the user did not provide one with the port_vlan parameter. + + :param module: Ansible module with parameters and client connection. + :return: String of Jinja template rendered with parameters or exit with + failure. + ''' + template_loader = jinja2.FileSystemLoader('./templates') + env = jinja2.Environment(loader=template_loader, + undefined=jinja2.DebugUndefined) + template = env.get_template(module.params['template']) + if not template: + module.fail_json(msg=str('Could not find template - %s' + % module.params['template'])) + + data = {'switch_port': module.params['switch_port'], + 'server_name': module.params['server_name']} + + temp_source = env.loader.get_source(env, module.params['template'])[0] + parsed_content = env.parse(temp_source) + temp_vars = list(meta.find_undeclared_variables(parsed_content)) + if 'port_vlan' in temp_vars: + if module.params['port_vlan']: + data['port_vlan'] = module.params['port_vlan'] + else: + module.fail_json(msg=str('Template %s requires a vlan. Please' + ' re-run with vlan number provided.' + % module.params['template'])) + + template = template.render(data) + if not valid_template(module.params['switch_port'], template): + module.fail_json(msg=str('Template content does not configure proper' + ' interface - %s' % template)) + return template + + +def updated_configlet_content(module, existing_config, new_config): + ''' Update the configlet configuration with the new section for the port + specified by the user. + + :param module: Ansible module with parameters and client connection. + :param existing_config: String of current configlet configuration. + :param new_config: String of configuration for user specified port to + replace in the existing config. + :return: String of the full updated configuration. + ''' + regex = r'^interface Ethernet%s' % module.params['switch_port'] + match = re.search(regex, existing_config, re.M) + if not match: + module.fail_json(msg=str('interface section not found - %s' + % existing_config)) + block_start, line_end = match.regs[0] + + updated_config = existing_config[:block_start] + new_config + match = re.search(r'!\n', existing_config[line_end:], re.M) + if match: + _, block_end = match.regs[0] + block_end = line_end + block_end + updated_config += '\n%s' % existing_config[block_end:] + return updated_config + + +def configlet_update_task(module): + ''' Poll device info of switch from CVP up to three times to see if the + configlet updates have spawned a task. It sometimes takes a second for + the task to be spawned after configlet updates. If a task is found + return the task ID. Otherwise return None. + + :param module: Ansible module with parameters and client connection. + :return: Task ID or None. + ''' + for num in range(3): + device_info = switch_info(module) + if (('taskIdList' in device_info) and + (len(device_info['taskIdList']) > 0)): + for task in device_info['taskIdList']: + if ('Configlet Assign' in task['description'] and + task['data']['WORKFLOW_ACTION'] == 'Configlet Push'): + return task['workOrderId'] + time.sleep(1) + return None + + +def wait_for_task_completion(module, task): + ''' Poll CVP for the executed task to complete. There is currently no + timeout. Exits with failure if task status is Failed or Cancelled. + + :param module: Ansible module with parameters and client connection. + :param task: Task ID to poll for completion. + :return: True or exit with failure if task is cancelled or fails. + ''' + task_complete = False + while not task_complete: + task_info = module.client.api.get_task_by_id(task) + task_status = task_info['workOrderUserDefinedStatus'] + if task_status == 'Completed': + return True + elif task_status in ['Failed', 'Cancelled']: + module.fail_json(msg=str('Task %s has reported status %s. Please' + ' consult the CVP admins for more' + ' information.' % (task, task_status))) + time.sleep(2) + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + host=dict(required=True), + port=dict(required=False, default=None), + protocol=dict(default='https', choices=['http', 'https']), + username=dict(required=True), + password=dict(required=True, no_log=True), + server_name=dict(required=True), + switch_name=dict(required=True), + switch_port=dict(required=True), + port_vlan=dict(required=False, default=None), + template=dict(require=True), + action=dict(default='show', choices=['show', 'add', 'remove']), + auto_run=dict(type='bool', default=False)) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + if not HAS_JINJA2: + module.fail_json(msg='The Jinja2 python module is required.') + if not HAS_CVPRAC: + module.fail_json(msg='The cvprac python module is required.') + result = dict(changed=False) + module.client = connect(module) + + try: + result['switchInfo'] = switch_info(module) + if module.params['action'] in ['add', 'remove']: + switch_in_compliance(module, result['switchInfo']) + switch_configlet = server_configurable_configlet(module, + result['switchInfo']) + if not switch_configlet: + module.fail_json(msg=str('Switch %s has no configurable server' + ' ports.' % module.params['switch_name'])) + result['switchConfigurable'] = True + if not port_configurable(module, switch_configlet): + module.fail_json(msg=str('Port %s is not configurable as a server' + ' port on switch %s.' + % (module.params['switch_port'], + module.params['switch_name']))) + result['portConfigurable'] = True + result['taskCreated'] = False + result['taskExecuted'] = False + result['taskCompleted'] = False + result.update(configlet_action(module, switch_configlet)) + if module.params['auto_run'] and module.params['action'] != 'show': + task_id = configlet_update_task(module) + if task_id: + result['taskId'] = task_id + note = ('Update config on %s with %s action from Ansible.' + % (module.params['switch_name'], + module.params['action'])) + module.client.api.add_note_to_task(task_id, note) + module.client.api.execute_task(task_id) + result['taskExecuted'] = True + task_completed = wait_for_task_completion(module, task_id) + if task_completed: + result['taskCompleted'] = True + else: + result['taskCreated'] = False + except CvpApiError as e: + module.fail_json(msg=str(e)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_etherstub.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_etherstub.py new file mode 100644 index 00000000..c8c7591a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_etherstub.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_etherstub +short_description: Manage etherstubs on Solaris/illumos systems. +description: + - Create or delete etherstubs on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - Etherstub name. + required: true + temporary: + description: + - Specifies that the etherstub is temporary. Temporary etherstubs + do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create or delete Solaris/illumos etherstub. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create 'stub0' etherstub + community.network.dladm_etherstub: + name: stub0 + state: present + +- name: Remove 'stub0 etherstub + community.network.dladm_etherstub: + name: stub0 + state: absent +''' + +RETURN = ''' +name: + description: etherstub name + returned: always + type: str + sample: "switch0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: etherstub's persistence + returned: always + type: bool + sample: "True" +''' +from ansible.module_utils.basic import AnsibleModule + + +class Etherstub(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def etherstub_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-etherstub') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + etherstub = Etherstub(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = etherstub.name + result['state'] = etherstub.state + result['temporary'] = etherstub.temporary + + if etherstub.state == 'absent': + if etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.delete_etherstub() + if rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + elif etherstub.state == 'present': + if not etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.create_etherstub() + + if rc is not None and rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_iptun.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_iptun.py new file mode 100644 index 00000000..2b2d3175 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_iptun.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_iptun +short_description: Manage IP tunnel interfaces on Solaris/illumos systems. +description: + - Manage IP tunnel interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - IP tunnel interface name. + required: true + temporary: + description: + - Specifies that the IP tunnel interface is temporary. Temporary IP tunnel + interfaces do not persist across reboots. + required: false + default: false + type: bool + type: + description: + - Specifies the type of tunnel to be created. + required: false + default: "ipv4" + choices: [ "ipv4", "ipv6", "6to4" ] + aliases: ['tunnel_type'] + local_address: + description: + - Literal IP address or hostname corresponding to the tunnel source. + required: false + aliases: [ "local" ] + remote_address: + description: + - Literal IP address or hostname corresponding to the tunnel destination. + required: false + aliases: [ "remote" ] + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create IPv4 tunnel interface 'iptun0' + community.network.dladm_iptun: name=iptun0 local_address=192.0.2.23 remote_address=203.0.113.10 state=present + +- name: Change IPv4 tunnel remote address + community.network.dladm_iptun: name=iptun0 type=ipv4 local_address=192.0.2.23 remote_address=203.0.113.11 + +- name: Create IPv6 tunnel interface 'tun0' + community.network.dladm_iptun: name=tun0 type=ipv6 local_address=192.0.2.23 remote_address=203.0.113.42 + +- name: Remove 'iptun0' tunnel interface + community.network.dladm_iptun: name=iptun0 state=absent +''' + +RETURN = ''' +name: + description: tunnel interface name + returned: always + type: str + sample: iptun0 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +local_address: + description: local IP address + returned: always + type: str + sample: 1.1.1.1/32 +remote_address: + description: remote IP address + returned: always + type: str + sample: 2.2.2.2/32 +type: + description: tunnel type + returned: always + type: str + sample: ipv4 +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TYPES = ['ipv4', 'ipv6', '6to4'] + + +class IPTun(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.type = module.params['type'] + self.local_address = module.params['local_address'] + self.remote_address = module.params['remote_address'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self.dladm_bin = self.module.get_bin_path('dladm', True) + + def iptun_exists(self): + cmd = [self.dladm_bin] + + cmd.append('show-iptun') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('create-iptun') + + if self.temporary: + cmd.append('-t') + + cmd.append('-T') + cmd.append(self.type) + cmd.append('-a') + cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('delete-iptun') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def update_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('modify-iptun') + + if self.temporary: + cmd.append('-t') + cmd.append('-a') + cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def _query_iptun_props(self): + cmd = [self.dladm_bin] + + cmd.append('show-iptun') + cmd.append('-p') + cmd.append('-c') + cmd.append('link,type,flags,local,remote') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def iptun_needs_updating(self): + (rc, out, err) = self._query_iptun_props() + + NEEDS_UPDATING = False + + if rc == 0: + configured_local, configured_remote = out.split(':')[3:] + + if self.local_address != configured_local or self.remote_address != configured_remote: + NEEDS_UPDATING = True + + return NEEDS_UPDATING + else: + self.module.fail_json(msg='Failed to query tunnel interface %s properties' % self.name, + err=err, + rc=rc) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, type='str'), + type=dict(default='ipv4', type='str', aliases=['tunnel_type'], + choices=SUPPORTED_TYPES), + local_address=dict(type='str', aliases=['local']), + remote_address=dict(type='str', aliases=['remote']), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + required_if=[ + ['state', 'present', ['local_address', 'remote_address']], + ], + supports_check_mode=True + ) + + iptun = IPTun(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = iptun.name + result['type'] = iptun.type + result['local_address'] = iptun.local_address + result['remote_address'] = iptun.remote_address + result['state'] = iptun.state + result['temporary'] = iptun.temporary + + if iptun.state == 'absent': + if iptun.iptun_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = iptun.delete_iptun() + if rc != 0: + module.fail_json(name=iptun.name, msg=err, rc=rc) + elif iptun.state == 'present': + if not iptun.iptun_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = iptun.create_iptun() + + if rc is not None and rc != 0: + module.fail_json(name=iptun.name, msg=err, rc=rc) + else: + if iptun.iptun_needs_updating(): + (rc, out, err) = iptun.update_iptun() + if rc != 0: + module.fail_json(msg='Error while updating tunnel interface: "%s"' % err, + name=iptun.name, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_linkprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_linkprop.py new file mode 100644 index 00000000..25247a69 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_linkprop.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_linkprop +short_description: Manage link properties on Solaris/illumos systems. +description: + - Set / reset link properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + link: + description: + - Link interface name. + required: true + aliases: [ "nic", "interface" ] + property: + description: + - Specifies the name of the property we want to manage. + required: true + aliases: [ "name" ] + value: + description: + - Specifies the value we want to set for the link property. + required: false + temporary: + description: + - Specifies that lin property configuration is temporary. Temporary + link property configuration does not persist across reboots. + required: false + type: bool + default: false + state: + description: + - Set or reset the property value. + required: false + default: "present" + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Set 'maxbw' to 100M on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=maxbw value=100M state=present + +- name: Set 'mtu' to 9000 on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=mtu value=9000 + +- name: Reset 'mtu' property on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=mtu state=reset +''' + +RETURN = ''' +property: + description: property name + returned: always + type: str + sample: mtu +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +link: + description: link name + returned: always + type: str + sample: e100g0 +value: + description: property value + returned: always + type: str + sample: 9000 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class LinkProp(object): + + def __init__(self, module): + self.module = module + + self.link = module.params['link'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self.dladm_bin = self.module.get_bin_path('dladm', True) + + def property_exists(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" on link %s' % + (self.property, self.link), + property=self.property, + link=self.link) + + def property_is_modified(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('value,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_readonly(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('perm') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and out == 'r-': + return True + else: + return False + + def property_is_set(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('value') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.dladm_bin] + + cmd.append('set-linkprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + '=' + self.value) + cmd.append(self.link) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.dladm_bin] + + cmd.append('reset-linkprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + link=dict(required=True, default=None, type='str', aliases=['nic', 'interface']), + property=dict(required=True, type='str', aliases=['name']), + value=dict(required=False, type='str'), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + required_if=[ + ['state', 'present', ['value']], + ], + + supports_check_mode=True + ) + + linkprop = LinkProp(module) + + rc = None + out = '' + err = '' + result = {} + result['property'] = linkprop.property + result['link'] = linkprop.link + result['state'] = linkprop.state + if linkprop.value: + result['value'] = linkprop.value + + if linkprop.state == 'absent' or linkprop.state == 'reset': + if linkprop.property_exists(): + if not linkprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = linkprop.reset_property() + if rc != 0: + module.fail_json(property=linkprop.property, + link=linkprop.link, + msg=err, + rc=rc) + + elif linkprop.state == 'present': + if linkprop.property_exists(): + if not linkprop.property_is_readonly(): + if not linkprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = linkprop.set_property() + if rc != 0: + module.fail_json(property=linkprop.property, + link=linkprop.link, + msg=err, + rc=rc) + else: + module.fail_json(msg='Property "%s" is read-only!' % (linkprop.property), + property=linkprop.property, + link=linkprop.link) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vlan.py new file mode 100644 index 00000000..8ed48633 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vlan.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_vlan +short_description: Manage VLAN interfaces on Solaris/illumos systems. +description: + - Create or delete VLAN interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - VLAN interface name. + required: true + link: + description: + - VLAN underlying link name. + required: true + temporary: + description: + - Specifies that the VLAN interface is temporary. Temporary VLANs + do not persist across reboots. + required: false + default: false + type: bool + vlan_id: + description: + - VLAN ID value for VLAN interface. + required: false + default: false + aliases: [ "vid" ] + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create 'vlan42' VLAN over 'bnx0' link + community.network.dladm_vlan: name=vlan42 link=bnx0 vlan_id=42 state=present + +- name: Remove 'vlan1337' VLAN interface + community.network.dladm_vlan: name=vlan1337 state=absent +''' + +RETURN = ''' +name: + description: VLAN name + returned: always + type: str + sample: vlan42 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +link: + description: VLAN's underlying link name + returned: always + type: str + sample: e100g0 +vlan_id: + description: VLAN ID + returned: always + type: str + sample: 42 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class VLAN(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.vlan_id = module.params['vlan_id'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def vlan_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-vlan') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_vlan(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-vlan') + + if self.temporary: + cmd.append('-t') + + cmd.append('-l') + cmd.append(self.link) + cmd.append('-v') + cmd.append(self.vlan_id) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_vlan(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-vlan') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def is_valid_vlan_id(self): + + return 0 <= int(self.vlan_id) <= 4095 + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, type='str'), + link=dict(default=None, type='str'), + vlan_id=dict(default=0, aliases=['vid']), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + required_if=[ + ['state', 'present', ['vlan_id', 'link', 'name']], + ], + supports_check_mode=True + ) + + vlan = VLAN(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = vlan.name + result['link'] = vlan.link + result['state'] = vlan.state + result['temporary'] = vlan.temporary + + if int(vlan.vlan_id) != 0: + if not vlan.is_valid_vlan_id(): + module.fail_json(msg='Invalid VLAN id value', + name=vlan.name, + state=vlan.state, + link=vlan.link, + vlan_id=vlan.vlan_id) + result['vlan_id'] = vlan.vlan_id + + if vlan.state == 'absent': + if vlan.vlan_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vlan.delete_vlan() + if rc != 0: + module.fail_json(name=vlan.name, msg=err, rc=rc) + elif vlan.state == 'present': + if not vlan.vlan_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vlan.create_vlan() + + if rc is not None and rc != 0: + module.fail_json(name=vlan.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vnic.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vnic.py new file mode 100644 index 00000000..c7e30c5d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/dladm_vnic.py @@ -0,0 +1,265 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_vnic +short_description: Manage VNICs on Solaris/illumos systems. +description: + - Create or delete VNICs on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - VNIC name. + required: true + type: str + link: + description: + - VNIC underlying link name. + required: true + type: str + temporary: + description: + - Specifies that the VNIC is temporary. Temporary VNICs + do not persist across reboots. + required: false + default: false + type: bool + mac: + description: + - Sets the VNIC's MAC address. Must be valid unicast MAC address. + required: false + default: false + aliases: [ "macaddr" ] + type: str + vlan: + description: + - Enable VLAN tagging for this VNIC. The VLAN tag will have id + I(vlan). + required: false + default: false + aliases: [ "vlan_id" ] + type: int + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] + type: str +''' + +EXAMPLES = ''' +- name: Create 'vnic0' VNIC over 'bnx0' link + community.network.dladm_vnic: + name: vnic0 + link: bnx0 + state: present + +- name: Create VNIC with specified MAC and VLAN tag over 'aggr0' + community.network.dladm_vnic: + name: vnic1 + link: aggr0 + mac: '00:00:5E:00:53:23' + vlan: 4 + +- name: Remove 'vnic0' VNIC + community.network.dladm_vnic: + name: vnic0 + link: bnx0 + state: absent +''' + +RETURN = ''' +name: + description: VNIC name + returned: always + type: str + sample: "vnic0" +link: + description: VNIC underlying link name + returned: always + type: str + sample: "igb0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: VNIC's persistence + returned: always + type: bool + sample: "True" +mac: + description: MAC address to use for VNIC + returned: if mac is specified + type: str + sample: "00:00:5E:00:53:42" +vlan: + description: VLAN to use for VNIC + returned: success + type: int + sample: 42 +''' + +import re + +from ansible.module_utils.basic import AnsibleModule + + +class VNIC(object): + + UNICAST_MAC_REGEX = r'^[a-f0-9][2-9a-f0]:([a-f0-9]{2}:){4}[a-f0-9]{2}$' + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.mac = module.params['mac'] + self.vlan = module.params['vlan'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def vnic_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-vnic') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-vnic') + + if self.temporary: + cmd.append('-t') + + if self.mac: + cmd.append('-m') + cmd.append(self.mac) + + if self.vlan: + cmd.append('-v') + cmd.append(self.vlan) + + cmd.append('-l') + cmd.append(self.link) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-vnic') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def is_valid_unicast_mac(self): + + mac_re = re.match(self.UNICAST_MAC_REGEX, self.mac) + + return mac_re is not None + + def is_valid_vlan_id(self): + + return 0 < self.vlan < 4095 + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + link=dict(type='str', required=True), + mac=dict(type='str', aliases=['macaddr']), + vlan=dict(type='int', aliases=['vlan_id']), + temporary=dict(type='bool', default=False), + state=dict(type='str', default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + vnic = VNIC(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = vnic.name + result['link'] = vnic.link + result['state'] = vnic.state + result['temporary'] = vnic.temporary + + if vnic.mac is not None: + if not vnic.is_valid_unicast_mac(): + module.fail_json(msg='Invalid unicast MAC address', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['mac'] = vnic.mac + + if vnic.vlan is not None: + if not vnic.is_valid_vlan_id(): + module.fail_json(msg='Invalid VLAN tag', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['vlan'] = vnic.vlan + + if vnic.state == 'absent': + if vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.delete_vnic() + if rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + elif vnic.state == 'present': + if not vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.create_vnic() + + if rc is not None and rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_command.py new file mode 100644 index 00000000..cc4a73b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_command.py @@ -0,0 +1,172 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +module: edgeos_command +author: + - Chad Norgan (@beardymcbeards) + - Sam Doran (@samdoran) +short_description: Run one or more commands on EdgeOS devices +description: + - This command module allows running one or more commands on a remote + device running EdgeOS, such as the Ubiquiti EdgeRouter. + - This module does not support running commands in configuration mode. + - Certain C(show) commands in EdgeOS produce many lines of output and + use a custom pager that can cause this module to hang. If the + value of the environment variable C(ANSIBLE_EDGEOS_TERMINAL_LENGTH) + is not set, the default number of 10000 is used. + - "This is a network module and requires C(connection: network_cli) + in order to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). +options: + commands: + description: + - The commands or ordered set of commands that should be run against the + remote device. The output of the command is returned to the playbook. + If the C(wait_for) argument is provided, the module is not returned + until the condition is met or the number of retries is exceeded. + required: True + wait_for: + description: + - Causes the task to wait for a specific condition to be met before + moving forward. If the condition is not met before the specified + number of retries is exceeded, the task will fail. + required: False + match: + description: + - Used in conjunction with C(wait_for) to create match policy. If set to + C(all), then all conditions in C(wait_for) must be met. If set to + C(any), then only one condition must match. + required: False + default: 'all' + choices: ['any', 'all'] + retries: + description: + - Number of times a command should be tried before it is considered failed. + The command is run on the target device and evaluated against the + C(wait_for) conditionals. + required: False + default: 10 + interval: + description: + - The number of seconds to wait between C(retries) of the command. + required: False + default: 1 + +notes: + - Tested against EdgeOS 1.9.7 + - Running C(show system boot-messages all) will cause the module to hang since + EdgeOS is using a custom pager setting to display the output of that command. +''' + +EXAMPLES = """ +tasks: + - name: Reboot the device + community.network.edgeos_command: + commands: reboot now + + - name: Show the configuration for eth0 and eth1 + community.network.edgeos_command: + commands: show interfaces ethernet {{ item }} + loop: + - eth0 + - eth1 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +""" +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands, to_lines +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import run_commands + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + if module.check_mode: + for item in list(commands): + if not item['command'].startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not ' + 'executing %s' % item['command'] + ) + commands.remove(item) + + return commands + + +def main(): + spec = dict( + commands=dict(type='list', required=True), + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + + warnings = list() + result = {'changed': False, 'warnings': warnings} + commands = parse_commands(module, warnings) + wait_for = module.params['wait_for'] or list() + + try: + conditionals = [Conditional(c) for c in wait_for] + except AttributeError as exc: + module.fail_json(msg=to_text(exc)) + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)), + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_config.py new file mode 100644 index 00000000..0bcb1cb3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_config.py @@ -0,0 +1,314 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeos_config +author: + - "Nathaniel Case (@Qalthos)" + - "Sam Doran (@samdoran)" +short_description: Manage EdgeOS configuration on remote device +description: + - This module provides configuration file management of EdgeOS + devices. It provides arguments for managing both the + configuration file and state of the active configuration. All + configuration statements are based on `set` and `delete` commands + in the device configuration. + - "This is a network module and requires the C(connection: network_cli) in order + to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). +notes: + - Tested against EdgeOS 1.9.7 + - Setting C(ANSIBLE_PERSISTENT_COMMAND_TIMEOUT) to 30 is recommended since + the save command can take longer than the default of 10 seconds on + some EdgeOS hardware. +options: + lines: + description: + - The ordered set of configuration lines to be managed and + compared with the existing configuration on the remote + device. + src: + description: + - The C(src) argument specifies the path to the source config + file to load. The source config file can either be in + bracket format or set format. The source file can include + Jinja2 template variables. + match: + description: + - The C(match) argument controls the method used to match + against the current active configuration. By default, the + desired config is matched against the active config and the + deltas are loaded. If the C(match) argument is set to C(none) + the active configuration is ignored and the configuration is + always loaded. + default: line + choices: ['line', 'none'] + backup: + description: + - The C(backup) argument will backup the current device's active + configuration to the Ansible control host prior to making any + changes. If the C(backup_options) value is not given, the backup + file will be located in the backup folder in the playbook root + directory or role root directory if the playbook is part of an + ansible role. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by edgeos_config' + config: + description: + - The C(config) argument specifies the base configuration to use + to compare against the desired configuration. If this value + is not specified, the module will automatically retrieve the + current active configuration from the remote device. + save: + description: + - The C(save) argument controls whether or not changes made + to the active configuration are saved to disk. This is + independent of committing the config. When set to C(True), the + active configuration is saved. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure the remote device + community.network.edgeos_config: + lines: + - set system host-name {{ inventory_hostname }} + - set service lldp + - delete service dhcp-server + +- name: Backup and load from file + community.network.edgeos_config: + src: edgeos.cfg + backup: yes + +- name: Configurable backup path + community.network.edgeos_config: + src: edgeos.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The list of configuration commands sent to the device + returned: always + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/edgeos_config.2016-07-16@22:28:34 +""" + +import re + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import load_config, get_config, run_commands + + +DEFAULT_COMMENT = 'configured by edgeos_config' + + +def config_to_commands(config): + set_format = config.startswith('set') or config.startswith('delete') + candidate = NetworkConfig(indent=4, contents=config) + if not set_format: + candidate = [c.line for c in candidate.items] + commands = list() + # this filters out less specific lines + for item in candidate: + for index, entry in enumerate(commands): + if item.startswith(entry): + del commands[index] + break + commands.append(item) + + commands = ['set %s' % cmd.replace(' {', '') for cmd in commands] + + else: + commands = to_native(candidate).split('\n') + + return commands + + +def get_candidate(module): + contents = module.params['src'] or module.params['lines'] + + if module.params['lines']: + contents = '\n'.join(contents) + + return config_to_commands(contents) + + +def check_command(module, command): + """Tests against a command line to be valid otherwise raise errors + + Error on uneven single quote which breaks ansible waiting for further input. Ansible + will handle even single quote failures correctly. + + :param command: the command line from current or new config + :type command: string + :raises ValueError: + * if contains odd number of single quotes + :return: command string unchanged + :rtype: string + """ + if command.count("'") % 2 != 0: + module.fail_json(msg="Unmatched single (') quote found in command: " + command) + + return command + + +def diff_config(module, commands, config): + config = [to_native(check_command(module, c)) for c in config.splitlines()] + + updates = list() + visited = set() + delete_commands = [line for line in commands if line.startswith('delete')] + + for line in commands: + item = to_native(check_command(module, line)) + + if not item.startswith('set') and not item.startswith('delete'): + raise ValueError('line must start with either `set` or `delete`') + + elif item.startswith('set'): + + if item not in config: + updates.append(line) + + # If there is a corresponding delete command in the desired config, make sure to append + # the set command even though it already exists in the running config + else: + ditem = re.sub('set', 'delete', item) + for line in delete_commands: + if ditem.startswith(line): + updates.append(item) + + elif item.startswith('delete'): + if not config: + updates.append(line) + else: + item = re.sub(r'delete', 'set', item) + for entry in config: + if entry.startswith(item) and line not in visited: + updates.append(line) + visited.add(line) + + return list(updates) + + +def run(module, result): + # get the current active config from the node or passed in via + # the config param + config = module.params['config'] or get_config(module) + + # create the candidate config object from the arguments + candidate = get_candidate(module) + + # create loadable config that includes only the configuration updates + commands = diff_config(module, candidate, config) + + result['commands'] = commands + + commit = not module.check_mode + comment = module.params['comment'] + + if commands: + load_config(module, commands, commit=commit, comment=comment) + + result['changed'] = True + + +def main(): + + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + spec = dict( + src=dict(type='path'), + lines=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + comment=dict(default=DEFAULT_COMMENT), + + config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + mutually_exclusive = [('lines', 'src')] + + module = AnsibleModule( + argument_spec=spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True + ) + + warnings = list() + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module=module) + + if any((module.params['src'], module.params['lines'])): + run(module, result) + + if module.params['save']: + diff = run_commands(module, commands=['configure', 'compare saved'])[1] + if diff != '[edit]': + if not module.check_mode: + run_commands(module, commands=['save']) + result['changed'] = True + run_commands(module, commands=['exit']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_facts.py new file mode 100644 index 00000000..e82af2ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeos_facts.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeos_facts +author: + - Nathaniel Case (@Qalthos) + - Sam Doran (@samdoran) +short_description: Collect facts from remote devices running EdgeOS +description: + - Collects a base set of device facts from a remote device that + is running EdgeOS. This module prepends all of the + base network fact keys with U(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against EdgeOS 1.9.7 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, default, config, and neighbors. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: "!config" +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.edgeos_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.edgeos_facts: + gather_subset: config + +- name: Collect everything exception the config + community.network.edgeos_facts: + gather_subset: "!config" +""" + +RETURN = """ +ansible_net_config: + description: The running-config from the device + returned: when config is configured + type: str +ansible_net_commits: + description: The set of available configuration revisions + returned: when present + type: list +ansible_net_hostname: + description: The configured system hostname + returned: always + type: str +ansible_net_model: + description: The device model string + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the device + returned: always + type: str +ansible_net_version: + description: The version of the software running + returned: always + type: str +ansible_net_neighbors: + description: The set of LLDP neighbors + returned: when interface is configured + type: list +ansible_net_gather_subset: + description: The list of subsets gathered by the module + returned: always + type: list +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import run_commands + + +class FactsBase(object): + + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS)) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show host name', + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + + self.facts['hostname'] = self.responses[1] + + def parse_version(self, data): + match = re.search(r'Version:\s*v(\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'HW model:\s*([A-Za-z0-9- ]+)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'HW S/N:\s+(\S+)', data) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = [ + 'show configuration commands|cat', + 'show system commit', + ] + + def populate(self): + super(Config, self).populate() + + self.facts['config'] = self.responses + + commits = self.responses[1] + entries = list() + entry = None + + for line in commits.split('\n'): + match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line) + if match: + if entry: + entries.append(entry) + + entry = dict(revision=match.group(1), + datetime=match.group(2), + by=str(match.group(3)).strip(), + via=str(match.group(4)).strip(), + comment=None) + elif entry: + entry['comment'] = line.strip() + + self.facts['commits'] = entries + + +class Neighbors(FactsBase): + + COMMANDS = [ + 'show lldp neighbors', + 'show lldp neighbors detail', + ] + + def populate(self): + super(Neighbors, self).populate() + + all_neighbors = self.responses[0] + if 'LLDP not configured' not in all_neighbors: + neighbors = self.parse( + self.responses[1] + ) + self.facts['neighbors'] = self.parse_neighbors(neighbors) + + def parse(self, data): + parsed = list() + values = None + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + values += '\n%s' % line + elif line.startswith('Interface'): + if values: + parsed.append(values) + values = line + if values: + parsed.append(values) + return parsed + + def parse_neighbors(self, data): + facts = dict() + for item in data: + interface = self.parse_interface(item) + host = self.parse_host(item) + port = self.parse_port(item) + if interface not in facts: + facts[interface] = list() + facts[interface].append(dict(host=host, port=port)) + return facts + + def parse_interface(self, data): + match = re.search(r'^Interface:\s+(\S+),', data) + return match.group(1) + + def parse_host(self, data): + match = re.search(r'SysName:\s+(.+)$', data, re.M) + if match: + return match.group(1) + + def parse_port(self, data): + match = re.search(r'PortDescr:\s+(.+)$', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + neighbors=Neighbors, + config=Config +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=spec, + supports_check_mode=True) + + warnings = list() + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Subset must be one of [%s], got %s' % + (', '.join(VALID_SUBSETS), subset)) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_facts.py new file mode 100644 index 00000000..e7b3e9cf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_facts.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeswitch_facts +author: "Frederic Bor (@f-bor)" +short_description: Collect facts from remote devices running Edgeswitch +description: + - Collects a base set of device facts from a remote device that + is running Ubiquiti Edgeswitch. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against Edgeswitch 1.7.4 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.edgeswitch_facts: + gather_subset: all + +- name: Collect only the running config and default facts + community.network.edgeswitch_facts: + gather_subset: + - config + +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# config +ansible_net_startupconfig: + description: The startup config from the device + returned: when config is configured + type: str + version_added: 1.2.0 + +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show sysinfo'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(self.responses[1]) + + def parse_version(self, data): + match = re.search(r'Software Version\.+ (.*)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'System Name\.+ (.*)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'Machine Model\.+ (.*)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial Number\.+ (.*)', data) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class StartupConfig(FactsBase): + + COMMANDS = ['show startup-config'] + + def populate(self): + super(StartupConfig, self).populate() + data = self.responses[0] + if data: + self.facts['startupconfig'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces description', + 'show interfaces status all' + ] + + def populate(self): + super(Interfaces, self).populate() + + interfaces = {} + + data = self.responses[0] + self.parse_interfaces_description(data, interfaces) + + data = self.responses[1] + self.parse_interfaces_status(data, interfaces) + + self.facts['interfaces'] = interfaces + + def parse_interfaces_description(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)\s+(\w+)\s+(\w+)', line) + if match: + name = match.group(1) + interface = {} + interface['operstatus'] = match.group(2) + interface['lineprotocol'] = match.group(3) + interface['description'] = line[30:] + interfaces[name] = interface + + def parse_interfaces_status(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)', line) + if match: + name = match.group(1) + interface = interfaces[name] + interface['physicalstatus'] = line[61:71].strip() + interface['mediatype'] = line[73:91].strip() + + +FACT_SUBSETS = dict( + default=Default, + config=Config, + startupconfig=StartupConfig, + interfaces=Interfaces, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_vlan.py new file mode 100644 index 00000000..4520309f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/edgeswitch_vlan.py @@ -0,0 +1,493 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeswitch_vlan +author: "Frederic Bor (@f-bor)" +short_description: Manage VLANs on Ubiquiti Edgeswitch network devices +description: + - This module provides declarative management of VLANs + on Ubiquiti Edgeswitch network devices. +notes: + - Tested against edgeswitch 1.7.4 + - This module use native Ubiquiti vlan syntax and does not support switchport compatibility syntax. + For clarity, it is strongly advised to not use both syntaxes on the same interface. + - Edgeswitch does not support deleting or changing name of VLAN 1 + - As auto_tag, auto_untag and auto_exclude are a kind of default setting for all interfaces, they are mutually exclusive + +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4093. + tagged_interfaces: + description: + - List of interfaces that should accept and transmit tagged frames for the VLAN. + Accept range of interfaces. + untagged_interfaces: + description: + - List of interfaces that should accept untagged frames and transmit them tagged for the VLAN. + Accept range of interfaces. + excluded_interfaces: + description: + - List of interfaces that should be excluded of the VLAN. + Accept range of interfaces. + auto_tag: + description: + - Each of the switch interfaces will be set to accept and transmit + untagged frames for I(vlan_id) unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + auto_untag: + description: + - Each of the switch interfaces will be set to accept untagged frames and + transmit them tagged for I(vlan_id) unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + auto_exclude: + description: + - Each of the switch interfaces will be excluded from I(vlan_id) + unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - action on the VLAN configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vlan + community.network.edgeswitch_vlan: + vlan_id: 100 + name: voice + action: present + +- name: Add interfaces to VLAN + community.network.edgeswitch_vlan: + vlan_id: 100 + tagged_interfaces: + - 0/1 + - 0/4-0/6 + +- name: Setup three vlans and delete the rest + community.network.edgeswitch_vlan: + purge: true + aggregate: + - { vlan_id: 1, name: default, auto_untag: true, excluded_interfaces: 0/45-0/48 } + - { vlan_id: 100, name: voice, auto_tag: true } + - { vlan_id: 200, name: video, auto_exclude: true, untagged_interfaces: 0/45-0/48, tagged_interfaces: 0/49 } + +- name: Delete vlan + community.network.edgeswitch_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan database + - vlan 100 + - vlan name 100 "test vlan" + - exit + - interface 0/1 + - vlan pvid 50 + - vlan participation include 50,100 + - vlan tagging 100 + - vlan participation exclude 200 + - no vlan tagging 200 +""" + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import build_aggregate_spec, map_params_to_obj +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch_interface import InterfaceConfiguration, merge_interfaces + + +def search_obj_in_list(vlan_id, lst): + for o in lst: + if o['vlan_id'] == vlan_id: + return o + + +def map_vlans_to_commands(want, have, module): + commands = [] + vlans_added = [] + vlans_removed = [] + vlans_names = [] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + vlans_removed.append(vlan_id) + + elif state == 'present': + if not obj_in_have: + vlans_added.append(vlan_id) + if name: + vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name)) + else: + if name: + if name != obj_in_have['name']: + vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name)) + + if module.params['purge']: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + # you can't delete vlan 1 on Edgeswitch + if not obj_in_want and h['vlan_id'] != '1': + vlans_removed.append(h['vlan_id']) + + if vlans_removed: + commands.append('no vlan {0}'.format(','.join(vlans_removed))) + + if vlans_added: + commands.append('vlan {0}'.format(','.join(vlans_added))) + + if vlans_names: + commands.extend(vlans_names) + + if commands: + commands.insert(0, 'vlan database') + commands.append('exit') + + return commands + + +class VlanInterfaceConfiguration(InterfaceConfiguration): + """ class holding vlan definitions for a given interface + """ + def __init__(self): + InterfaceConfiguration.__init__(self) + self.tagged = [] + self.untagged = [] + self.excluded = [] + + def set_vlan(self, vlan_id, type): + try: + self.tagged.remove(vlan_id) + except ValueError: + pass + + try: + self.untagged.remove(vlan_id) + except ValueError: + pass + + try: + self.excluded.remove(vlan_id) + except ValueError: + pass + + f = getattr(self, type) + f.append(vlan_id) + + def gen_commands(self, port, module): + """ to reduce commands generated by this module + we group vlans changes to have a max of 5 vlan commands by interface + """ + exclude = [] + include = [] + tag = [] + untag = [] + pvid = [] + + for vlan_id in self.excluded: + if vlan_id not in port['forbidden_vlans']: + exclude.append(vlan_id) + + if vlan_id in port['tagged_vlans']: + untag.append(vlan_id) + + for vlan_id in self.untagged: + if vlan_id in port['forbidden_vlans'] or vlan_id not in port['untagged_vlans'] and vlan_id not in port['tagged_vlans']: + include.append(vlan_id) + + if vlan_id in port['tagged_vlans']: + untag.append(vlan_id) + + if vlan_id != port['pvid_mode']: + pvid.append(vlan_id) + + for vlan_id in self.tagged: + if vlan_id not in port['tagged_vlans']: + tag.append(vlan_id) + include.append(vlan_id) + + if include: + self.commands.append('vlan participation include {0}'.format(','.join(include))) + + if pvid: + if len(pvid) > 1: + module.fail_json(msg='{0} can\'t have more than one untagged vlan') + return + self.commands.append('vlan pvid {0}'.format(pvid[0])) + + if untag: + self.commands.append('no vlan tagging {0}'.format(','.join(untag))) + + if tag: + self.commands.append('vlan tagging {0}'.format(','.join(tag))) + + if exclude: + self.commands.append('vlan participation exclude {0}'.format(','.join(exclude))) + + +def set_interfaces_vlan(interfaces_param, interfaces, vlan_id, type): + """ set vlan_id type for each interface in interfaces_param on interfaces + unrange interfaces_param if needed + """ + if interfaces_param: + for i in interfaces_param: + match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i) + if match: + group = match.group(1) + start = int(match.group(2)) + end = int(match.group(4)) + for x in range(start, end + 1): + key = '{0}/{1}'.format(group, x) + interfaces[key].set_vlan(vlan_id, type) + else: + interfaces[i].set_vlan(vlan_id, type) + + +def map_interfaces_to_commands(want, ports, module): + commands = list() + + # generate a configuration for each interface + interfaces = {} + for key, value in ports.items(): + interfaces[key] = VlanInterfaceConfiguration() + + for w in want: + state = w['state'] + if state != 'present': + continue + + auto_tag = w['auto_tag'] + auto_untag = w['auto_untag'] + auto_exclude = w['auto_exclude'] + vlan_id = w['vlan_id'] + tagged_interfaces = w['tagged_interfaces'] + untagged_interfaces = w['untagged_interfaces'] + excluded_interfaces = w['excluded_interfaces'] + + # set the default type, if any + for key, value in ports.items(): + if auto_tag: + interfaces[key].tagged.append(vlan_id) + elif auto_exclude: + interfaces[key].excluded.append(vlan_id) + elif auto_untag: + interfaces[key].untagged.append(vlan_id) + + # set explicit definitions + set_interfaces_vlan(tagged_interfaces, interfaces, vlan_id, 'tagged') + set_interfaces_vlan(untagged_interfaces, interfaces, vlan_id, 'untagged') + set_interfaces_vlan(excluded_interfaces, interfaces, vlan_id, 'excluded') + + # generate commands for each interface + for i, interface in interfaces.items(): + port = ports[i] + interface.gen_commands(port, module) + + # reduce them using range syntax when possible + interfaces = merge_interfaces(interfaces) + + # final output + for i, interface in interfaces.items(): + if len(interface.commands) > 0: + commands.append('interface {0}'.format(i)) + commands.extend(interface.commands) + + return commands + + +def parse_vlan_brief(vlan_out): + have = [] + for line in vlan_out.split('\n'): + obj = re.match(r'(?P\d+)\s+(?P[^\s]+)\s+', line) + if obj: + have.append(obj.groupdict()) + return have + + +def unrange(vlans): + res = [] + for vlan in vlans: + match = re.match(r'(\d+)-(\d+)', vlan) + if match: + start = int(match.group(1)) + end = int(match.group(2)) + for vlan_id in range(start, end + 1): + res.append(str(vlan_id)) + else: + res.append(vlan) + return res + + +def parse_interfaces_switchport(cmd_out): + ports = dict() + objs = re.findall( + r'Port: (\d+\/\d+)\n' + 'VLAN Membership Mode:(.*)\n' + 'Access Mode VLAN:(.*)\n' + 'General Mode PVID:(.*)\n' + 'General Mode Ingress Filtering:(.*)\n' + 'General Mode Acceptable Frame Type:(.*)\n' + 'General Mode Dynamically Added VLANs:(.*)\n' + 'General Mode Untagged VLANs:(.*)\n' + 'General Mode Tagged VLANs:(.*)\n' + 'General Mode Forbidden VLANs:(.*)\n', cmd_out) + for o in objs: + port = { + 'interface': o[0], + 'pvid_mode': o[3].replace("(default)", "").strip(), + 'untagged_vlans': unrange(o[7].strip().split(',')), + 'tagged_vlans': unrange(o[8].strip().split(',')), + 'forbidden_vlans': unrange(o[9].strip().split(',')) + } + ports[port['interface']] = port + return ports + + +def map_ports_to_obj(module): + return parse_interfaces_switchport(run_commands(module, ['show interfaces switchport'])[0]) + + +def map_config_to_obj(module): + return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0]) + + +def check_params(module, want): + """ Deeper checks on parameters + """ + def check_parmams_interface(interfaces): + if interfaces: + for i in interfaces: + match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i) + if match: + if match.group(1) != match.group(3): + module.fail_json(msg="interface range must be within same group: " + i) + else: + match = re.search(r'(\d+)\/(\d+)', i) + if not match: + module.fail_json(msg="wrong interface format: " + i) + + for w in want: + auto_tag = w['auto_tag'] + auto_untag = w['auto_untag'] + auto_exclude = w['auto_exclude'] + + c = 0 + if auto_tag: + c = c + 1 + + if auto_untag: + c = c + 1 + + if auto_exclude: + c = c + 1 + + if c > 1: + module.fail_json(msg="parameters are mutually exclusive: auto_tag, auto_untag, auto_exclude") + return + + check_parmams_interface(w['tagged_interfaces']) + check_parmams_interface(w['untagged_interfaces']) + check_parmams_interface(w['excluded_interfaces']) + w['vlan_id'] = str(w['vlan_id']) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + tagged_interfaces=dict(type='list'), + untagged_interfaces=dict(type='list'), + excluded_interfaces=dict(type='list'), + auto_tag=dict(type='bool'), + auto_exclude=dict(type='bool'), + auto_untag=dict(type='bool'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + argument_spec = build_aggregate_spec( + element_spec, + ['vlan_id'], + dict(purge=dict(default=False, type='bool')) + ) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [ + ['vlan_id', 'aggregate'], + ['auto_tag', 'auto_untag', 'auto_exclude']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + result = {'changed': False} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + check_params(module, want) + + # vlans are not created/deleted in configure mode + commands = map_vlans_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + run_commands(module, commands, check_rc=False) + result['changed'] = True + + ports = map_ports_to_obj(module) + + # interfaces vlan are set in configure mode + commands = map_interfaces_to_commands(want, ports, module) + result['commands'].extend(commands) + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_command.py new file mode 100644 index 00000000..61fcf1a7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_command.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute ENOS Commands on Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Run arbitrary commands on Lenovo ENOS devices +description: + - Sends arbitrary commands to an ENOS node and returns the results + read from the device. The C(enos_command) module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +extends_documentation_fragment: +- community.network.enos + +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + port: 22 + username: admin + password: admin + timeout: 30 + +--- +- name: Test contains operator + community.network.enos_command: + commands: + - show version + - show system memory + wait_for: + - "result[0] contains 'Lenovo'" + - "result[1] contains 'MemFree'" + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for single command + community.network.enos_command: + commands: ['show version'] + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for multiple commands + community.network.enos_command: + commands: + - show version + - show interface information + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + - "result.stdout | length == 2" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import run_commands, check_args +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import enos_argument_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + spec.update(enos_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_config.py new file mode 100644 index 00000000..86a8497d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_config.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to configure Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_config +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Lenovo ENOS configuration sections +description: + - Lenovo ENOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with ENOS configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.enos + +notes: + - Tested against ENOS 8.4.1 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is + mutually exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block', 'config'] + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by enos_config' + admin: + description: + - Enters into administration configuration mode for making config + changes to the device. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.enos_config: + "lines: hostname {{ inventory_hostname }}" + +- name: Configure interface settings + community.network.enos_config: + lines: + - enable + - ip ospf enable + parents: interface ip 13 + +- name: Load a config from disk and replace the current config + community.network.enos_config: + src: config.cfg + backup: yes + +- name: Configurable backup path + community.network.enos_config: + src: config.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/enos01.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import load_config, get_config +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import enos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +DEFAULT_COMMIT_COMMENT = 'configured by enos_config' + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + replace_config = replace == 'config' + path = module.params['parents'] + comment = module.params['comment'] + admin = module.params['admin'] + check_mode = module.check_mode + + candidate = get_candidate(module) + + if match != 'none' and replace != 'config': + contents = get_running_config(module) + configobj = NetworkConfig(contents=contents, indent=1) + commands = candidate.difference(configobj, path=path, match=match, + replace=replace) + else: + commands = candidate.items + + if commands: + commands = dumps(commands, 'commands').split('\n') + + if any((module.params['lines'], module.params['src'])): + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + + diff = load_config(module, commands) + if diff: + result['diff'] = dict(prepared=diff) + result['changed'] = True + + +def main(): + """main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block', 'config']), + + config=dict(), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + comment=dict(default=DEFAULT_COMMIT_COMMENT), + admin=dict(type='bool', default=False) + ) + + argument_spec.update(enos_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('replace', 'config', ['src'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_facts.py new file mode 100644 index 00000000..e5f972ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/enos_facts.py @@ -0,0 +1,503 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to Collect facts from Lenovo Switches running Lenovo ENOS commands +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_facts +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect facts from remote devices running Lenovo ENOS +description: + - Collects a base set of device facts from a remote Lenovo device + running on ENOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +extends_documentation_fragment: +- community.network.enos + +notes: + - Tested against ENOS 8.4.1 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module enos_facts. +--- +- name: Test Enos Facts + community.network.enos_facts: + provider={{ cli }} + + vars: + cli: + host: "{{ inventory_hostname }}" + port: 22 + username: admin + password: admin + transport: cli + timeout: 30 + authorize: True + auth_pass: + +--- +# Collect all facts from the device +- community.network.enos_facts: + gather_subset: all + provider: "{{ cli }}" + +# Collect only the config and default facts +- community.network.enos_facts: + gather_subset: + - config + provider: "{{ cli }}" + +# Do not collect hardware facts +- community.network.enos_facts: + gather_subset: + - "!hardware" + provider: "{{ cli }}" + +''' +RETURN = ''' + ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +# default + ansible_net_model: + description: The model name returned from the Lenovo ENOS device + returned: always + type: str + ansible_net_serialnum: + description: The serial number of the Lenovo ENOS device + returned: always + type: str + ansible_net_version: + description: The ENOS operating system version running on the remote device + returned: always + type: str + ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + ansible_net_image: + description: Indicates the active image for the device + returned: always + type: str +# hardware + ansible_net_memfree_mb: + description: The available free memory on the remote device in MB + returned: when hardware is configured + type: int +# config + ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces + ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_interfaces: + description: A hash of all interfaces running on the system. + This gives information on description, mac address, mtu, speed, + duplex and operstatus + returned: when interfaces is configured + type: dict + ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +''' + +import re + +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import run_commands, enos_argument_spec, check_args +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self.PERSISTENT_COMMAND_TIMEOUT = 60 + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, + check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show run'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + data_run = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + if data_run: + self.facts['hostname'] = self.parse_hostname(data_run) + + def parse_version(self, data): + match = re.search(r'^Software Version (.*?) ', data, re.M | re.I) + if match: + return match.group(1) + + def parse_hostname(self, data_run): + for line in data_run.split('\n'): + line = line.strip() + match = re.match(r'hostname (.*?)', line, re.M | re.I) + if match: + hosts = line.split() + hostname = hosts[1].strip('\"') + return hostname + return "NA" + + def parse_model(self, data): + match = re.search(r'^Lenovo RackSwitch (\S+)', data, re.M | re.I) + if match: + return match.group(1) + + def parse_image(self, data): + match = re.search(r'(.*) image1(.*)', data, re.M | re.I) + if match: + return "Image1" + else: + return "Image2" + + def parse_serialnum(self, data): + match = re.search(r'^Switch Serial No: (\S+)', data, re.M | re.I) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show system memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.run(['show system memory']) + data = to_text(data, errors='surrogate_or_strict').strip() + data = data.replace(r"\n", "\n") + if data: + self.facts['memtotal_mb'] = self.parse_memtotal(data) + self.facts['memfree_mb'] = self.parse_memfree(data) + + def parse_memtotal(self, data): + match = re.search(r'^MemTotal:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + def parse_memfree(self, data): + match = re.search(r'^MemFree:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interface status'] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data1 = self.run(['show interface status']) + data1 = to_text(data1, errors='surrogate_or_strict').strip() + data1 = data1.replace(r"\n", "\n") + data2 = self.run(['show lldp port']) + data2 = to_text(data2, errors='surrogate_or_strict').strip() + data2 = data2.replace(r"\n", "\n") + lines1 = None + lines2 = None + if data1: + lines1 = self.parse_interfaces(data1) + if data2: + lines2 = self.parse_interfaces(data2) + if lines1 is not None and lines2 is not None: + self.facts['interfaces'] = self.populate_interfaces(lines1, lines2) + data3 = self.run(['show lldp remote-device port']) + data3 = to_text(data3, errors='surrogate_or_strict').strip() + data3 = data3.replace(r"\n", "\n") + + lines3 = None + if data3: + lines3 = self.parse_neighbors(data3) + if lines3 is not None: + self.facts['neighbors'] = self.populate_neighbors(lines3) + + data4 = self.run(['show interface ip']) + data4 = data4[0].split('\n') + lines4 = None + if data4: + lines4 = self.parse_ipaddresses(data4) + ipv4_interfaces = self.set_ipv4_interfaces(lines4) + self.facts['all_ipv4_addresses'] = ipv4_interfaces + ipv6_interfaces = self.set_ipv6_interfaces(lines4) + self.facts['all_ipv6_addresses'] = ipv6_interfaces + + def parse_ipaddresses(self, data4): + parsed = list() + for line in data4: + if len(line) == 0: + continue + else: + line = line.strip() + if len(line) == 0: + continue + match = re.search(r'IP4', line, re.M | re.I) + if match: + key = match.group() + parsed.append(line) + match = re.search(r'IP6', line, re.M | re.I) + if match: + key = match.group() + parsed.append(line) + return parsed + + def set_ipv4_interfaces(self, line4): + ipv4_addresses = list() + for line in line4: + ipv4Split = line.split() + if ipv4Split[1] == "IP4": + ipv4_addresses.append(ipv4Split[2]) + return ipv4_addresses + + def set_ipv6_interfaces(self, line4): + ipv6_addresses = list() + for line in line4: + ipv6Split = line.split() + if ipv6Split[1] == "IP6": + ipv6_addresses.append(ipv6Split[2]) + return ipv6_addresses + + def populate_neighbors(self, lines3): + neighbors = dict() + for line in lines3: + neighborSplit = line.split("|") + innerData = dict() + innerData['Remote Chassis ID'] = neighborSplit[2].strip() + innerData['Remote Port'] = neighborSplit[3].strip() + sysName = neighborSplit[4].strip() + if sysName is not None: + innerData['Remote System Name'] = neighborSplit[4].strip() + else: + innerData['Remote System Name'] = "NA" + neighbors[neighborSplit[0].strip()] = innerData + return neighbors + + def populate_interfaces(self, lines1, lines2): + interfaces = dict() + for line1, line2 in zip(lines1, lines2): + line = line1 + " " + line2 + intfSplit = line.split() + innerData = dict() + innerData['description'] = intfSplit[6].strip() + innerData['macaddress'] = intfSplit[8].strip() + innerData['mtu'] = intfSplit[9].strip() + innerData['speed'] = intfSplit[1].strip() + innerData['duplex'] = intfSplit[2].strip() + innerData['operstatus'] = intfSplit[5].strip() + if("up" not in intfSplit[5].strip()) and ("down" not in intfSplit[5].strip()): + innerData['description'] = intfSplit[7].strip() + innerData['macaddress'] = intfSplit[9].strip() + innerData['mtu'] = intfSplit[10].strip() + innerData['operstatus'] = intfSplit[6].strip() + interfaces[intfSplit[0].strip()] = innerData + return interfaces + + def parse_neighbors(self, neighbors): + parsed = list() + for line in neighbors.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^([0-9]+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(INT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(EXT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(MGT+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def parse_interfaces(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^([0-9]+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(INT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(EXT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(MGT+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +PERSISTENT_COMMAND_TIMEOUT = 60 + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + argument_spec.update(enos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + check_args(module, warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/eric_eccli_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/eric_eccli_command.py new file mode 100644 index 00000000..40266b3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/eric_eccli_command.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# Copyright (c) 2019 Ericsson AB. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: eric_eccli_command +author: Ericsson IPOS OAM team (@itercheng) +short_description: Run commands on remote devices running ERICSSON ECCLI +description: + - Sends arbitrary commands to an ERICSSON eccli node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module also support running commands in configuration mode + in raw command style. +options: + commands: + description: + - List of commands to send to the remote ECCLI device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. If a command sent to the + device requires answering a prompt, it is possible to pass + a dict containing I(command), I(answer) and I(prompt). + Common answers are 'y' or "\\r" (carriage return, must be + double quotes). See examples. + type: list + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + type: str + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + type: int + default: 1 +notes: + - Tested against IPOS 19.3 + - For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide ` + - For more information on using Ansible to manage Ericsson devices see the Ericsson documents. + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(ERIC_ECCLI Platform Options guide,../network/user_guide/platform_eric_eccli.html). +""" + +EXAMPLES = r""" +tasks: + - name: Run show version on remote devices + community.network.eric_eccli_command: + commands: show version + + - name: Run show version and check to see if output contains IPOS + community.network.eric_eccli_command: + commands: show version + wait_for: result[0] contains IPOS + + - name: Run multiple commands on remote nodes + community.network.eric_eccli_command: + commands: + - show version + - show running-config interfaces + + - name: Run multiple commands and evaluate the output + community.network.eric_eccli_command: + commands: + - show version + - show running-config interfaces + wait_for: + - result[0] contains IPOS + - result[1] contains management +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.eric_eccli.eric_eccli import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + for item in list(commands): + if module.check_mode: + if item['command'].startswith('conf'): + warnings.append( + 'only non-config commands are supported when using check mode, not ' + 'executing %s' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list() + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_command.py new file mode 100644 index 00000000..eeea2f8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_command.py @@ -0,0 +1,215 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: exos_command +author: "Rafael D. Vencioneck (@rdvencioneck)" +short_description: Run commands on remote devices running Extreme EXOS +description: + - Sends arbitrary commands to an Extreme EXOS device and returns the results + read from the device. This module includes an argument that will cause the + module to wait for a specific condition before returning or timing out if + the condition is not met. + - This module does not support running configuration commands. + Please use M(community.network.exos_config) to configure EXOS devices. +notes: + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote EXOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.exos_command: + commands: show version + - name: Run show version and check to see if output contains ExtremeXOS + community.network.exos_command: + commands: show version + wait_for: result[0] contains ExtremeXOS + - name: Run multiple commands on remote nodes + community.network.exos_command: + commands: + - show version + - show ports no-refresh + - name: Run multiple commands and evaluate the output + community.network.exos_command: + commands: + - show version + - show ports no-refresh + wait_for: + - result[0] contains ExtremeXOS + - result[1] contains 20 + - name: Run command that requires answering a prompt + community.network.exos_command: + commands: + - command: 'clear license-info' + prompt: 'Are you sure.*' + answer: 'Yes' +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + command_split = re.match(r'^(\w*)(.*)$', item['command']) + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + elif command_split and command_split.group(1) not in ('check', 'clear', 'debug', 'history', + 'ls', 'mrinfo', 'mtrace', 'nslookup', + 'ping', 'rtlookup', 'show', 'traceroute'): + module.fail_json( + msg='some commands were not recognized. exos_command can only run read-only' + 'commands. For configuration commands, please use exos_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not be satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_config.py new file mode 100644 index 00000000..77e5f585 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_config.py @@ -0,0 +1,431 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: exos_config +author: "Lance Richardson (@hlrichardson)" +short_description: Manage Extreme Networks EXOS configuration sections +description: + - Extreme EXOS configurations use a simple flat text file syntax. + This module provides an implementation for working with EXOS + configuration lines in a deterministic way. +notes: + - Tested against EXOS version 22.6.0b19 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that behavior. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config. If the argument is set to I(changed), then the running-config + will only be copied to the startup-config if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + default: running + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure SNMP system name + community.network.exos_config: + lines: configure snmp sysName "{{ inventory_hostname }}" + +- name: Configure interface settings + community.network.exos_config: + lines: + - configure ports 2 description-string "Master Uplink" + backup: yes + +- name: Check the running-config against master config + community.network.exos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.exos_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: Save running to startup when modified + community.network.exos_config: + save_when: modified + +- name: Configurable backup path + community.network.exos_config: + lines: + - configure ports 2 description-string "Master Uplink" + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['create vlan "foo"', 'configure snmp sysName "x620-red"'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/x870_config.2018-08-08@15:00:21 + +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import run_commands, get_config, load_config, get_diff +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +__metaclass__ = type + + +def get_running_config(module, current_config=None, flags=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module, flags=flags) + return contents + + +def get_startup_config(module, flags=None): + reply = run_commands(module, {'command': 'show switch', 'output': 'text'}) + match = re.search(r'Config Selected: +(\S+)\.cfg', to_text(reply, errors='surrogate_or_strict').strip(), re.MULTILINE) + if match: + cfgname = match.group(1).strip() + command = ' '.join(['debug cfgmgr show configuration file', cfgname]) + if flags: + command += ' '.join(to_list(flags)).strip() + reply = run_commands(module, {'command': command, 'output': 'text'}) + data = reply[0] + else: + data = '' + return data + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + candidate.add(module.params['lines']) + candidate = dumps(candidate, 'raw') + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "save configuration", + "prompt": "Do you want to save configuration", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `save configuration` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running'], default='running'), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + if warnings: + result['warnings'] = warnings + + config = None + flags = ['detail'] if module.params['defaults'] else [] + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + running = get_running_config(module, config) + + try: + response = get_diff(module, candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response.get('config_diff') + + if config_diff: + commands = config_diff.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + running = get_running_config(module) + startup = get_startup_config(module) + + running_config = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=startup, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + contents = get_running_config(module) + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + contents = get_startup_config(module) + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_facts.py new file mode 100644 index 00000000..174463dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_facts.py @@ -0,0 +1,184 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: exos_facts +author: + - "Lance Richardson (@hlrichardson)" + - "Ujwal Koamrla (@ujwalkomarla)" +short_description: Collect facts from devices running Extreme EXOS +description: + - Collects a base set of device facts from a remote device that + is running EXOS. This module prepends all of the base network + fact keys with C(ansible_net_). The facts module will + always collect a base set of facts from the device and can + enable or disable collection of additional facts. +notes: + - Tested against EXOS 22.5.1.7 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + type: list + default: ['!config'] + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like interfaces, vlans etc. + Can specify a list of values to include a larger subset. + Values can also be used with an initial C(!) to specify that + a specific subset should not be collected. + Valid subsets are 'all', 'lldp_global'. + type: list +''' + +EXAMPLES = """ + - name: Gather all legacy facts + community.network.exos_facts: + gather_subset: all + + - name: Gather only the config and default facts + community.network.exos_facts: + gather_subset: config + + - name: Do not gather hardware facts + community.network.exos_facts: + gather_subset: "!hardware" + + - name: Gather legacy and resource facts + community.network.exos_facts: + gather_subset: all + gather_network_resources: all + + - name: Gather only the lldp global resource facts and no legacy facts + community.network.exos_facts: + gather_subset: + - '!all' + - '!min' + gather_network_resource: + - lldp_global + + - name: Gather lldp global resource and minimal legacy facts + community.network.exos_facts: + gather_subset: min + gather_network_resource: lldp_global +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +ansible_net_gather_network_resources: + description: The list of fact for network resource subsets collected from the device + returned: when the resource is configured + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.facts.facts import FactsArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts + + +def main(): + """Main entry point for AnsibleModule + """ + argument_spec = FactsArgs.argument_spec + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = ['default value for `gather_subset` ' + 'will be changed to `min` from `!config` in community.network 2.0.0 onwards'] + + result = Facts(module).get_facts() + + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_l2_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_l2_interfaces.py new file mode 100644 index 00000000..2fc82fef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_l2_interfaces.py @@ -0,0 +1,1131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for exos_l2_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_l2_interfaces +version_added: '0.2.0' +short_description: Manage L2 interfaces on Extreme Networks EXOS devices. +description: This module provides declarative management of L2 interfaces on Extreme Networks EXOS network devices. +author: Jayalakshmi Viswanathan (@jayalakshmiV) +notes: + - Tested against EXOS 30.2.1.8 + - This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of L2 interfaces options + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface + type: str + required: True + access: + description: + - Switchport mode access command to configure the interface as a layer 2 access. + type: dict + suboptions: + vlan: + description: + - Configure given VLAN in access port. It's used as the access VLAN ID. + type: int + trunk: + description: + - Switchport mode trunk command to configure the interface as a Layer 2 trunk. + type: dict + suboptions: + native_vlan: + description: + - Native VLAN to be configured in trunk port. It's used as the trunk native VLAN ID. + type: int + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. These are the only VLANs that will be configured on the trunk. + type: list + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Delete L2 interface configuration for the given arguments + community.network.exos_l2_interfaces: + config: + - name: '3' + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using deleted without any config passed +#"(NOTE: This will delete all of configured resource module attributes from each configured interface)" + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Delete L2 interface configuration for the given arguments + community.network.exos_l2_interfaces: + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using merged + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 10 + name: '1' + - name: '2' + trunk: + trunk_allowed_vlans: 10 + - name: '3' + trunk: + native_vlan: 10 + trunk_allowed_vlans: 20 + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 10, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "trunk-vlans": [10], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "native-vlan": 10, +# "trunk-vlans": [20], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20 +# ] +# } +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20 +# ] +# } +# } +# } +# }, +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Overrride device configuration of all L2 interfaces with provided configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 10 + name: '2' + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 10, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 10 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 20 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Replace device configuration of listed L2 interfaces with provided configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 20 + name: '1' + - name: '2' + trunk: + trunk_allowed_vlans: 10 + - name: '3' + trunk: + native_vlan: 10 + trunk_allowed_vlan: 20,30 + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 20 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 20, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "trunk-vlans": [10], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "native-vlan": 10, +# "trunk-vlans": [20, 30] +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 20 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": null, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 20 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.l2_interfaces.l2_interfaces import L2_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config', )), + ('state', 'replaced', ('config', ))] + module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = L2_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_global.py new file mode 100644 index 00000000..94c6f1ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_global.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for exos_lldp_global +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_lldp_global +short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on EXOS platforms. +description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on Extreme Networks EXOS platforms. +author: Ujwal Komarla (@ujwalkomarla) +notes: +- Tested against Extreme Networks EXOS version 30.2.1.8 on x460g2. +- This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of LLDP options + type: dict + suboptions: + interval: + description: + - Frequency at which LLDP advertisements are sent (in seconds). By default - 30 seconds. + type: int + default: 30 + tlv_select: + description: + - This attribute can be used to specify the TLVs that need to be sent in the LLDP packets. By default, only system name and system description is sent + type: dict + suboptions: + management_address: + description: + - Used to specify the management address in TLV messages + type: bool + port_description: + description: + - Used to specify the port description TLV + type: bool + system_capabilities: + description: + - Used to specify the system capabilities TLV + type: bool + system_description: + description: + - Used to specify the system description TLV + type: bool + default: true + system_name: + description: + - Used to specify the system name TLV + type: bool + default: true + + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - deleted + default: merged +''' +EXAMPLES = """ +# Using merged + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Merge provided LLDP configuration with device configuration + community.network.exos_lldp_global: + config: + interval: 10000 + tlv_select: + system_capabilities: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": false, +# "management_address": false, +# "system_capabilities": true +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +# Using replaced + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Replace device configuration with provided LLDP configuration + community.network.exos_lldp_global: + config: + interval: 10000 + tlv_select: + system_capabilities: true + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_NAME", +# "SYSTEM_DESCRIPTION", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": false, +# "system_description": false, +# "port_description": false, +# "management_address": false, +# "system_capabilities": true +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_NAME", +# "SYSTEM_DESCRIPTION", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +# Using deleted + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Delete attributes of given LLDP service (This won't delete the LLDP service itself) + community.network.exos_lldp_global: + config: + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": true, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The configuration as structured data after module completion. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_global.lldp_global import Lldp_globalArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.lldp_global.lldp_global import Lldp_global + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',))] + module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = Lldp_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_interfaces.py new file mode 100644 index 00000000..01a7a760 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_lldp_interfaces.py @@ -0,0 +1,674 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for exos_lldp_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_lldp_interfaces +version_added: '0.2.0' +short_description: Manage link layer discovery protocol (LLDP) attributes of interfaces on EXOS platforms. +description: + - This module manages link layer discovery protocol (LLDP) attributes of interfaces on Extreme Networks EXOS platforms. +author: Jayalakshmi Viswanathan (@JayalakshmiV) +options: + config: + description: The list of link layer discovery protocol interface attribute configurations + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface LLDP needs to be configured on. + type: str + required: True + enabled: + description: + - This is a boolean value to control disabling of LLDP on the interface C(name) + type: bool + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_lldp_interfaces: + config: + - name: '2' + enabled: false + - name: '5' + enabled: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": false, +# "name": "2" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=2/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "5" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=5/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: False +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: True + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "5" +# } +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Replaces device configuration of listed lldp_interfaces with provided configuration + community.network.exos_lldp_interfaces: + config: + - name: '1' + enabled: false + - name: '3' + enabled: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": false, +# "name": "1" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=1/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: False +# - name: '2' +# enabled: True +# - name: '3' +# enabled: True +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": false, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + + +# Using deleted + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": false, +# "name": "1" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# }, +# } +# ] +# } +# } + +- name: Delete lldp interface configuration (this will not delete other lldp configuration) + community.network.exos_lldp_interfaces: + config: + - name: '1' + - name: '3' + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: False +# - name: '2' +# enabled: False +# - name: '3' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "1" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=1/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: False +# - name: '3' +# enabled: True +# +# After state: +# ------------- +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# }, +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# }, +# } +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Override device configuration of all lldp_interfaces with provided configuration + community.network.exos_lldp_interfaces: + config: + - name: '3' + enabled: true + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "5" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=5/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: True +# - name: '4' +# enabled: True +# - name: '5' +# enabled: True + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "5" +# } +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.lldp_interfaces.lldp_interfaces import Lldp_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config', )), + ('state', 'replaced', ('config', ))] + module = AnsibleModule(argument_spec=Lldp_interfacesArgs.argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = Lldp_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_vlans.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_vlans.py new file mode 100644 index 00000000..276ba92d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/exos_vlans.py @@ -0,0 +1,753 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for exos_vlans +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_vlans +version_added: '0.2.0' +short_description: Manage VLANs on Extreme Networks EXOS devices. +description: This module provides declarative management of VLANs on Extreme Networks EXOS network devices. +author: Jayalakshmi Viswanathan (@jayalakshmiV) +notes: + - Tested against EXOS 30.2.1.8 + - This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of VLANs options + type: list + elements: dict + suboptions: + name: + description: + - Ascii name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094 + type: int + required: True + state: + description: + - Operational state of the VLAN + type: str + choices: + - active + - suspend + default: active + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Delete attributes of given VLANs + community.network.exos_vlans: + config: + - vlan_id: 10 + - vlan_id: 20 + - vlan_id: 30 + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# } +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=10" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=20" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=30" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# } +# ] +# } +# } + + +# Using merged + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# } +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_vlans: + config: + - name: vlan_10 + vlan_id: 10 + state: active + - name: vlan_20 + vlan_id: 20 + state: active + - name: vlan_30 + vlan_id: 30 + state: active + state: merged + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# { +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Override device configuration of all VLANs with provided configuration + community.network.exos_vlans: + config: + - name: TEST_VLAN10 + vlan_id: 10 + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "TEST_VLAN10", +# "state": "active", +# "vlan_id": 10 +# }, +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": { +# "vlan": [ +# { +# "config": { +# "name": "TEST_VLAN10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# } +# } +# ] +# } +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=20" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=30" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Replaces device configuration of listed VLANs with provided configuration + community.network.exos_vlans: + config: + - name: Test_VLAN20 + vlan_id: 20 + - name: Test_VLAN30 + vlan_id: 30 + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "TEST_VLAN20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "TEST_VLAN30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": { +# "vlan": [ +# { +# "config": { +# "name": "TEST_VLAN20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# } +# "config": { +# "name": "TEST_VLAN30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# } +# } +# ] +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.vlans.vlans import VlansArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.vlans.vlans import Vlans + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',))] + module = AnsibleModule(argument_spec=VlansArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = Vlans(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/faz_device.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/faz_device.py new file mode 100644 index 00000000..b6dff85c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/faz_device.py @@ -0,0 +1,432 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: faz_device +author: Luke Weighall (@lweighall) +short_description: Add or remove device +description: + - Add or remove a device or list of devices to FortiAnalyzer Device Manager. ADOM Capable. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + default: root + type: str + + mode: + description: + - Add or delete devices. Or promote unregistered devices that are in the FortiAnalyzer "waiting pool" + required: false + default: add + choices: ["add", "delete", "promote"] + type: str + + device_username: + description: + - The username of the device being added to FortiAnalyzer. + required: false + type: str + + device_password: + description: + - The password of the device being added to FortiAnalyzer. + required: false + type: str + + device_ip: + description: + - The IP of the device being added to FortiAnalyzer. + required: false + type: str + + device_unique_name: + description: + - The desired "friendly" name of the device being added to FortiAnalyzer. + required: false + type: str + + device_serial: + description: + - The serial number of the device being added to FortiAnalyzer. + required: false + type: str + + os_type: + description: + - The os type of the device being added (default 0). + required: true + choices: ["unknown", "fos", "fsw", "foc", "fml", "faz", "fwb", "fch", "fct", "log", "fmg", "fsa", "fdd", "fac"] + type: str + + mgmt_mode: + description: + - Management Mode of the device you are adding. + choices: ["unreg", "fmg", "faz", "fmgfaz"] + required: true + type: str + + os_minor_vers: + description: + - Minor OS rev of the device. + required: true + type: str + + os_ver: + description: + - Major OS rev of the device + required: true + choices: ["unknown", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0"] + type: str + + platform_str: + description: + - Required for determine the platform for VM platforms. ie FortiGate-VM64 + required: false + type: str + + faz_quota: + description: + - Specifies the quota for the device in FAZ + required: False + type: str +''' + +EXAMPLES = ''' +- name: DISCOVER AND ADD DEVICE A PHYSICAL FORTIGATE + community.network.faz_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.201" + device_unique_name: "FGT1" + device_serial: "FGVM000000117994" + state: "present" + mgmt_mode: "faz" + os_type: "fos" + os_ver: "5.0" + minor_rev: 6 + + +- name: DISCOVER AND ADD DEVICE A VIRTUAL FORTIGATE + community.network.faz_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.202" + device_unique_name: "FGT2" + mgmt_mode: "faz" + os_type: "fos" + os_ver: "5.0" + minor_rev: 6 + state: "present" + platform_str: "FortiGate-VM64" + +- name: DELETE DEVICE FGT01 + community.network.faz_device: + adom: "root" + device_unique_name: "ansible-fgt01" + mode: "delete" + +- name: DELETE DEVICE FGT02 + community.network.faz_device: + adom: "root" + device_unique_name: "ansible-fgt02" + mode: "delete" + +- name: PROMOTE FGT01 IN FAZ BY IP + community.network.faz_device: + adom: "root" + device_password: "fortinet" + device_ip: "10.7.220.151" + device_username: "ansible" + mgmt_mode: "faz" + mode: "promote" + + +- name: PROMOTE FGT02 IN FAZ + community.network.faz_device: + adom: "root" + device_password: "fortinet" + device_unique_name: "ansible-fgt02" + device_username: "ansible" + mgmt_mode: "faz" + mode: "promote" + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.fortianalyzer import FortiAnalyzerHandler +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZBaseException +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZCommon +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZMethods +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import DEFAULT_RESULT_OBJ +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAIL_SOCKET_MSG + + +def faz_add_device(faz, paramgram): + """ + This method is used to add devices to the faz or delete them + """ + + datagram = { + "adom": paramgram["adom"], + "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"], + "ip": paramgram["ip"], "name": paramgram["device_unique_name"], + "mgmt_mode": paramgram["mgmt_mode"], "os_type": paramgram["os_type"], + "mr": paramgram["os_minor_vers"]} + } + + if paramgram["platform_str"] is not None: + datagram["device"]["platform_str"] = paramgram["platform_str"] + + if paramgram["sn"] is not None: + datagram["device"]["sn"] = paramgram["sn"] + + if paramgram["device_action"] is not None: + datagram["device"]["device_action"] = paramgram["device_action"] + + if paramgram["faz.quota"] is not None: + datagram["device"]["faz.quota"] = paramgram["faz.quota"] + + url = '/dvm/cmd/add/device/' + response = faz.process_request(url, datagram, FAZMethods.EXEC) + return response + + +def faz_delete_device(faz, paramgram): + """ + This method deletes a device from the FAZ + """ + datagram = { + "adom": paramgram["adom"], + "device": paramgram["device_unique_name"], + } + + url = '/dvm/cmd/del/device/' + response = faz.process_request(url, datagram, FAZMethods.EXEC) + return response + + +def faz_get_unknown_devices(faz): + """ + This method gets devices with an unknown management type field + """ + + faz_filter = ["mgmt_mode", "==", "0"] + + datagram = { + "filter": faz_filter + } + + url = "/dvmdb/device" + response = faz.process_request(url, datagram, FAZMethods.GET) + + return response + + +def faz_approve_unregistered_device_by_ip(faz, paramgram): + """ + This method approves unregistered devices by ip. + """ + # TRY TO FIND DETAILS ON THIS UNREGISTERED DEVICE + unknown_devices = faz_get_unknown_devices(faz) + target_device = None + if unknown_devices[0] == 0: + for device in unknown_devices[1]: + if device["ip"] == paramgram["ip"]: + target_device = device + else: + return "No devices are waiting to be registered!" + + # now that we have the target device details...fill out the datagram and make the call to promote it + if target_device is not None: + target_device_paramgram = { + "adom": paramgram["adom"], + "ip": target_device["ip"], + "device_username": paramgram["device_username"], + "device_password": paramgram["device_password"], + "device_unique_name": paramgram["device_unique_name"], + "sn": target_device["sn"], + "os_type": target_device["os_type"], + "mgmt_mode": paramgram["mgmt_mode"], + "os_minor_vers": target_device["mr"], + "os_ver": target_device["os_ver"], + "platform_str": target_device["platform_str"], + "faz.quota": target_device["faz.quota"], + "device_action": paramgram["device_action"] + } + + add_device = faz_add_device(faz, target_device_paramgram) + return add_device + + return str("Couldn't find the desired device with ip: " + str(paramgram["device_ip"])) + + +def faz_approve_unregistered_device_by_name(faz, paramgram): + # TRY TO FIND DETAILS ON THIS UNREGISTERED DEVICE + unknown_devices = faz_get_unknown_devices(faz) + target_device = None + if unknown_devices[0] == 0: + for device in unknown_devices[1]: + if device["name"] == paramgram["device_unique_name"]: + target_device = device + else: + return "No devices are waiting to be registered!" + + # now that we have the target device details...fill out the datagram and make the call to promote it + if target_device is not None: + target_device_paramgram = { + "adom": paramgram["adom"], + "ip": target_device["ip"], + "device_username": paramgram["device_username"], + "device_password": paramgram["device_password"], + "device_unique_name": paramgram["device_unique_name"], + "sn": target_device["sn"], + "os_type": target_device["os_type"], + "mgmt_mode": paramgram["mgmt_mode"], + "os_minor_vers": target_device["mr"], + "os_ver": target_device["os_ver"], + "platform_str": target_device["platform_str"], + "faz.quota": target_device["faz.quota"], + "device_action": paramgram["device_action"] + } + + add_device = faz_add_device(faz, target_device_paramgram) + return add_device + + return str("Couldn't find the desired device with name: " + str(paramgram["device_unique_name"])) + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "delete", "promote"], type="str", default="add"), + + device_ip=dict(required=False, type="str"), + device_username=dict(required=False, type="str"), + device_password=dict(required=False, type="str", no_log=True), + device_unique_name=dict(required=False, type="str"), + device_serial=dict(required=False, type="str"), + + os_type=dict(required=False, type="str", choices=["unknown", "fos", "fsw", "foc", "fml", + "faz", "fwb", "fch", "fct", "log", "fmg", + "fsa", "fdd", "fac"]), + mgmt_mode=dict(required=False, type="str", choices=["unreg", "fmg", "faz", "fmgfaz"]), + os_minor_vers=dict(required=False, type="str"), + os_ver=dict(required=False, type="str", choices=["unknown", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0"]), + platform_str=dict(required=False, type="str"), + faz_quota=dict(required=False, type="str") + ) + + required_if = [ + ['mode', 'delete', ['device_unique_name']], + ['mode', 'add', ['device_serial', 'device_username', + 'device_password', 'device_unique_name', 'device_ip', 'mgmt_mode', 'platform_str']] + + ] + + module = AnsibleModule(argument_spec, supports_check_mode=True, required_if=required_if, ) + + # START SESSION LOGIC + paramgram = { + "adom": module.params["adom"], + "mode": module.params["mode"], + "ip": module.params["device_ip"], + "device_username": module.params["device_username"], + "device_password": module.params["device_password"], + "device_unique_name": module.params["device_unique_name"], + "sn": module.params["device_serial"], + "os_type": module.params["os_type"], + "mgmt_mode": module.params["mgmt_mode"], + "os_minor_vers": module.params["os_minor_vers"], + "os_ver": module.params["os_ver"], + "platform_str": module.params["platform_str"], + "faz.quota": module.params["faz_quota"], + "device_action": None + } + # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO + + if paramgram["mode"] == "add": + paramgram["device_action"] = "add_model" + elif paramgram["mode"] == "promote": + paramgram["device_action"] = "promote_unreg" + module.paramgram = paramgram + + # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS + faz = None + if module._socket_path: + connection = Connection(module._socket_path) + faz = FortiAnalyzerHandler(connection, module) + faz.tools = FAZCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["mode"] == "add": + results = faz_add_device(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to add the device. Error: " + str(err)) + + try: + if paramgram["mode"] == "promote": + if paramgram["ip"] is not None: + results = faz_approve_unregistered_device_by_ip(faz, paramgram) + elif paramgram["device_unique_name"] is not None: + results = faz_approve_unregistered_device_by_name(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to promote the device. Error: " + str(err)) + + try: + if paramgram["mode"] == "delete": + results = faz_delete_device(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to delete the device. Error: " + str(err)) + + # PROCESS RESULTS + try: + faz.govern_response(module=module, results=results, + ansible_facts=faz.construct_ansible_facts(results, module.params, paramgram)) + except BaseException as err: + raise FAZBaseException(msg="An error occurred with govern_response(). Error: " + str(err)) + + # This should only be hit if faz.govern_response is missed or failed somehow. In fact. It should never be hit. + # But it's here JIC. + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/flowadm.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/flowadm.py new file mode 100644 index 00000000..1dce95dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/flowadm.py @@ -0,0 +1,508 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: flowadm +short_description: Manage bandwidth resource control and priority for protocols, services and zones on Solaris/illumos systems +description: + - Create/modify/remove networking bandwidth and associated resources for a type of traffic on a particular link. +author: Adam Å tevko (@xen0l) +options: + name: + description: > + - A flow is defined as a set of attributes based on Layer 3 and Layer 4 + headers, which can be used to identify a protocol, service, or a zone. + required: true + aliases: [ 'flow' ] + link: + description: + - Specifiies a link to configure flow on. + required: false + local_ip: + description: + - Identifies a network flow by the local IP address. + required: false + remote_ip: + description: + - Identifies a network flow by the remote IP address. + required: false + transport: + description: > + - Specifies a Layer 4 protocol to be used. It is typically used in combination with I(local_port) to + identify the service that needs special attention. + required: false + local_port: + description: + - Identifies a service specified by the local port. + required: false + dsfield: + description: > + - Identifies the 8-bit differentiated services field (as defined in + RFC 2474). The optional dsfield_mask is used to state the bits of interest in + the differentiated services field when comparing with the dsfield + value. Both values must be in hexadecimal. + required: false + maxbw: + description: > + - Sets the full duplex bandwidth for the flow. The bandwidth is + specified as an integer with one of the scale suffixes(K, M, or G + for Kbps, Mbps, and Gbps). If no units are specified, the input + value will be read as Mbps. + required: false + priority: + description: + - Sets the relative priority for the flow. + required: false + default: 'medium' + choices: [ 'low', 'medium', 'high' ] + temporary: + description: + - Specifies that the configured flow is temporary. Temporary + flows do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create/delete/enable/disable an IP address on the network interface. + required: false + default: present + choices: [ 'absent', 'present', 'resetted' ] +''' + +EXAMPLES = ''' +- name: Limit SSH traffic to 100M via vnic0 interface + community.network.flowadm: + link: vnic0 + flow: ssh_out + transport: tcp + local_port: 22 + maxbw: 100M + state: present + +- name: Reset flow properties + community.network.flowadm: + name: dns + state: resetted + +- name: Configure policy for EF PHB (DSCP value of 101110 from RFC 2598) with a bandwidth of 500 Mbps and a high priority + community.network.flowadm: + link: bge0 + dsfield: '0x2e:0xfc' + maxbw: 500M + priority: high + flow: efphb-flow + state: present +''' + +RETURN = ''' +name: + description: flow name + returned: always + type: str + sample: "http_drop" +link: + description: flow's link + returned: if link is defined + type: str + sample: "vnic0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: flow's persistence + returned: always + type: bool + sample: "True" +priority: + description: flow's priority + returned: if priority is defined + type: str + sample: "low" +transport: + description: flow's transport + returned: if transport is defined + type: str + sample: "tcp" +maxbw: + description: flow's maximum bandwidth + returned: if maxbw is defined + type: str + sample: "100M" +local_Ip: + description: flow's local IP address + returned: if local_ip is defined + type: str + sample: "10.0.0.42" +local_port: + description: flow's local port + returned: if local_port is defined + type: int + sample: 1337 +remote_Ip: + description: flow's remote IP address + returned: if remote_ip is defined + type: str + sample: "10.0.0.42" +dsfield: + description: flow's differentiated services value + returned: if dsfield is defined + type: str + sample: "0x2e:0xfc" +''' + + +import socket + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TRANSPORTS = ['tcp', 'udp', 'sctp', 'icmp', 'icmpv6'] +SUPPORTED_PRIORITIES = ['low', 'medium', 'high'] + +SUPPORTED_ATTRIBUTES = ['local_ip', 'remote_ip', 'transport', 'local_port', 'dsfield'] +SUPPORTPED_PROPERTIES = ['maxbw', 'priority'] + + +class Flow(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.local_ip = module.params['local_ip'] + self.remote_ip = module.params['remote_ip'] + self.transport = module.params['transport'] + self.local_port = module.params['local_port'] + self.dsfield = module.params['dsfield'] + self.maxbw = module.params['maxbw'] + self.priority = module.params['priority'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self._needs_updating = { + 'maxbw': False, + 'priority': False, + } + + @classmethod + def is_valid_port(cls, port): + return 1 <= int(port) <= 65535 + + @classmethod + def is_valid_address(cls, ip): + + if ip.count('/') == 1: + ip_address, netmask = ip.split('/') + else: + ip_address = ip + + if len(ip_address.split('.')) == 4: + try: + socket.inet_pton(socket.AF_INET, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 32: + return False + else: + try: + socket.inet_pton(socket.AF_INET6, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 128: + return False + + return True + + @classmethod + def is_hex(cls, number): + try: + int(number, 16) + except ValueError: + return False + + return True + + @classmethod + def is_valid_dsfield(cls, dsfield): + + dsmask = None + + if dsfield.count(':') == 1: + dsval = dsfield.split(':')[0] + else: + dsval, dsmask = dsfield.split(':') + + if dsmask and not 0x01 <= int(dsmask, 16) <= 0xff and not 0x01 <= int(dsval, 16) <= 0xff: + return False + elif not 0x01 <= int(dsval, 16) <= 0xff: + return False + + return True + + def flow_exists(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flow') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def delete_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('remove-flow') + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def create_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('add-flow') + cmd.append('-l') + cmd.append(self.link) + + if self.local_ip: + cmd.append('-a') + cmd.append('local_ip=' + self.local_ip) + + if self.remote_ip: + cmd.append('-a') + cmd.append('remote_ip=' + self.remote_ip) + + if self.transport: + cmd.append('-a') + cmd.append('transport=' + self.transport) + + if self.local_port: + cmd.append('-a') + cmd.append('local_port=' + self.local_port) + + if self.dsfield: + cmd.append('-a') + cmd.append('dsfield=' + self.dsfield) + + if self.maxbw: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def _query_flow_props(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flowprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('property,possible') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def flow_needs_udpating(self): + (rc, out, err) = self._query_flow_props() + + NEEDS_UPDATING = False + + if rc == 0: + properties = (line.split(':') for line in out.rstrip().split('\n')) + for prop, value in properties: + if prop == 'maxbw' and self.maxbw != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + elif prop == 'priority' and self.priority != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + return NEEDS_UPDATING + else: + self.module.fail_json(msg='Error while checking flow properties: %s' % err, + stderr=err, + rc=rc) + + def update_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('set-flowprop') + + if self.maxbw and self._needs_updating['maxbw']: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority and self._needs_updating['priority']: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=['flow']), + link=dict(required=False), + local_ip=dict(required=False), + remote_ip=dict(required=False), + transport=dict(required=False, choices=SUPPORTED_TRANSPORTS), + local_port=dict(required=False), + dsfield=dict(required=False), + maxbw=dict(required=False), + priority=dict(required=False, + default='medium', + choices=SUPPORTED_PRIORITIES), + temporary=dict(default=False, type='bool'), + state=dict(required=False, + default='present', + choices=['absent', 'present', 'resetted']), + ), + mutually_exclusive=[ + ('local_ip', 'remote_ip'), + ('local_ip', 'transport'), + ('local_ip', 'local_port'), + ('local_ip', 'dsfield'), + ('remote_ip', 'transport'), + ('remote_ip', 'local_port'), + ('remote_ip', 'dsfield'), + ('transport', 'dsfield'), + ('local_port', 'dsfield'), + ], + supports_check_mode=True + ) + + flow = Flow(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = flow.name + result['state'] = flow.state + result['temporary'] = flow.temporary + + if flow.link: + result['link'] = flow.link + + if flow.maxbw: + result['maxbw'] = flow.maxbw + + if flow.priority: + result['priority'] = flow.priority + + if flow.local_ip: + if flow.is_valid_address(flow.local_ip): + result['local_ip'] = flow.local_ip + + if flow.remote_ip: + if flow.is_valid_address(flow.remote_ip): + result['remote_ip'] = flow.remote_ip + + if flow.transport: + result['transport'] = flow.transport + + if flow.local_port: + if flow.is_valid_port(flow.local_port): + result['local_port'] = flow.local_port + else: + module.fail_json(msg='Invalid port: %s' % flow.local_port, + rc=1) + + if flow.dsfield: + if flow.is_valid_dsfield(flow.dsfield): + result['dsfield'] = flow.dsfield + else: + module.fail_json(msg='Invalid dsfield: %s' % flow.dsfield, + rc=1) + + if flow.state == 'absent': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.delete_flow() + if rc != 0: + module.fail_json(msg='Error while deleting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'present': + if not flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.create_flow() + if rc != 0: + module.fail_json(msg='Error while creating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + else: + if flow.flow_needs_udpating(): + (rc, out, err) = flow.update_flow() + if rc != 0: + module.fail_json(msg='Error while updating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'resetted': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.reset_flow() + if rc != 0: + module.fail_json(msg='Error while resetting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device.py new file mode 100644 index 00000000..701ff882 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Add or remove device from FortiManager. +description: + - Add or remove a device or list of devices from FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + default: root + + mode: + description: + - The desired mode of the specified object. + required: false + default: add + choices: ["add", "delete"] + + blind_add: + description: + - When adding a device, module will check if it exists, and skip if it does. + - If enabled, this option will stop the module from checking if it already exists, and blindly add the device. + required: false + default: "disable" + choices: ["enable", "disable"] + + device_username: + description: + - The username of the device being added to FortiManager. + required: false + + device_password: + description: + - The password of the device being added to FortiManager. + required: false + + device_ip: + description: + - The IP of the device being added to FortiManager. Supports both IPv4 and IPv6. + required: false + + device_unique_name: + description: + - The desired "friendly" name of the device being added to FortiManager. + required: false + + device_serial: + description: + - The serial number of the device being added to FortiManager. + required: false +''' + +EXAMPLES = ''' +- name: DISCOVER AND ADD DEVICE FGT1 + community.network.fmgr_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.201" + device_unique_name: "FGT1" + device_serial: "FGVM000000117994" + mode: "add" + blind_add: "enable" + +- name: DISCOVER AND ADD DEVICE FGT2 + community.network.fmgr_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.202" + device_unique_name: "FGT2" + device_serial: "FGVM000000117992" + mode: "delete" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def discover_device(fmgr, paramgram): + """ + This method is used to discover devices before adding them to FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "odd_request_form": "True", + "device": {"adm_usr": paramgram["device_username"], + "adm_pass": paramgram["device_password"], + "ip": paramgram["device_ip"]} + } + + url = '/dvm/cmd/discover/device/' + + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def add_device(fmgr, paramgram): + """ + This method is used to add devices to the FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "adom": paramgram["adom"], + "flags": ["create_task", "nonblocking"], + "odd_request_form": "True", + "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"], + "ip": paramgram["device_ip"], "name": paramgram["device_unique_name"], + "sn": paramgram["device_serial"], "mgmt_mode": "fmgfaz", "flags": 24} + } + + url = '/dvm/cmd/add/device/' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def delete_device(fmgr, paramgram): + """ + This method deletes a device from the FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "adom": paramgram["adom"], + "flags": ["create_task", "nonblocking"], + "device": paramgram["device_unique_name"], + } + + url = '/dvm/cmd/del/device/' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def get_device(fmgr, paramgram): + """ + This method attempts to find the firewall on FortiManager to see if it already exists. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "adom": paramgram["adom"], + "filter": ["name", "==", paramgram["device_unique_name"]], + } + + url = '/dvmdb/adom/{adom}/device/{name}'.format(adom=paramgram["adom"], + name=paramgram["device_unique_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "delete"], type="str", default="add"), + blind_add=dict(choices=["enable", "disable"], type="str", default="disable"), + device_ip=dict(required=False, type="str"), + device_username=dict(required=False, type="str"), + device_password=dict(required=False, type="str", no_log=True), + device_unique_name=dict(required=True, type="str"), + device_serial=dict(required=False, type="str") + ) + + # BUILD MODULE OBJECT SO WE CAN BUILD THE PARAMGRAM + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + + # BUILD THE PARAMGRAM + paramgram = { + "device_ip": module.params["device_ip"], + "device_username": module.params["device_username"], + "device_password": module.params["device_password"], + "device_unique_name": module.params["device_unique_name"], + "device_serial": module.params["device_serial"], + "adom": module.params["adom"], + "mode": module.params["mode"] + } + + # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO + module.paramgram = paramgram + + # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["mode"] == "add": + # CHECK IF DEVICE EXISTS + if module.params["blind_add"] == "disable": + exists_results = get_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=exists_results, good_codes=(0, -3), changed=False, + ansible_facts=fmgr.construct_ansible_facts(exists_results, + module.params, paramgram)) + + discover_results = discover_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=discover_results, stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(discover_results, + module.params, paramgram)) + + if discover_results[0] == 0: + results = add_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=discover_results, stop_on_success=True, + changed_if_success=True, + ansible_facts=fmgr.construct_ansible_facts(discover_results, + module.params, paramgram)) + + if paramgram["mode"] == "delete": + results = delete_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_config.py new file mode 100644 index 00000000..f9183d9c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_config.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_config +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Edit device configurations +description: + - Edit device configurations from FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + device_unique_name: + description: + - The unique device's name that you are editing. A.K.A. Friendly name of the device in FortiManager. + required: True + + device_hostname: + description: + - The device's new hostname. + required: false + + install_config: + description: + - Tells FMGR to attempt to install the config after making it. + required: false + default: disable + + interface: + description: + - The interface/port number you are editing. + required: false + + interface_ip: + description: + - The IP and subnet of the interface/port you are editing. + required: false + + interface_allow_access: + description: + - Specify what protocols are allowed on the interface, comma-separated list (see examples). + required: false +''' + +EXAMPLES = ''' +- name: CHANGE HOSTNAME + community.network.fmgr_device_config: + device_hostname: "ChangedbyAnsible" + device_unique_name: "FGT1" + +- name: EDIT INTERFACE INFORMATION + community.network.fmgr_device_config: + adom: "root" + device_unique_name: "FGT2" + interface: "port3" + interface_ip: "10.1.1.1/24" + interface_allow_access: "ping, telnet, https" + +- name: INSTALL CONFIG + community.network.fmgr_device_config: + adom: "root" + device_unique_name: "FGT1" + install_config: "enable" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods + + +def update_device_hostname(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "hostname": paramgram["device_hostname"] + } + + url = "pm/config/device/{device_name}/global/system/global".format(device_name=paramgram["device_unique_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + return response + + +def update_device_interface(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + access_list = list() + allow_access_list = paramgram["interface_allow_access"].replace(' ', '') + access_list = allow_access_list.split(',') + + datagram = { + "allowaccess": access_list, + "ip": paramgram["interface_ip"] + } + + url = "/pm/config/device/{device_name}/global/system/interface" \ + "/{interface}".format(device_name=paramgram["device_unique_name"], interface=paramgram["interface"]) + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + return response + + +def exec_config(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "scope": { + "name": paramgram["device_unique_name"] + }, + "adom": paramgram["adom"], + "flags": "none" + } + + url = "/securityconsole/install/device" + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + device_unique_name=dict(required=True, type="str"), + device_hostname=dict(required=False, type="str"), + interface=dict(required=False, type="str"), + interface_ip=dict(required=False, type="str"), + interface_allow_access=dict(required=False, type="str"), + install_config=dict(required=False, type="str", default="disable"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "device_unique_name": module.params["device_unique_name"], + "device_hostname": module.params["device_hostname"], + "interface": module.params["interface"], + "interface_ip": module.params["interface_ip"], + "interface_allow_access": module.params["interface_allow_access"], + "install_config": module.params["install_config"], + "adom": module.params["adom"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["device_hostname"] is not None: + results = update_device_hostname(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["interface_ip"] is not None or paramgram["interface_allow_access"] is not None: + results = update_device_interface(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["install_config"] == "enable": + results = exec_config(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_group.py new file mode 100644 index 00000000..148e38dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_group.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_group +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Alter FortiManager device groups. +description: + - Add or edit device groups and assign devices to device groups FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + vdom: + description: + - The VDOM of the Fortigate you want to add, must match the device in FMGR. Usually root. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + grp_name: + description: + - The name of the device group. + required: false + + grp_desc: + description: + - The description of the device group. + required: false + + grp_members: + description: + - A comma separated list of device names or device groups to be added as members to the device group. + - If Group Members are defined, and mode="delete", only group members will be removed. + - If you want to delete a group itself, you must omit this parameter from the task in playbook. + required: false + +''' + + +EXAMPLES = ''' +- name: CREATE DEVICE GROUP + community.network.fmgr_device_group: + grp_name: "TestGroup" + grp_desc: "CreatedbyAnsible" + adom: "ansible" + mode: "add" + +- name: CREATE DEVICE GROUP 2 + community.network.fmgr_device_group: + grp_name: "AnsibleGroup" + grp_desc: "CreatedbyAnsible" + adom: "ansible" + mode: "add" + +- name: ADD DEVICES TO DEVICE GROUP + community.network.fmgr_device_group: + mode: "add" + grp_name: "TestGroup" + grp_members: "FGT1,FGT2" + adom: "ansible" + vdom: "root" + +- name: REMOVE DEVICES TO DEVICE GROUP + community.network.fmgr_device_group: + mode: "delete" + grp_name: "TestGroup" + grp_members: "FGT1,FGT2" + adom: "ansible" + +- name: DELETE DEVICE GROUP + community.network.fmgr_device_group: + grp_name: "AnsibleGroup" + grp_desc: "CreatedbyAnsible" + mode: "delete" + adom: "ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def get_groups(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "method": "get" + } + + url = '/dvmdb/adom/{adom}/group'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def add_device_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + mode = paramgram["mode"] + + datagram = { + "name": paramgram["grp_name"], + "desc": paramgram["grp_desc"], + "os_type": "fos" + } + + url = '/dvmdb/adom/{adom}/group'.format(adom=paramgram["adom"]) + + # IF MODE = SET -- USE THE 'SET' API CALL MODE + if mode == "set": + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + # IF MODE = UPDATE -- USER THE 'UPDATE' API CALL MODE + elif mode == "update": + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + # IF MODE = ADD -- USE THE 'ADD' API CALL MODE + elif mode == "add": + response = fmgr.process_request(url, datagram, FMGRMethods.ADD) + + return response + + +def delete_device_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + + datagram = { + "adom": paramgram["adom"], + "name": paramgram["grp_name"] + } + + url = '/dvmdb/adom/{adom}/group/{grp_name}'.format(adom=paramgram["adom"], grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + return response + + +def add_group_member(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + device_member_list = paramgram["grp_members"].replace(' ', '') + device_member_list = device_member_list.split(',') + + for dev_name in device_member_list: + datagram = {'name': dev_name, 'vdom': paramgram["vdom"]} + + url = '/dvmdb/adom/{adom}/group/{grp_name}/object member'.format(adom=paramgram["adom"], + grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.ADD) + + return response + + +def delete_group_member(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + device_member_list = paramgram["grp_members"].replace(' ', '') + device_member_list = device_member_list.split(',') + + for dev_name in device_member_list: + datagram = {'name': dev_name, 'vdom': paramgram["vdom"]} + + url = '/dvmdb/adom/{adom}/group/{grp_name}/object member'.format(adom=paramgram["adom"], + grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + vdom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + grp_desc=dict(required=False, type="str"), + grp_name=dict(required=True, type="str"), + grp_members=dict(required=False, type="str"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "mode": module.params["mode"], + "grp_name": module.params["grp_name"], + "grp_desc": module.params["grp_desc"], + "grp_members": module.params["grp_members"], + "adom": module.params["adom"], + "vdom": module.params["vdom"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + # PROCESS THE GROUP ADDS FIRST + if paramgram["grp_name"] is not None and paramgram["mode"] in ["add", "set", "update"]: + # add device group + results = add_device_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP MEMBER ADDS + if paramgram["grp_members"] is not None and paramgram["mode"] in ["add", "set", "update"]: + # assign devices to device group + results = add_group_member(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP MEMBER DELETES + if paramgram["grp_members"] is not None and paramgram["mode"] == "delete": + # remove devices grom a group + results = delete_group_member(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP DELETES, ONLY IF GRP_MEMBERS IS NOT NULL TOO + if paramgram["grp_name"] is not None and paramgram["mode"] == "delete" and paramgram["grp_members"] is None: + # delete device group + results = delete_device_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_provision_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_provision_template.py new file mode 100644 index 00000000..52f4323a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_device_provision_template.py @@ -0,0 +1,1546 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_provision_template +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages Device Provisioning Templates in FortiManager. +description: + - Allows the editing and assignment of device provisioning templates in FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + device_unique_name: + description: + - The unique device's name that you are editing. + required: True + + provisioning_template: + description: + - The provisioning template you want to apply (default = default). + required: True + + provision_targets: + description: + - The friendly names of devices in FortiManager to assign the provisioning template to. CSV separated list. + required: True + + snmp_status: + description: + - Enables or disables SNMP globally. + required: False + choices: ["enable", "disable"] + + snmp_v2c_query_port: + description: + - Sets the snmp v2c community query port. + required: False + + snmp_v2c_trap_port: + description: + - Sets the snmp v2c community trap port. + required: False + + snmp_v2c_status: + description: + - Enables or disables the v2c community specified. + required: False + choices: ["enable", "disable"] + + snmp_v2c_trap_status: + description: + - Enables or disables the v2c community specified for traps. + required: False + choices: ["enable", "disable"] + + snmp_v2c_query_status: + description: + - Enables or disables the v2c community specified for queries. + required: False + choices: ["enable", "disable"] + + snmp_v2c_name: + description: + - Specifies the v2c community name. + required: False + + snmp_v2c_id: + description: + - Primary key for the snmp community. this must be unique! + required: False + + snmp_v2c_trap_src_ipv4: + description: + - Source ip the traps should come from IPv4. + required: False + + snmp_v2c_trap_hosts_ipv4: + description: > + - IPv4 addresses of the hosts that should get SNMP v2c traps, comma separated, must include mask + ("10.7.220.59 255.255.255.255, 10.7.220.60 255.255.255.255"). + required: False + + snmp_v2c_query_hosts_ipv4: + description: > + - IPv4 addresses or subnets that are allowed to query SNMP v2c, comma separated + ("10.7.220.59 255.255.255.0, 10.7.220.0 255.255.255.0"). + required: False + + snmpv3_auth_proto: + description: + - SNMPv3 auth protocol. + required: False + choices: ["md5", "sha"] + + snmpv3_auth_pwd: + description: + - SNMPv3 auth pwd __ currently not encrypted! ensure this file is locked down permissions wise! + required: False + + snmpv3_name: + description: + - SNMPv3 user name. + required: False + + snmpv3_notify_hosts: + description: + - List of ipv4 hosts to send snmpv3 traps to. Comma separated IPv4 list. + required: False + + snmpv3_priv_proto: + description: + - SNMPv3 priv protocol. + required: False + choices: ["aes", "des", "aes256", "aes256cisco"] + + snmpv3_priv_pwd: + description: + - SNMPv3 priv pwd currently not encrypted! ensure this file is locked down permissions wise! + required: False + + snmpv3_queries: + description: + - Allow snmpv3_queries. + required: False + choices: ["enable", "disable"] + + snmpv3_query_port: + description: + - SNMPv3 query port. + required: False + + snmpv3_security_level: + description: + - SNMPv3 security level. + required: False + choices: ["no-auth-no-priv", "auth-no-priv", "auth-priv"] + + snmpv3_source_ip: + description: + - SNMPv3 source ipv4 address for traps. + required: False + + snmpv3_status: + description: + - SNMPv3 user is enabled or disabled. + required: False + choices: ["enable", "disable"] + + snmpv3_trap_rport: + description: + - SNMPv3 trap remote port. + required: False + + snmpv3_trap_status: + description: + - SNMPv3 traps is enabled or disabled. + required: False + choices: ["enable", "disable"] + + syslog_port: + description: + - Syslog port that will be set. + required: False + + syslog_server: + description: + - Server the syslogs will be sent to. + required: False + + syslog_status: + description: + - Enables or disables syslogs. + required: False + choices: ["enable", "disable"] + + syslog_mode: + description: + - Remote syslog logging over UDP/Reliable TCP. + - choice | udp | Enable syslogging over UDP. + - choice | legacy-reliable | Enable legacy reliable syslogging by RFC3195 (Reliable Delivery for Syslog). + - choice | reliable | Enable reliable syslogging by RFC6587 (Transmission of Syslog Messages over TCP). + required: false + choices: ["udp", "legacy-reliable", "reliable"] + default: "udp" + + syslog_filter: + description: + - Sets the logging level for syslog. + required: False + choices: ["emergency", "alert", "critical", "error", "warning", "notification", "information", "debug"] + + syslog_facility: + description: + - Remote syslog facility. + - choice | kernel | Kernel messages. + - choice | user | Random user-level messages. + - choice | mail | Mail system. + - choice | daemon | System daemons. + - choice | auth | Security/authorization messages. + - choice | syslog | Messages generated internally by syslog. + - choice | lpr | Line printer subsystem. + - choice | news | Network news subsystem. + - choice | uucp | Network news subsystem. + - choice | cron | Clock daemon. + - choice | authpriv | Security/authorization messages (private). + - choice | ftp | FTP daemon. + - choice | ntp | NTP daemon. + - choice | audit | Log audit. + - choice | alert | Log alert. + - choice | clock | Clock daemon. + - choice | local0 | Reserved for local use. + - choice | local1 | Reserved for local use. + - choice | local2 | Reserved for local use. + - choice | local3 | Reserved for local use. + - choice | local4 | Reserved for local use. + - choice | local5 | Reserved for local use. + - choice | local6 | Reserved for local use. + - choice | local7 | Reserved for local use. + required: false + choices: ["kernel", "user", "mail", "daemon", "auth", "syslog", + "lpr", "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit", + "alert", "clock", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"] + default: "syslog" + + syslog_enc_algorithm: + description: + - Enable/disable reliable syslogging with TLS encryption. + - choice | high | SSL communication with high encryption algorithms. + - choice | low | SSL communication with low encryption algorithms. + - choice | disable | Disable SSL communication. + - choice | high-medium | SSL communication with high and medium encryption algorithms. + required: false + choices: ["high", "low", "disable", "high-medium"] + default: "disable" + + syslog_certificate: + description: + - Certificate used to communicate with Syslog server if encryption on. + required: false + + ntp_status: + description: + - Enables or disables ntp. + required: False + choices: ["enable", "disable"] + + ntp_sync_interval: + description: + - Sets the interval in minutes for ntp sync. + required: False + + ntp_type: + description: + - Enables fortiguard servers or custom servers are the ntp source. + required: False + choices: ["fortiguard", "custom"] + + ntp_server: + description: + - Only used with custom ntp_type -- specifies IP of server to sync to -- comma separated ip addresses for multiples. + required: False + + ntp_auth: + description: + - Enables or disables ntp authentication. + required: False + choices: ["enable", "disable"] + + ntp_auth_pwd: + description: + - Sets the ntp auth password. + required: False + + ntp_v3: + description: + - Enables or disables ntpv3 (default is ntpv4). + required: False + choices: ["enable", "disable"] + + admin_https_redirect: + description: + - Enables or disables https redirect from http. + required: False + choices: ["enable", "disable"] + + admin_https_port: + description: + - SSL admin gui port number. + required: False + + admin_http_port: + description: + - Non-SSL admin gui port number. + required: False + + admin_timeout: + description: + - Admin timeout in minutes. + required: False + + admin_language: + description: + - Sets the admin gui language. + required: False + choices: ["english", "simch", "japanese", "korean", "spanish", "trach", "french", "portuguese"] + + admin_switch_controller: + description: + - Enables or disables the switch controller. + required: False + choices: ["enable", "disable"] + + admin_gui_theme: + description: + - Changes the admin gui theme. + required: False + choices: ["green", "red", "blue", "melongene", "mariner"] + + admin_enable_fortiguard: + description: + - Enables FortiGuard security updates to their default settings. + required: False + choices: ["none", "direct", "this-fmg"] + + admin_fortianalyzer_target: + description: + - Configures faz target. + required: False + + admin_fortiguard_target: + description: + - Configures fortiguard target. + - admin_enable_fortiguard must be set to "direct". + required: False + + smtp_username: + description: + - SMTP auth username. + required: False + + smtp_password: + description: + - SMTP password. + required: False + + smtp_port: + description: + - SMTP port number. + required: False + + smtp_replyto: + description: + - SMTP reply to address. + required: False + + smtp_conn_sec: + description: + - defines the ssl level for smtp. + required: False + choices: ["none", "starttls", "smtps"] + + smtp_server: + description: + - SMTP server ipv4 address. + required: False + + smtp_source_ipv4: + description: + - SMTP source ip address. + required: False + + smtp_validate_cert: + description: + - Enables or disables valid certificate checking for smtp. + required: False + choices: ["enable", "disable"] + + dns_suffix: + description: + - Sets the local dns domain suffix. + required: False + + dns_primary_ipv4: + description: + - primary ipv4 dns forwarder. + required: False + + dns_secondary_ipv4: + description: + - secondary ipv4 dns forwarder. + required: False + + delete_provisioning_template: + description: + - If specified, all other options are ignored. The specified provisioning template will be deleted. + required: False + +''' + + +EXAMPLES = ''' +- name: SET SNMP SYSTEM INFO + community.network.fmgr_device_provision_template: + provisioning_template: "default" + snmp_status: "enable" + mode: "set" + +- name: SET SNMP SYSTEM INFO ANSIBLE ADOM + community.network.fmgr_device_provision_template: + provisioning_template: "default" + snmp_status: "enable" + mode: "set" + adom: "ansible" + +- name: SET SNMP SYSTEM INFO different template (SNMPv2) + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + snmp_status: "enable" + mode: "set" + adom: "ansible" + snmp_v2c_query_port: "162" + snmp_v2c_trap_port: "161" + snmp_v2c_status: "enable" + snmp_v2c_trap_status: "enable" + snmp_v2c_query_status: "enable" + snmp_v2c_name: "ansibleV2c" + snmp_v2c_id: "1" + snmp_v2c_trap_src_ipv4: "10.7.220.41" + snmp_v2c_trap_hosts_ipv4: "10.7.220.59 255.255.255.255, 10.7.220.60 255.255.255.255" + snmp_v2c_query_hosts_ipv4: "10.7.220.59 255.255.255.255, 10.7.220.0 255.255.255.0" + +- name: SET SNMP SYSTEM INFO different template (SNMPv3) + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + snmp_status: "enable" + mode: "set" + adom: "ansible" + snmpv3_auth_proto: "sha" + snmpv3_auth_pwd: "fortinet" + snmpv3_name: "ansibleSNMPv3" + snmpv3_notify_hosts: "10.7.220.59,10.7.220.60" + snmpv3_priv_proto: "aes256" + snmpv3_priv_pwd: "fortinet" + snmpv3_queries: "enable" + snmpv3_query_port: "161" + snmpv3_security_level: "auth_priv" + snmpv3_source_ip: "0.0.0.0" + snmpv3_status: "enable" + snmpv3_trap_rport: "162" + snmpv3_trap_status: "enable" + +- name: SET SYSLOG INFO + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + syslog_server: "10.7.220.59" + syslog_port: "514" + syslog_mode: "disable" + syslog_status: "enable" + syslog_filter: "information" + +- name: SET NTP TO FORTIGUARD + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + ntp_status: "enable" + ntp_sync_interval: "60" + type: "fortiguard" + +- name: SET NTP TO CUSTOM SERVER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + ntp_status: "enable" + ntp_sync_interval: "60" + ntp_type: "custom" + ntp_server: "10.7.220.32,10.7.220.1" + ntp_auth: "enable" + ntp_auth_pwd: "fortinet" + ntp_v3: "disable" + +- name: SET ADMIN GLOBAL SETTINGS + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + admin_https_redirect: "enable" + admin_https_port: "4433" + admin_http_port: "8080" + admin_timeout: "30" + admin_language: "english" + admin_switch_controller: "enable" + admin_gui_theme: "blue" + admin_enable_fortiguard: "direct" + admin_fortiguard_target: "10.7.220.128" + admin_fortianalyzer_target: "10.7.220.61" + +- name: SET CUSTOM SMTP SERVER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + smtp_username: "ansible" + smtp_password: "fortinet" + smtp_port: "25" + smtp_replyto: "ansible@do-not-reply.com" + smtp_conn_sec: "starttls" + smtp_server: "10.7.220.32" + smtp_source_ipv4: "0.0.0.0" + smtp_validate_cert: "disable" + +- name: SET DNS SERVERS + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + dns_suffix: "ansible.local" + dns_primary_ipv4: "8.8.8.8" + dns_secondary_ipv4: "4.4.4.4" + +- name: SET PROVISIONING TEMPLATE DEVICE TARGETS IN FORTIMANAGER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + provision_targets: "FGT1, FGT2" + +- name: DELETE ENTIRE PROVISIONING TEMPLATE + community.network.fmgr_device_provision_template: + delete_provisioning_template: "ansibleTest" + mode: "delete" + adom: "ansible" + +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def get_devprof(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + datagram = {} + + url = "/pm/devprof/adom/{adom}/{name}".format(adom=paramgram["adom"], name=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + + return response + + +def set_devprof(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + elif paramgram["mode"] == "delete": + datagram = {} + + url = "/pm/devprof/adom/{adom}/{name}".format(adom=paramgram["adom"], + name=paramgram["delete_provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def get_devprof_scope(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + + return response + + +def set_devprof_scope(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + + targets = [] + for target in paramgram["provision_targets"].split(","): + # split the host on the space to get the mask out + new_target = {"name": target.strip()} + targets.append(new_target) + + datagram["scope member"] = targets + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + elif paramgram["mode"] == "delete": + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + "scope member": paramgram["targets_to_add"] + } + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def set_devprof_snmp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "status": paramgram["snmp_status"] + } + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp/sysinfo".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def set_devprof_snmp_v2c(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "query-v2c-port": paramgram["snmp_v2c_query_port"], + "trap-v2c-rport": paramgram["snmp_v2c_trap_port"], + "status": paramgram["snmp_v2c_status"], + "trap-v2c-status": paramgram["snmp_v2c_trap_status"], + "query-v2c-status": paramgram["snmp_v2c_query_status"], + "name": paramgram["snmp_v2c_name"], + "id": paramgram["snmp_v2c_id"], + "meta fields": dict(), + "hosts": list(), + "events": 411578417151, + "query-v1-status": 0, + "query-v1-port": 161, + "trap-v1-status": 0, + "trap-v1-lport": 162, + "trap-v1-rport": 162, + "trap-v2c-lport": 162, + } + + # BUILD THE HOST STRINGS + id_counter = 1 + if paramgram["snmp_v2c_trap_hosts_ipv4"] or paramgram["snmp_v2c_query_hosts_ipv4"]: + hosts = [] + if paramgram["snmp_v2c_query_hosts_ipv4"]: + for ipv4_host in paramgram["snmp_v2c_query_hosts_ipv4"].strip().split(","): + # split the host on the space to get the mask out + new_ipv4_host = {"ha-direct": "enable", + "host-type": "query", + "id": id_counter, + "ip": ipv4_host.strip().split(), + "meta fields": {}, + "source-ip": "0.0.0.0"} + hosts.append(new_ipv4_host) + id_counter += 1 + + if paramgram["snmp_v2c_trap_hosts_ipv4"]: + for ipv4_host in paramgram["snmp_v2c_trap_hosts_ipv4"].strip().split(","): + # split the host on the space to get the mask out + new_ipv4_host = {"ha-direct": "enable", + "host-type": "trap", + "id": id_counter, + "ip": ipv4_host.strip().split(), + "meta fields": {}, + "source-ip": paramgram["snmp_v2c_trap_src_ipv4"]} + hosts.append(new_ipv4_host) + id_counter += 1 + datagram["hosts"] = hosts + + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp/community".format(adom=adom, + provisioning_template=paramgram[ + "provisioning_template"]) + elif paramgram["mode"] == "delete": + datagram = { + "confirm": 1 + } + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "system/snmp/community/{snmp_v2c_id}".format(adom=adom, + provisioning_template=paramgram["provisioning_template"], + snmp_v2c_id=paramgram["snmp_v2c_id"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_snmp_v3(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = {} + datagram["auth-pwd"] = paramgram["snmpv3_auth_pwd"] + datagram["priv-pwd"] = paramgram["snmpv3_priv_pwd"] + datagram["trap-rport"] = paramgram["snmpv3_trap_rport"] + datagram["query-port"] = paramgram["snmpv3_query_port"] + datagram["name"] = paramgram["snmpv3_name"] + datagram["notify-hosts"] = paramgram["snmpv3_notify_hosts"].strip().split(",") + datagram["events"] = 1647387997183 + datagram["trap-lport"] = 162 + + datagram["source-ip"] = paramgram["snmpv3_source_ip"] + datagram["ha-direct"] = 0 + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "system/snmp/user".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + elif paramgram["mode"] == "delete": + datagram = { + "confirm": 1 + } + + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp" \ + "/user/{snmpv3_name}".format(adom=adom, + provisioning_template=paramgram["provisioning_template"], + snmpv3_name=paramgram["snmpv3_name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_syslog(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + + datagram = { + "status": paramgram["syslog_status"], + "port": paramgram["syslog_port"], + "server": paramgram["syslog_server"], + "mode": paramgram["syslog_mode"], + "facility": paramgram["syslog_facility"] + } + + if paramgram["mode"] in ['set', 'add', 'update']: + if paramgram["syslog_enc_algorithm"] in ["high", "low", "high-medium"] \ + and paramgram["syslog_certificate"] is not None: + datagram["certificate"] = paramgram["certificate"] + datagram["enc-algorithm"] = paramgram["syslog_enc_algorithm"] + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "log/syslogd/setting".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + elif paramgram["mode"] == "delete": + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "log/syslogd/setting".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_syslog_filter(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + datagram = { + "severity": paramgram["syslog_filter"] + } + response = DEFAULT_RESULT_OBJ + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/log/syslogd/filter".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_ntp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + + # IF SET TO FORTIGUARD, BUILD A STRING SPECIFIC TO THAT + if paramgram["ntp_type"] == "fortiguard": + datagram = {} + if paramgram["ntp_status"] == "enable": + datagram["ntpsync"] = 1 + if paramgram["ntp_status"] == "disable": + datagram["ntpsync"] = 0 + if paramgram["ntp_sync_interval"] is None: + datagram["syncinterval"] = 1 + else: + datagram["syncinterval"] = paramgram["ntp_sync_interval"] + + datagram["type"] = 0 + + # IF THE NTP TYPE IS CUSTOM BUILD THE SERVER LIST + if paramgram["ntp_type"] == "custom": + id_counter = 0 + key_counter = 0 + ntpservers = [] + datagram = {} + if paramgram["ntp_status"] == "enable": + datagram["ntpsync"] = 1 + if paramgram["ntp_status"] == "disable": + datagram["ntpsync"] = 0 + try: + datagram["syncinterval"] = paramgram["ntp_sync_interval"] + except BaseException: + datagram["syncinterval"] = 1 + datagram["type"] = 1 + + for server in paramgram["ntp_server"].strip().split(","): + id_counter += 1 + server_fields = dict() + + key_counter += 1 + if paramgram["ntp_auth"] == "enable": + server_fields["authentication"] = 1 + server_fields["key"] = paramgram["ntp_auth_pwd"] + server_fields["key-id"] = key_counter + else: + server_fields["authentication"] = 0 + server_fields["key"] = "" + server_fields["key-id"] = key_counter + + if paramgram["ntp_v3"] == "enable": + server_fields["ntp_v3"] = 1 + else: + server_fields["ntp_v3"] = 0 + + # split the host on the space to get the mask out + new_ntp_server = {"authentication": server_fields["authentication"], + "id": id_counter, "key": server_fields["key"], + "key-id": id_counter, "ntpv3": server_fields["ntp_v3"], + "server": server} + ntpservers.append(new_ntp_server) + datagram["ntpserver"] = ntpservers + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/ntp".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_admin(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "admin-https-redirect": paramgram["admin_https_redirect"], + "admin-port": paramgram["admin_http_port"], + "admin-sport": paramgram["admin_https_port"], + "admintimeout": paramgram["admin_timeout"], + "language": paramgram["admin_language"], + "gui-theme": paramgram["admin_gui_theme"], + "switch-controller": paramgram["admin_switch_controller"], + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/global".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_smtp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "port": paramgram["smtp_port"], + "reply-to": paramgram["smtp_replyto"], + "server": paramgram["smtp_server"], + "source-ip": paramgram["smtp_source_ipv4"] + } + + if paramgram["smtp_username"]: + datagram["authenticate"] = 1 + datagram["username"] = paramgram["smtp_username"] + datagram["password"] = paramgram["smtp_password"] + + if paramgram["smtp_conn_sec"] == "none": + datagram["security"] = 0 + if paramgram["smtp_conn_sec"] == "starttls": + datagram["security"] = 1 + if paramgram["smtp_conn_sec"] == "smtps": + datagram["security"] = 2 + + if paramgram["smtp_validate_cert"] == "enable": + datagram["validate-server"] = 1 + else: + datagram["validate-server"] = 0 + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/email-server".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_dns(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "domain": paramgram["dns_suffix"], + "primary": paramgram["dns_primary_ipv4"], + "secondary": paramgram["dns_secondary_ipv4"], + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/dns".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_toggle_fg(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + response = DEFAULT_RESULT_OBJ + datagram = {} + if paramgram["admin_enable_fortiguard"] in ["direct", "this-fmg"]: + datagram["include-default-servers"] = "enable" + elif paramgram["admin_enable_fortiguard"] == "none": + datagram["include-default-servers"] = "disable" + + datagram["server-list"] = list() + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/central-management".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + + return response + + +def set_devprof_fg(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "target": paramgram["admin_enable_fortiguard"], + "target-ip": None + } + + if paramgram["mode"] in ['set', 'add', 'update']: + if paramgram["admin_fortiguard_target"] is not None and datagram["target"] == "direct": + datagram["target-ip"] = paramgram["admin_fortiguard_target"] + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/device/profile/fortiguard".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_faz(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + response = DEFAULT_RESULT_OBJ + datagram = { + "target-ip": paramgram["admin_fortianalyzer_target"], + "target": "others", + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/device/profile/fortianalyzer".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + if paramgram["mode"] == "delete": + datagram["hastarget"] = "False" + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + provisioning_template=dict(required=False, type="str"), + provision_targets=dict(required=False, type="str"), + + device_unique_name=dict(required=False, type="str"), + snmp_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_query_port=dict(required=False, type="int"), + snmp_v2c_trap_port=dict(required=False, type="int"), + snmp_v2c_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_trap_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_query_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_name=dict(required=False, type="str", no_log=True), + snmp_v2c_id=dict(required=False, type="int"), + snmp_v2c_trap_src_ipv4=dict(required=False, type="str"), + snmp_v2c_trap_hosts_ipv4=dict(required=False, type="str"), + snmp_v2c_query_hosts_ipv4=dict(required=False, type="str"), + + snmpv3_auth_proto=dict(required=False, type="str", choices=["md5", "sha"]), + snmpv3_auth_pwd=dict(required=False, type="str", no_log=True), + snmpv3_name=dict(required=False, type="str"), + snmpv3_notify_hosts=dict(required=False, type="str"), + snmpv3_priv_proto=dict(required=False, type="str", choices=["aes", "des", "aes256", "aes256cisco"]), + snmpv3_priv_pwd=dict(required=False, type="str", no_log=True), + snmpv3_queries=dict(required=False, type="str", choices=["enable", "disable"]), + snmpv3_query_port=dict(required=False, type="int"), + snmpv3_security_level=dict(required=False, type="str", + choices=["no-auth-no-priv", "auth-no-priv", "auth-priv"]), + snmpv3_source_ip=dict(required=False, type="str"), + snmpv3_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmpv3_trap_rport=dict(required=False, type="int"), + snmpv3_trap_status=dict(required=False, type="str", choices=["enable", "disable"]), + + syslog_port=dict(required=False, type="int"), + syslog_server=dict(required=False, type="str"), + syslog_mode=dict(required=False, type="str", choices=["udp", "legacy-reliable", "reliable"], default="udp"), + syslog_status=dict(required=False, type="str", choices=["enable", "disable"]), + syslog_filter=dict(required=False, type="str", choices=["emergency", "alert", "critical", "error", + "warning", "notification", "information", "debug"]), + syslog_enc_algorithm=dict(required=False, type="str", choices=["high", "low", "disable", "high-medium"], + default="disable"), + syslog_facility=dict(required=False, type="str", choices=["kernel", "user", "mail", "daemon", "auth", + "syslog", "lpr", "news", "uucp", "cron", "authpriv", + "ftp", "ntp", "audit", "alert", "clock", "local0", + "local1", "local2", "local3", "local4", "local5", + "local6", "local7"], default="syslog"), + syslog_certificate=dict(required=False, type="str"), + + ntp_status=dict(required=False, type="str", choices=["enable", "disable"]), + ntp_sync_interval=dict(required=False, type="int"), + ntp_type=dict(required=False, type="str", choices=["fortiguard", "custom"]), + ntp_server=dict(required=False, type="str"), + ntp_auth=dict(required=False, type="str", choices=["enable", "disable"]), + ntp_auth_pwd=dict(required=False, type="str", no_log=True), + ntp_v3=dict(required=False, type="str", choices=["enable", "disable"]), + + admin_https_redirect=dict(required=False, type="str", choices=["enable", "disable"]), + admin_https_port=dict(required=False, type="int"), + admin_http_port=dict(required=False, type="int"), + admin_timeout=dict(required=False, type="int"), + admin_language=dict(required=False, type="str", + choices=["english", "simch", "japanese", "korean", + "spanish", "trach", "french", "portuguese"]), + admin_switch_controller=dict(required=False, type="str", choices=["enable", "disable"]), + admin_gui_theme=dict(required=False, type="str", choices=["green", "red", "blue", "melongene", "mariner"]), + admin_enable_fortiguard=dict(required=False, type="str", choices=["none", "direct", "this-fmg"]), + admin_fortianalyzer_target=dict(required=False, type="str"), + admin_fortiguard_target=dict(required=False, type="str"), + + smtp_username=dict(required=False, type="str"), + smtp_password=dict(required=False, type="str", no_log=True), + smtp_port=dict(required=False, type="int"), + smtp_replyto=dict(required=False, type="str"), + smtp_conn_sec=dict(required=False, type="str", choices=["none", "starttls", "smtps"]), + smtp_server=dict(required=False, type="str"), + smtp_source_ipv4=dict(required=False, type="str"), + smtp_validate_cert=dict(required=False, type="str", choices=["enable", "disable"]), + + dns_suffix=dict(required=False, type="str"), + dns_primary_ipv4=dict(required=False, type="str"), + dns_secondary_ipv4=dict(required=False, type="str"), + delete_provisioning_template=dict(required=False, type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "adom": module.params["adom"], + "mode": module.params["mode"], + "provision_targets": module.params["provision_targets"], + "provisioning_template": module.params["provisioning_template"], + + "snmp_status": module.params["snmp_status"], + "snmp_v2c_query_port": module.params["snmp_v2c_query_port"], + "snmp_v2c_trap_port": module.params["snmp_v2c_trap_port"], + "snmp_v2c_status": module.params["snmp_v2c_status"], + "snmp_v2c_trap_status": module.params["snmp_v2c_trap_status"], + "snmp_v2c_query_status": module.params["snmp_v2c_query_status"], + "snmp_v2c_name": module.params["snmp_v2c_name"], + "snmp_v2c_id": module.params["snmp_v2c_id"], + "snmp_v2c_trap_src_ipv4": module.params["snmp_v2c_trap_src_ipv4"], + "snmp_v2c_trap_hosts_ipv4": module.params["snmp_v2c_trap_hosts_ipv4"], + "snmp_v2c_query_hosts_ipv4": module.params["snmp_v2c_query_hosts_ipv4"], + + "snmpv3_auth_proto": module.params["snmpv3_auth_proto"], + "snmpv3_auth_pwd": module.params["snmpv3_auth_pwd"], + "snmpv3_name": module.params["snmpv3_name"], + "snmpv3_notify_hosts": module.params["snmpv3_notify_hosts"], + "snmpv3_priv_proto": module.params["snmpv3_priv_proto"], + "snmpv3_priv_pwd": module.params["snmpv3_priv_pwd"], + "snmpv3_queries": module.params["snmpv3_queries"], + "snmpv3_query_port": module.params["snmpv3_query_port"], + "snmpv3_security_level": module.params["snmpv3_security_level"], + "snmpv3_source_ip": module.params["snmpv3_source_ip"], + "snmpv3_status": module.params["snmpv3_status"], + "snmpv3_trap_rport": module.params["snmpv3_trap_rport"], + "snmpv3_trap_status": module.params["snmpv3_trap_status"], + + "syslog_port": module.params["syslog_port"], + "syslog_server": module.params["syslog_server"], + "syslog_mode": module.params["syslog_mode"], + "syslog_status": module.params["syslog_status"], + "syslog_filter": module.params["syslog_filter"], + "syslog_facility": module.params["syslog_facility"], + "syslog_enc_algorithm": module.params["syslog_enc_algorithm"], + "syslog_certificate": module.params["syslog_certificate"], + + "ntp_status": module.params["ntp_status"], + "ntp_sync_interval": module.params["ntp_sync_interval"], + "ntp_type": module.params["ntp_type"], + "ntp_server": module.params["ntp_server"], + "ntp_auth": module.params["ntp_auth"], + "ntp_auth_pwd": module.params["ntp_auth_pwd"], + "ntp_v3": module.params["ntp_v3"], + + "admin_https_redirect": module.params["admin_https_redirect"], + "admin_https_port": module.params["admin_https_port"], + "admin_http_port": module.params["admin_http_port"], + "admin_timeout": module.params["admin_timeout"], + "admin_language": module.params["admin_language"], + "admin_switch_controller": module.params["admin_switch_controller"], + "admin_gui_theme": module.params["admin_gui_theme"], + "admin_enable_fortiguard": module.params["admin_enable_fortiguard"], + "admin_fortianalyzer_target": module.params["admin_fortianalyzer_target"], + "admin_fortiguard_target": module.params["admin_fortiguard_target"], + + "smtp_username": module.params["smtp_username"], + "smtp_password": module.params["smtp_password"], + "smtp_port": module.params["smtp_port"], + "smtp_replyto": module.params["smtp_replyto"], + "smtp_conn_sec": module.params["smtp_conn_sec"], + "smtp_server": module.params["smtp_server"], + "smtp_source_ipv4": module.params["smtp_source_ipv4"], + "smtp_validate_cert": module.params["smtp_validate_cert"], + + "dns_suffix": module.params["dns_suffix"], + "dns_primary_ipv4": module.params["dns_primary_ipv4"], + "dns_secondary_ipv4": module.params["dns_secondary_ipv4"], + "delete_provisioning_template": module.params["delete_provisioning_template"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + try: + # CHECK IF WE ARE DELETING AN ENTIRE TEMPLATE. IF THAT'S THE CASE DO IT FIRST AND IGNORE THE REST. + if paramgram["delete_provisioning_template"] is not None: + results = set_devprof(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10, -1], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram), + stop_on_success=True) + except Exception as err: + raise FMGBaseException(err) + + try: + # CHECK TO SEE IF THE DEVPROF TEMPLATE EXISTS + devprof = get_devprof(fmgr, paramgram) + if devprof[0] != 0: + results = set_devprof(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SNMP SETTINGS IF THE SNMP_STATUS VARIABLE IS SET + if paramgram["snmp_status"] is not None: + results = set_devprof_snmp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE SNMP V2C COMMUNITY SETTINGS IF THEY ARE ALL HERE + if all(v is not None for v in (paramgram["snmp_v2c_query_port"], paramgram["snmp_v2c_trap_port"], + paramgram["snmp_v2c_status"], paramgram["snmp_v2c_trap_status"], + paramgram["snmp_v2c_query_status"], paramgram["snmp_v2c_name"], + paramgram["snmp_v2c_id"])): + results = set_devprof_snmp_v2c(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033], stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE SNMPV3 USER IF THERE + if all(v is not None for v in ( + [paramgram["snmpv3_auth_proto"], paramgram["snmpv3_auth_pwd"], paramgram["snmpv3_name"], + paramgram["snmpv3_notify_hosts"], paramgram["snmpv3_priv_proto"], + paramgram["snmpv3_priv_pwd"], + paramgram["snmpv3_queries"], + paramgram["snmpv3_query_port"], paramgram["snmpv3_security_level"], + paramgram["snmpv3_source_ip"], + paramgram["snmpv3_status"], paramgram["snmpv3_trap_rport"], paramgram["snmpv3_trap_status"]])): + + results = set_devprof_snmp_v3(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SYSLOG SETTINGS IF THE ALL THE NEEDED SYSLOG VARIABLES ARE PRESENT + if all(v is not None for v in [paramgram["syslog_port"], paramgram["syslog_mode"], + paramgram["syslog_server"], paramgram["syslog_status"]]): + # enable syslog in the devprof template + results = set_devprof_syslog(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE SYSLOG FILTER IS PRESENT THEN RUN THAT + if paramgram["syslog_filter"] is not None: + results = set_devprof_syslog_filter(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS NTP OPTIONS + if paramgram["ntp_status"]: + # VALIDATE INPUT + if paramgram["ntp_type"] == "custom" and paramgram["ntp_server"] is None: + module.exit_json(msg="You requested custom NTP type but did not provide ntp_server parameter.") + if paramgram["ntp_auth"] == "enable" and paramgram["ntp_auth_pwd"] is None: + module.exit_json( + msg="You requested NTP Authentication but did not provide ntp_auth_pwd parameter.") + + results = set_devprof_ntp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + try: + # PROCESS THE ADMIN OPTIONS + if any(v is not None for v in ( + paramgram["admin_https_redirect"], paramgram["admin_https_port"], paramgram["admin_http_port"], + paramgram["admin_timeout"], + paramgram["admin_language"], paramgram["admin_switch_controller"], + paramgram["admin_gui_theme"])): + + results = set_devprof_admin(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS FORTIGUARD OPTIONS + if paramgram["admin_enable_fortiguard"] is not None: + + results = set_devprof_toggle_fg(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + results = set_devprof_fg(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SMTP OPTIONS + if all(v is not None for v in ( + paramgram["smtp_username"], paramgram["smtp_password"], paramgram["smtp_port"], + paramgram["smtp_replyto"], + paramgram["smtp_conn_sec"], paramgram["smtp_server"], + paramgram["smtp_source_ipv4"], paramgram["smtp_validate_cert"])): + + results = set_devprof_smtp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE DNS OPTIONS + if any(v is not None for v in + (paramgram["dns_suffix"], paramgram["dns_primary_ipv4"], paramgram["dns_secondary_ipv4"])): + results = set_devprof_dns(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE admin_fortianalyzer_target OPTIONS + if paramgram["admin_fortianalyzer_target"] is not None: + + results = set_devprof_faz(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE PROVISIONING TEMPLATE TARGET PARAMETER + if paramgram["provision_targets"] is not None: + if paramgram["mode"] != "delete": + results = set_devprof_scope(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["mode"] == "delete": + # WE NEED TO FIGURE OUT WHAT'S THERE FIRST, BEFORE WE CAN RUN THIS + targets_to_add = list() + try: + current_scope = get_devprof_scope(fmgr, paramgram) + targets_to_remove = paramgram["provision_targets"].strip().split(",") + targets = current_scope[1][1]["scope member"] + for target in targets: + if target["name"] not in targets_to_remove: + target_append = {"name": target["name"]} + targets_to_add.append(target_append) + except BaseException: + pass + paramgram["targets_to_add"] = targets_to_add + results = set_devprof_scope(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_address.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_address.py new file mode 100644 index 00000000..02b17553 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_address.py @@ -0,0 +1,661 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_address +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the management of firewall objects in FortiManager +description: + - Allows for the management of IPv4, IPv6, and multicast address objects within FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + allow_routing: + description: + - Enable/disable use of this address in the static route configuration. + choices: ['enable', 'disable'] + default: 'disable' + + associated_interface: + description: + - Associated interface name. + + cache_ttl: + description: + - Minimal TTL of individual IP addresses in FQDN cache. Only applies when type = wildcard-fqdn. + + color: + description: + - Color of the object in FortiManager GUI. + - Takes integers 1-32 + default: 22 + + comment: + description: + - Comment for the object in FortiManager. + + country: + description: + - Country name. Required if type = geographic. + + end_ip: + description: + - End IP. Only used when ipv4 = iprange. + + group_members: + description: + - Address group member. If this is defined w/out group_name, the operation will fail. + + group_name: + description: + - Address group name. If this is defined in playbook task, all other options are ignored. + + ipv4: + description: + - Type of IPv4 Object. + - Must not be specified with either multicast or IPv6 parameters. + choices: ['ipmask', 'iprange', 'fqdn', 'wildcard', 'geography', 'wildcard-fqdn', 'group'] + + ipv4addr: + description: + - IP and network mask. If only defining one IP use this parameter. (i.e. 10.7.220.30/255.255.255.255) + - Can also define subnets (i.e. 10.7.220.0/255.255.255.0) + - Also accepts CIDR (i.e. 10.7.220.0/24) + - If Netmask is omitted after IP address, /32 is assumed. + - When multicast is set to Broadcast Subnet the ipv4addr parameter is used to specify the subnet. + + ipv6: + description: + - Puts module into IPv6 mode. + - Must not be specified with either ipv4 or multicast parameters. + choices: ['ip', 'iprange', 'group'] + + ipv6addr: + description: + - IPv6 address in full. (i.e. 2001:0db8:85a3:0000:0000:8a2e:0370:7334) + + fqdn: + description: + - Fully qualified domain name. + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + + multicast: + description: + - Manages Multicast Address Objects. + - Sets either a Multicast IP Range or a Broadcast Subnet. + - Must not be specified with either ipv4 or ipv6 parameters. + - When set to Broadcast Subnet the ipv4addr parameter is used to specify the subnet. + - Can create IPv4 Multicast Objects (multicastrange and broadcastmask options -- uses start/end-ip and ipv4addr). + choices: ['multicastrange', 'broadcastmask', 'ip6'] + + name: + description: + - Friendly Name Address object name in FortiManager. + + obj_id: + description: + - Object ID for NSX. + + start_ip: + description: + - Start IP. Only used when ipv4 = iprange. + + visibility: + description: + - Enable/disable address visibility. + choices: ['enable', 'disable'] + default: 'enable' + + wildcard: + description: + - IP address and wildcard netmask. Required if ipv4 = wildcard. + + wildcard_fqdn: + description: + - Wildcard FQDN. Required if ipv4 = wildcard-fqdn. +''' + +EXAMPLES = ''' +- name: ADD IPv4 IP ADDRESS OBJECT + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.30/32" + name: "ansible_v4Obj" + comment: "Created by Ansible" + color: "6" + +- name: ADD IPv4 IP ADDRESS OBJECT MORE OPTIONS + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.34/32" + name: "ansible_v4Obj_MORE" + comment: "Created by Ansible" + color: "6" + allow_routing: "enable" + cache_ttl: "180" + associated_interface: "port1" + obj_id: "123" + +- name: ADD IPv4 IP ADDRESS SUBNET OBJECT + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.0/255.255.255.128" + name: "ansible_subnet" + comment: "Created by Ansible" + mode: "set" + +- name: ADD IPv4 IP ADDRESS RANGE OBJECT + community.network.fmgr_fwobj_address: + ipv4: "iprange" + start_ip: "10.7.220.1" + end_ip: "10.7.220.125" + name: "ansible_range" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS WILDCARD OBJECT + community.network.fmgr_fwobj_address: + ipv4: "wildcard" + wildcard: "10.7.220.30/255.255.255.255" + name: "ansible_wildcard" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS WILDCARD FQDN OBJECT + community.network.fmgr_fwobj_address: + ipv4: "wildcard-fqdn" + wildcard_fqdn: "*.myds.com" + name: "Synology myds DDNS service" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS FQDN OBJECT + community.network.fmgr_fwobj_address: + ipv4: "fqdn" + fqdn: "ansible.com" + name: "ansible_fqdn" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS GEO OBJECT + community.network.fmgr_fwobj_address: + ipv4: "geography" + country: "usa" + name: "ansible_geo" + comment: "Created by Ansible" + +- name: ADD IPv6 ADDRESS + community.network.fmgr_fwobj_address: + ipv6: "ip" + ipv6addr: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + name: "ansible_v6Obj" + comment: "Created by Ansible" + +- name: ADD IPv6 ADDRESS RANGE + community.network.fmgr_fwobj_address: + ipv6: "iprange" + start_ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + end_ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7446" + name: "ansible_v6range" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS GROUP + community.network.fmgr_fwobj_address: + ipv4: "group" + group_name: "ansibleIPv4Group" + group_members: "ansible_fqdn, ansible_wildcard, ansible_range" + +- name: ADD IPv6 IP ADDRESS GROUP + community.network.fmgr_fwobj_address: + ipv6: "group" + group_name: "ansibleIPv6Group" + group_members: "ansible_v6Obj, ansible_v6range" + +- name: ADD MULTICAST RANGE + community.network.fmgr_fwobj_address: + multicast: "multicastrange" + start_ip: "224.0.0.251" + end_ip: "224.0.0.251" + name: "ansible_multicastrange" + comment: "Created by Ansible" + +- name: ADD BROADCAST SUBNET + community.network.fmgr_fwobj_address: + multicast: "broadcastmask" + ipv4addr: "10.7.220.0/24" + name: "ansible_broadcastSubnet" + comment: "Created by Ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + + +import re +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_fwobj_ipv4(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "comment": paramgram["comment"], + "associated-interface": paramgram["associated-interface"], + "cache-ttl": paramgram["cache-ttl"], + "name": paramgram["name"], + "allow-routing": paramgram["allow-routing"], + "color": paramgram["color"], + "meta fields": {}, + "dynamic_mapping": [], + "visibility": paramgram["allow-routing"], + "type": paramgram["ipv4"], + } + + # SET THE CORRECT URL BASED ON THE TYPE (WE'RE DOING GROUPS IN THIS METHOD, TOO) + if datagram["type"] == "group": + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp'.format(adom=paramgram["adom"]) + else: + url = '/pm/config/adom/{adom}/obj/firewall/address'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'ipmask' + ######################### + if datagram["type"] == "ipmask": + # CREATE THE SUBNET LIST OBJECT + subnet = [] + # EVAL THE IPV4ADDR INPUT AND SPLIT THE IP ADDRESS FROM THE MASK AND APPEND THEM TO THE SUBNET LIST + for subnets in paramgram["ipv4addr"].split("/"): + subnet.append(subnets) + + # CHECK THAT THE SECOND ENTRY IN THE SUBNET LIST (WHAT WAS TO THE RIGHT OF THE / CHARACTER) + # IS IN SUBNET MASK FORMAT AND NOT CIDR FORMAT. + # IF IT IS IN CIDR FORMAT, WE NEED TO CONVERT IT TO SUBNET BIT MASK FORMAT FOR THE JSON API + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + # IF THE SUBNET PARAMETER INPUT DIDN'T LOOK LIKE xxx.xxx.xxx.xxx TO REGEX... + # ... RUN IT THROUGH THE CIDR_TO_NETMASK() FUNCTION + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + # AND THEN UPDATE THE SUBNET LIST OBJECT + subnet[1] = mask + + # INCLUDE THE SUBNET LIST OBJECT IN THE DATAGRAM DICTIONARY TO BE SUBMITTED + datagram["subnet"] = subnet + + ######################### + # IF type = 'iprange' + ######################### + if datagram["type"] == "iprange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + datagram["subnet"] = ["0.0.0.0", "0.0.0.0"] + + ######################### + # IF type = 'geography' + ######################### + if datagram["type"] == "geography": + datagram["country"] = paramgram["country"] + + ######################### + # IF type = 'wildcard' + ######################### + if datagram["type"] == "wildcard": + + subnet = [] + for subnets in paramgram["wildcard"].split("/"): + subnet.append(subnets) + + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + subnet[1] = mask + + datagram["wildcard"] = subnet + + ######################### + # IF type = 'wildcard-fqdn' + ######################### + if datagram["type"] == "wildcard-fqdn": + datagram["wildcard-fqdn"] = paramgram["wildcard-fqdn"] + + ######################### + # IF type = 'fqdn' + ######################### + if datagram["type"] == "fqdn": + datagram["fqdn"] = paramgram["fqdn"] + + ######################### + # IF type = 'group' + ######################### + if datagram["type"] == "group": + datagram = { + "comment": paramgram["comment"], + "name": paramgram["group_name"], + "color": paramgram["color"], + "meta fields": {}, + "dynamic_mapping": [], + "visibility": paramgram["visibility"] + } + + members = [] + group_members = paramgram["group_members"].replace(" ", "") + try: + for member in group_members.split(","): + members.append(member) + except Exception: + pass + + datagram["member"] = members + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + # IF A GROUP, SET THE CORRECT NAME AND URL FOR THE GROUP ENDPOINT + if paramgram["ipv4"] == "group": + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp/{name}'.format(adom=paramgram["adom"], + name=paramgram["group_name"]) + # OTHERWISE WE'RE JUST GOING TO USE THE ADDRESS ENDPOINT + else: + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/address/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_ipv6(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "comment": paramgram["comment"], + "name": paramgram["name"], + "color": paramgram["color"], + "dynamic_mapping": [], + "visibility": paramgram["visibility"], + "type": paramgram["ipv6"] + } + + # SET THE CORRECT URL BASED ON THE TYPE (WE'RE DOING GROUPS IN THIS METHOD, TOO) + if datagram["type"] == "group": + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp6'.format(adom=paramgram["adom"]) + else: + url = '/pm/config/adom/{adom}/obj/firewall/address6'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'ip' + ######################### + if datagram["type"] == "ip": + datagram["type"] = "ipprefix" + datagram["ip6"] = paramgram["ipv6addr"] + + ######################### + # IF type = 'iprange' + ######################### + if datagram["type"] == "iprange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + + ######################### + # IF type = 'group' + ######################### + if datagram["type"] == "group": + datagram = None + datagram = { + "comment": paramgram["comment"], + "name": paramgram["group_name"], + "color": paramgram["color"], + "visibility": paramgram["visibility"] + } + + members = [] + group_members = paramgram["group_members"].replace(" ", "") + try: + for member in group_members.split(","): + members.append(member) + except Exception: + pass + + datagram["member"] = members + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + # IF A GROUP, SET THE CORRECT NAME AND URL FOR THE GROUP ENDPOINT + if paramgram["ipv6"] == "group": + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp6/{name}'.format(adom=paramgram["adom"], + name=paramgram["group_name"]) + # OTHERWISE WE'RE JUST GOING TO USE THE ADDRESS ENDPOINT + else: + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/address6/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_multicast(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "associated-interface": paramgram["associated-interface"], + "comment": paramgram["comment"], + "name": paramgram["name"], + "color": paramgram["color"], + "type": paramgram["multicast"], + "visibility": paramgram["visibility"], + } + + # SET THE CORRECT URL + url = '/pm/config/adom/{adom}/obj/firewall/multicast-address'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'multicastrange' + ######################### + if paramgram["multicast"] == "multicastrange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + datagram["subnet"] = ["0.0.0.0", "0.0.0.0"] + + ######################### + # IF type = 'broadcastmask' + ######################### + if paramgram["multicast"] == "broadcastmask": + # EVAL THE IPV4ADDR INPUT AND SPLIT THE IP ADDRESS FROM THE MASK AND APPEND THEM TO THE SUBNET LIST + subnet = [] + for subnets in paramgram["ipv4addr"].split("/"): + subnet.append(subnets) + # CHECK THAT THE SECOND ENTRY IN THE SUBNET LIST (WHAT WAS TO THE RIGHT OF THE / CHARACTER) + # IS IN SUBNET MASK FORMAT AND NOT CIDR FORMAT. + # IF IT IS IN CIDR FORMAT, WE NEED TO CONVERT IT TO SUBNET BIT MASK FORMAT FOR THE JSON API + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + # IF THE SUBNET PARAMETER INPUT DIDN'T LOOK LIKE 255.255.255.255 TO REGEX... + # ... RUN IT THROUGH THE fmgr_cidr_to_netmask() FUNCTION + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + # AND THEN UPDATE THE SUBNET LIST OBJECT + subnet[1] = mask + + # INCLUDE THE SUBNET LIST OBJECT IN THE DATAGRAM DICTIONARY TO BE SUBMITTED + datagram["subnet"] = subnet + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/multicast-address/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete"], type="str", default="add"), + + allow_routing=dict(required=False, type="str", choices=['enable', 'disable'], default="disable"), + associated_interface=dict(required=False, type="str"), + cache_ttl=dict(required=False, type="str"), + color=dict(required=False, type="str", default=22), + comment=dict(required=False, type="str"), + country=dict(required=False, type="str"), + fqdn=dict(required=False, type="str"), + name=dict(required=False, type="str"), + start_ip=dict(required=False, type="str"), + end_ip=dict(required=False, type="str"), + ipv4=dict(required=False, type="str", choices=['ipmask', 'iprange', 'fqdn', 'wildcard', + 'geography', 'wildcard-fqdn', 'group']), + visibility=dict(required=False, type="str", choices=['enable', 'disable'], default="enable"), + wildcard=dict(required=False, type="str"), + wildcard_fqdn=dict(required=False, type="str"), + ipv6=dict(required=False, type="str", choices=['ip', 'iprange', 'group']), + group_members=dict(required=False, type="str"), + group_name=dict(required=False, type="str"), + ipv4addr=dict(required=False, type="str"), + ipv6addr=dict(required=False, type="str"), + multicast=dict(required=False, type="str", choices=['multicastrange', 'broadcastmask', 'ip6']), + obj_id=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + mutually_exclusive=[ + ['ipv4', 'ipv6'], + ['ipv4', 'multicast'], + ['ipv6', 'multicast'] + ]) + paramgram = { + "adom": module.params["adom"], + "allow-routing": module.params["allow_routing"], + "associated-interface": module.params["associated_interface"], + "cache-ttl": module.params["cache_ttl"], + "color": module.params["color"], + "comment": module.params["comment"], + "country": module.params["country"], + "end-ip": module.params["end_ip"], + "fqdn": module.params["fqdn"], + "name": module.params["name"], + "start-ip": module.params["start_ip"], + "visibility": module.params["visibility"], + "wildcard": module.params["wildcard"], + "wildcard-fqdn": module.params["wildcard_fqdn"], + "ipv6": module.params["ipv6"], + "ipv4": module.params["ipv4"], + "group_members": module.params["group_members"], + "group_name": module.params["group_name"], + "ipv4addr": module.params["ipv4addr"], + "ipv6addr": module.params["ipv6addr"], + "multicast": module.params["multicast"], + "mode": module.params["mode"], + "obj-id": module.params["obj_id"], + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr._tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + try: + if paramgram["ipv4"]: + results = fmgr_fwobj_ipv4(fmgr, paramgram) + + elif paramgram["ipv6"]: + results = fmgr_fwobj_ipv6(fmgr, paramgram) + + elif paramgram["multicast"]: + results = fmgr_fwobj_multicast(fmgr, paramgram) + + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + if results is not None: + return module.exit_json(**results[1]) + else: + return module.exit_json(msg="Couldn't find a proper ipv4 or ipv6 or multicast parameter " + "to run in the logic tree. Exiting...") + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool.py new file mode 100644 index 00000000..36718197 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool.py @@ -0,0 +1,442 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_ippool +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the editing of IP Pool Objects within FortiManager. +description: + - Allows users to add/edit/delete IP Pool Objects. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + type: + description: + - IP pool type (overload, one-to-one, fixed port range, or port block allocation). + - choice | overload | IP addresses in the IP pool can be shared by clients. + - choice | one-to-one | One to one mapping. + - choice | fixed-port-range | Fixed port range. + - choice | port-block-allocation | Port block allocation. + required: false + choices: ["overload", "one-to-one", "fixed-port-range", "port-block-allocation"] + + startip: + description: + - First IPv4 address (inclusive) in the range for the address pool (format xxx.xxx.xxx.xxx, Default| 0.0.0.0). + required: false + + source_startip: + description: + - First IPv4 address (inclusive) in the range of the source addresses to be translated (format xxx.xxx.xxx.xxx, + Default| 0.0.0.0). + required: false + + source_endip: + description: + - Final IPv4 address (inclusive) in the range of the source addresses to be translated (format xxx.xxx.xxx.xxx, + Default| 0.0.0.0). + required: false + + permit_any_host: + description: + - Enable/disable full cone NAT. + - choice | disable | Disable full cone NAT. + - choice | enable | Enable full cone NAT. + required: false + choices: ["disable", "enable"] + + pba_timeout: + description: + - Port block allocation timeout (seconds). + required: false + + num_blocks_per_user: + description: + - Number of addresses blocks that can be used by a user (1 to 128, default = 8). + required: false + + name: + description: + - IP pool name. + required: false + + endip: + description: + - Final IPv4 address (inclusive) in the range for the address pool (format xxx.xxx.xxx.xxx, Default| 0.0.0.0). + required: false + + comments: + description: + - Comment. + required: false + + block_size: + description: + - Number of addresses in a block (64 to 4096, default = 128). + required: false + + associated_interface: + description: + - Associated interface name. + required: false + + arp_reply: + description: + - Enable/disable replying to ARP requests when an IP Pool is added to a policy (default = enable). + - choice | disable | Disable ARP reply. + - choice | enable | Enable ARP reply. + required: false + choices: ["disable", "enable"] + + arp_intf: + description: + - Select an interface from available options that will reply to ARP requests. (If blank, any is selected). + required: false + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameter.ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_arp_intf: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_arp_reply: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["disable", "enable"] + + dynamic_mapping_associated_interface: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_block_size: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_comments: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_num_blocks_per_user: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_pba_timeout: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_permit_any_host: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["disable", "enable"] + + dynamic_mapping_source_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_source_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_type: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["overload", "one-to-one", "fixed-port-range", "port-block-allocation"] + + +''' + +EXAMPLES = ''' +- name: ADD FMGR_FIREWALL_IPPOOL Overload + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_overload" + comments: "Created by ansible" + type: "overload" + + # OPTIONS FOR ALL MODES + startip: "10.10.10.10" + endip: "10.10.10.100" + arp_reply: "enable" + +- name: ADD FMGR_FIREWALL_IPPOOL one-to-one + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_121" + comments: "Created by ansible" + type: "one-to-one" + + # OPTIONS FOR ALL MODES + startip: "10.10.20.10" + endip: "10.10.20.100" + arp_reply: "enable" + +- name: ADD FMGR_FIREWALL_IPPOOL FIXED PORT RANGE + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_fixed_port" + comments: "Created by ansible" + type: "fixed-port-range" + + # OPTIONS FOR ALL MODES + startip: "10.10.40.10" + endip: "10.10.40.100" + arp_reply: "enable" + # FIXED PORT RANGE OPTIONS + source_startip: "192.168.20.1" + source_endip: "192.168.20.20" + +- name: ADD FMGR_FIREWALL_IPPOOL PORT BLOCK ALLOCATION + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_port_block_allocation" + comments: "Created by ansible" + type: "port-block-allocation" + + # OPTIONS FOR ALL MODES + startip: "10.10.30.10" + endip: "10.10.30.100" + arp_reply: "enable" + # PORT BLOCK ALLOCATION OPTIONS + block_size: "128" + num_blocks_per_user: "1" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_fwobj_ippool_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ippool'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ippool/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + type=dict(required=False, type="str", choices=["overload", + "one-to-one", + "fixed-port-range", + "port-block-allocation"]), + startip=dict(required=False, type="str"), + source_startip=dict(required=False, type="str"), + source_endip=dict(required=False, type="str"), + permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + pba_timeout=dict(required=False, type="int"), + num_blocks_per_user=dict(required=False, type="int"), + name=dict(required=False, type="str"), + endip=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + block_size=dict(required=False, type="int"), + associated_interface=dict(required=False, type="str"), + arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + arp_intf=dict(required=False, type="str"), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_arp_intf=dict(required=False, type="str"), + dynamic_mapping_arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_associated_interface=dict(required=False, type="str"), + dynamic_mapping_block_size=dict(required=False, type="int"), + dynamic_mapping_comments=dict(required=False, type="str"), + dynamic_mapping_endip=dict(required=False, type="str"), + dynamic_mapping_num_blocks_per_user=dict(required=False, type="int"), + dynamic_mapping_pba_timeout=dict(required=False, type="int"), + dynamic_mapping_permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_source_endip=dict(required=False, type="str"), + dynamic_mapping_source_startip=dict(required=False, type="str"), + dynamic_mapping_startip=dict(required=False, type="str"), + dynamic_mapping_type=dict(required=False, type="str", choices=["overload", + "one-to-one", + "fixed-port-range", + "port-block-allocation"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "type": module.params["type"], + "startip": module.params["startip"], + "source-startip": module.params["source_startip"], + "source-endip": module.params["source_endip"], + "permit-any-host": module.params["permit_any_host"], + "pba-timeout": module.params["pba_timeout"], + "num-blocks-per-user": module.params["num_blocks_per_user"], + "name": module.params["name"], + "endip": module.params["endip"], + "comments": module.params["comments"], + "block-size": module.params["block_size"], + "associated-interface": module.params["associated_interface"], + "arp-reply": module.params["arp_reply"], + "arp-intf": module.params["arp_intf"], + "dynamic_mapping": { + "arp-intf": module.params["dynamic_mapping_arp_intf"], + "arp-reply": module.params["dynamic_mapping_arp_reply"], + "associated-interface": module.params["dynamic_mapping_associated_interface"], + "block-size": module.params["dynamic_mapping_block_size"], + "comments": module.params["dynamic_mapping_comments"], + "endip": module.params["dynamic_mapping_endip"], + "num-blocks-per-user": module.params["dynamic_mapping_num_blocks_per_user"], + "pba-timeout": module.params["dynamic_mapping_pba_timeout"], + "permit-any-host": module.params["dynamic_mapping_permit_any_host"], + "source-endip": module.params["dynamic_mapping_source_endip"], + "source-startip": module.params["dynamic_mapping_source_startip"], + "startip": module.params["dynamic_mapping_startip"], + "type": module.params["dynamic_mapping_type"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + # UPDATE THE CHANGED PARAMGRAM + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_fwobj_ippool_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool6.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool6.py new file mode 100644 index 00000000..15e8977f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_ippool6.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_ippool6 +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the editing of IP Pool Objects within FortiManager. +description: + - Allows users to add/edit/delete IPv6 Pool Objects. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + startip: + description: + - First IPv6 address (inclusive) in the range for the address pool. + required: false + + name: + description: + - IPv6 IP pool name. + required: false + + endip: + description: + - Final IPv6 address (inclusive) in the range for the address pool. + required: false + + comments: + description: + - Comment. + required: false + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_comments: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + +''' + +EXAMPLES = ''' +- name: ADD FMGR_FIREWALL_IPPOOL6 + fmgr_firewall_ippool6: + mode: "add" + adom: "ansible" + startip: + name: "IPv6 IPPool" + endip: + comments: "Created by Ansible" + +- name: DELETE FMGR_FIREWALL_IPPOOL6 + fmgr_firewall_ippool6: + mode: "delete" + adom: "ansible" + name: "IPv6 IPPool" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_fwobj_ippool6_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ippool6'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ippool6/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + startip=dict(required=False, type="str"), + name=dict(required=False, type="str"), + endip=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_comments=dict(required=False, type="str"), + dynamic_mapping_endip=dict(required=False, type="str"), + dynamic_mapping_startip=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "startip": module.params["startip"], + "name": module.params["name"], + "endip": module.params["endip"], + "comments": module.params["comments"], + "dynamic_mapping": { + "comments": module.params["dynamic_mapping_comments"], + "endip": module.params["dynamic_mapping_endip"], + "startip": module.params["dynamic_mapping_startip"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_fwobj_ippool6_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_service.py new file mode 100644 index 00000000..ae2b7ff0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_service.py @@ -0,0 +1,617 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_service +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages FortiManager Firewall Service Objects. +description: + - Manages FortiManager Firewall Service Objects. + +options: + adom: + description: + -The ADOM the configuration should belong to. + required: false + default: root + + app_category: + description: + - Application category ID. + required: false + + app_service_type: + description: + - Application service type. + required: false + + application: + description: + - Application ID. + required: false + + category: + description: + - Service category. + required: false + + check_reset_range: + description: + - Enable disable RST check. + required: false + + color: + description: + - GUI icon color. + required: false + default: 22 + + comment: + description: + - Comment. + required: false + + custom_type: + description: + - Tells module what kind of custom service to be added. + choices: ['tcp_udp_sctp', 'icmp', 'icmp6', 'ip', 'http', 'ftp', 'connect', 'socks_tcp', 'socks_udp', 'all'] + default: all + required: false + + explicit_proxy: + description: + - Enable/disable explicit web proxy service. + choices: ['enable', 'disable'] + default: 'disable' + required: false + + fqdn: + description: + - Fully qualified domain name. + required: false + default: "" + + group_name: + description: + - Name of the Service Group. + required: false + + group_member: + description: + - Comma-Seperated list of members' names. + required: false + + icmp_code: + description: + - ICMP code. + required: false + + icmp_type: + description: + - ICMP type. + required: false + + iprange: + description: + - Start IP-End IP. + required: false + default: "0.0.0.0" + + name: + description: + - Custom service name. + required: false + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + required: false + + object_type: + description: + - Tells module if we are adding a custom service, category, or group. + choices: ['custom', 'group', 'category'] + required: false + + protocol: + description: + - Protocol type. + required: false + + protocol_number: + description: + - IP protocol number. + required: false + + sctp_portrange: + description: + - Multiple SCTP port ranges. Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + session_ttl: + description: + - Session TTL (300 - 604800, 0 = default). + required: false + default: 0 + + tcp_halfclose_timer: + description: + - TCP half close timeout (1 - 86400 sec, 0 = default). + required: false + default: 0 + + tcp_halfopen_timer: + description: + - TCP half close timeout (1 - 86400 sec, 0 = default). + required: false + default: 0 + + tcp_portrange: + description: + - Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + tcp_timewait_timer: + description: + - TCP half close timeout (1 - 300 sec, 0 = default). + required: false + default: 0 + + udp_idle_timer: + description: + - TCP half close timeout (0 - 86400 sec, 0 = default). + required: false + default: 0 + + udp_portrange: + description: + - Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + visibility: + description: + - Enable/disable service visibility. + required: false + choices: ["enable", "disable"] + default: "enable" + +''' + +EXAMPLES = ''' +- name: ADD A CUSTOM SERVICE FOR TCP/UDP/SCP + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_service" + object_type: "custom" + custom_type: "tcp_udp_sctp" + tcp_portrange: "443" + udp_portrange: "51" + sctp_portrange: "100" + +- name: ADD A CUSTOM SERVICE FOR TCP/UDP/SCP WITH SOURCE RANGES AND MULTIPLES + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_serviceWithSource" + object_type: "custom" + custom_type: "tcp_udp_sctp" + tcp_portrange: "443:2000-1000,80-82:10000-20000" + udp_portrange: "51:100-200,162:200-400" + sctp_portrange: "100:2000-2500" + +- name: ADD A CUSTOM SERVICE FOR ICMP + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp" + object_type: "custom" + custom_type: "icmp" + icmp_type: "8" + icmp_code: "3" + +- name: ADD A CUSTOM SERVICE FOR ICMP6 + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp6" + object_type: "custom" + custom_type: "icmp6" + icmp_type: "5" + icmp_code: "1" + +- name: ADD A CUSTOM SERVICE FOR IP - GRE + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp6" + object_type: "custom" + custom_type: "ip" + protocol_number: "47" + +- name: ADD A CUSTOM PROXY FOR ALL WITH SOURCE RANGES AND MULTIPLES + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_proxy_all" + object_type: "custom" + custom_type: "all" + explicit_proxy: "enable" + tcp_portrange: "443:2000-1000,80-82:10000-20000" + iprange: "www.ansible.com" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_fwobj_service_custom(fmgr, paramgram): + """ + description: + - the tcp and udp-portrange parameters are in a list when there are multiple. they are not in a list when they + singular or by themselves (only 1 was listed) + - the syntax for this is (destPort:sourcePort). Ranges are (xxxx-xxxx) i.e. 443:443, or 443:1000-2000. + - if you leave out the second field after the colon (source port) it assumes any source port (which is usual) + - multiples would look like ['443:1000-2000','80'] + - a single would look simple like "443:1000-2000" without the list around it ( a string!) + - the protocol parameter is the protocol NUMBER, not the string of it. + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + # SET THE URL FOR ADD / SET + url = '/pm/config/adom/{adom}/obj/firewall/service/custom'.format(adom=paramgram["adom"]) + # BUILD THE DEFAULT DATAGRAM + datagram = { + # ADVANCED OPTIONS + "app-category": paramgram["app-category"], + "app-service-type": paramgram["app-service-type"], + "application": paramgram["application"], + "category": paramgram["category"], + "check-reset-range": paramgram["check-reset-range"], + "color": paramgram["color"], + "session-ttl": paramgram["session-ttl"], + "tcp-halfclose-timer": paramgram["tcp-halfclose-timer"], + "tcp-halfopen-timer": paramgram["tcp-halfopen-timer"], + "tcp-timewait-timer": paramgram["tcp-timewait-timer"], + "udp-idle-timer": paramgram["udp-idle-timer"], + "visibility": paramgram["visibility"], + "comment": paramgram["comment"], + "proxy": paramgram["explicit-proxy"], + "name": paramgram["name"] + } + + if datagram["proxy"] == "disable": + ####################################### + # object-type = "TCP/UDP/SCTP" + ####################################### + if paramgram["custom_type"] == "tcp_udp_sctp": + datagram["protocol"] = "TCP/UDP/SCTP" + # PROCESS PORT RANGES TO PUT INTO THE PROPER SYNTAX + if paramgram["tcp-portrange"] is not None: + tcp_list = [] + for tcp in paramgram["tcp-portrange"].split(","): + tcp = tcp.strip() + tcp_list.append(tcp) + datagram["tcp-portrange"] = tcp_list + + if paramgram["udp-portrange"] is not None: + udp_list = [] + for udp in paramgram["udp-portrange"].split(","): + udp = udp.strip() + udp_list.append(udp) + datagram["udp-portrange"] = udp_list + + if paramgram["sctp-portrange"] is not None: + sctp_list = [] + for sctp in paramgram["sctp-portrange"].split(","): + sctp = sctp.strip() + sctp_list.append(sctp) + datagram["sctp-portrange"] = sctp_list + + ####################################### + # object-type = "ICMP" + ####################################### + if paramgram["custom_type"] == "icmp": + datagram["icmpcode"] = paramgram["icmp_code"] + datagram["icmptype"] = paramgram["icmp_type"] + datagram["protocol"] = "ICMP" + + ####################################### + # object-type = "ICMP6" + ####################################### + if paramgram["custom_type"] == "icmp6": + datagram["icmpcode"] = paramgram["icmp_code"] + datagram["icmptype"] = paramgram["icmp_type"] + datagram["protocol"] = "ICMP6" + + ####################################### + # object-type = "IP" + ####################################### + if paramgram["custom_type"] == "ip": + datagram["protocol"] = "IP" + datagram["protocol-number"] = paramgram["protocol-number"] + + ####################################### + # object-type in any of the explicit proxy options + ####################################### + if datagram["proxy"] == "enable": + datagram["protocol"] = paramgram["custom_type"].upper() + datagram["iprange"] = paramgram["iprange"] + + # PROCESS PROXY TCP PORT RANGES TO PUT INTO THE PROPER SYNTAX + if paramgram["tcp-portrange"] is not None: + tcp_list = [] + for tcp in paramgram["tcp-portrange"].split(","): + tcp = tcp.strip() + tcp_list.append(tcp) + datagram["tcp-portrange"] = tcp_list + + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/custom' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_service_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/config/adom/{adom}/obj/firewall/service/group'.format(adom=paramgram["adom"]) + datagram = { + "name": paramgram["group-name"], + "comment": paramgram["comment"], + "proxy": paramgram["explicit-proxy"], + "color": paramgram["color"] + } + + members = paramgram["group-member"] + member = [] + for obj in members.split(","): + member.append(obj.strip()) + datagram["member"] = member + + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/group' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["group-name"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_service_category(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/config/adom/{adom}/obj/firewall/service/category'.format(adom=paramgram["adom"]) + # GET RID OF ANY WHITESPACE + category = paramgram["category"] + category = category.strip() + + datagram = { + "name": paramgram["category"], + "comment": "Created by Ansible" + } + + # IF MODE = DELETE + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/category' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["category"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(required=False, type="str", choices=['add', 'set', 'delete'], default="add"), + app_category=dict(required=False, type="str"), + app_service_type=dict(required=False, type="str"), + application=dict(required=False, type="str"), + category=dict(required=False, type="str"), + check_reset_range=dict(required=False, type="str"), + color=dict(required=False, type="int", default=22), + comment=dict(required=False, type="str"), + custom_type=dict(required=False, type="str", choices=['tcp_udp_sctp', 'icmp', 'icmp6', 'ip', 'http', 'ftp', + 'connect', 'socks_tcp', 'socks_udp', 'all'], + default="all"), + explicit_proxy=dict(required=False, type="str", choices=['enable', 'disable'], default="disable"), + fqdn=dict(required=False, type="str", default=""), + group_name=dict(required=False, type="str"), + group_member=dict(required=False, type="str"), + icmp_code=dict(required=False, type="int"), + icmp_type=dict(required=False, type="int"), + iprange=dict(required=False, type="str", default="0.0.0.0"), + name=dict(required=False, type="str"), + protocol=dict(required=False, type="str"), + protocol_number=dict(required=False, type="int"), + sctp_portrange=dict(required=False, type="str"), + session_ttl=dict(required=False, type="int", default=0), + object_type=dict(required=False, type="str", choices=['custom', 'group', 'category']), + tcp_halfclose_timer=dict(required=False, type="int", default=0), + tcp_halfopen_timer=dict(required=False, type="int", default=0), + tcp_portrange=dict(required=False, type="str"), + tcp_timewait_timer=dict(required=False, type="int", default=0), + udp_idle_timer=dict(required=False, type="int", default=0), + udp_portrange=dict(required=False, type="str"), + visibility=dict(required=False, type="str", default="enable", choices=["enable", "disable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE DATAGRAM + paramgram = { + "adom": module.params["adom"], + "app-category": module.params["app_category"], + "app-service-type": module.params["app_service_type"], + "application": module.params["application"], + "category": module.params["category"], + "check-reset-range": module.params["check_reset_range"], + "color": module.params["color"], + "comment": module.params["comment"], + "custom_type": module.params["custom_type"], + "explicit-proxy": module.params["explicit_proxy"], + "fqdn": module.params["fqdn"], + "group-name": module.params["group_name"], + "group-member": module.params["group_member"], + "icmp_code": module.params["icmp_code"], + "icmp_type": module.params["icmp_type"], + "iprange": module.params["iprange"], + "name": module.params["name"], + "mode": module.params["mode"], + "protocol": module.params["protocol"], + "protocol-number": module.params["protocol_number"], + "sctp-portrange": module.params["sctp_portrange"], + "object_type": module.params["object_type"], + "session-ttl": module.params["session_ttl"], + "tcp-halfclose-timer": module.params["tcp_halfclose_timer"], + "tcp-halfopen-timer": module.params["tcp_halfopen_timer"], + "tcp-portrange": module.params["tcp_portrange"], + "tcp-timewait-timer": module.params["tcp_timewait_timer"], + "udp-idle-timer": module.params["udp_idle_timer"], + "udp-portrange": module.params["udp_portrange"], + "visibility": module.params["visibility"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + # CHECK FOR CATEGORIES TO ADD + # THIS IS ONLY WHEN OBJECT_TYPE ISN'T SPECIFICALLY ADDING A CATEGORY! + # WE NEED TO ADD THE CATEGORY BEFORE ADDING THE OBJECT + # IF ANY category ARE DEFINED AND MODE IS ADD OR SET LETS ADD THOSE + # THIS IS A "BLIND ADD" AND THE EXIT CODE FOR OBJECT ALREADY EXISTS IS TREATED AS A PASS + if paramgram["category"] is not None and paramgram["mode"] in ['add', 'set'] \ + and paramgram["object_type"] != "category": + category_add = fmgr_fwobj_service_category(fmgr, paramgram) + fmgr.govern_response(module=module, results=category_add, + ansible_facts=fmgr.construct_ansible_facts(category_add, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS CATEGORY... + if paramgram["object_type"] == 'category': + results = fmgr_fwobj_service_category(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS CUSTOM... + if paramgram["object_type"] == 'custom': + results = fmgr_fwobj_service_custom(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS GROUP... + if paramgram["object_type"] == 'group': + results = fmgr_fwobj_service_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_vip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_vip.py new file mode 100644 index 00000000..d1902b42 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwobj_vip.py @@ -0,0 +1,2424 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_vip +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages Virtual IPs objects in FortiManager +description: + - Manages Virtual IP objects in FortiManager for IPv4 + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + websphere_server: + description: + - Enable to add an HTTP header to indicate SSL offloading for a WebSphere server. + - choice | disable | Do not add HTTP header indicating SSL offload for WebSphere server. + - choice | enable | Add HTTP header indicating SSL offload for WebSphere server. + required: false + choices: ["disable", "enable"] + + weblogic_server: + description: + - Enable to add an HTTP header to indicate SSL offloading for a WebLogic server. + - choice | disable | Do not add HTTP header indicating SSL offload for WebLogic server. + - choice | enable | Add HTTP header indicating SSL offload for WebLogic server. + required: false + choices: ["disable", "enable"] + + type: + description: + - Configure a static NAT, load balance, server load balance, DNS translation, or FQDN VIP. + - choice | static-nat | Static NAT. + - choice | load-balance | Load balance. + - choice | server-load-balance | Server load balance. + - choice | dns-translation | DNS translation. + - choice | fqdn | FQDN Translation + required: false + choices: ["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"] + + ssl_server_session_state_type: + description: + - How to expire SSL sessions for the segment of the SSL connection between the server and the FortiGate. + - choice | disable | Do not keep session states. + - choice | time | Expire session states after this many minutes. + - choice | count | Expire session states when this maximum is reached. + - choice | both | Expire session states based on time or count, whichever occurs first. + required: false + choices: ["disable", "time", "count", "both"] + + ssl_server_session_state_timeout: + description: + - Number of minutes to keep FortiGate to Server SSL session state. + required: false + + ssl_server_session_state_max: + description: + - Maximum number of FortiGate to Server SSL session states to keep. + required: false + + ssl_server_min_version: + description: + - Lowest SSL/TLS version acceptable from a server. Use the client setting by default. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + - choice | client | Use same value as client configuration. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + ssl_server_max_version: + description: + - Highest SSL/TLS version acceptable from a server. Use the client setting by default. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + - choice | client | Use same value as client configuration. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + ssl_server_algorithm: + description: + - Permitted encryption algorithms for the server side of SSL full mode sessions according to encryption strength + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | custom | Custom encryption. Use ssl-server-cipher-suites to select the cipher suites that are allowed. + - choice | client | Use the same encryption algorithms for both client and server sessions. + required: false + choices: ["high", "low", "medium", "custom", "client"] + + ssl_send_empty_frags: + description: + - Enable/disable sending empty fragments to avoid CBC IV attacks (SSL 3.0 & TLS 1.0 only). + - choice | disable | Do not send empty fragments. + - choice | enable | Send empty fragments. + required: false + choices: ["disable", "enable"] + + ssl_pfs: + description: + - Select the cipher suites that can be used for SSL perfect forward secrecy (PFS). + - choice | require | Allow only Diffie-Hellman cipher-suites, so PFS is applied. + - choice | deny | Allow only non-Diffie-Hellman cipher-suites, so PFS is not applied. + - choice | allow | Allow use of any cipher suite so PFS may or may not be used depending on the cipher suite + required: false + choices: ["require", "deny", "allow"] + + ssl_mode: + description: + - Apply SSL offloading mode + - choice | half | Client to FortiGate SSL. + - choice | full | Client to FortiGate and FortiGate to Server SSL. + required: false + choices: ["half", "full"] + + ssl_min_version: + description: + - Lowest SSL/TLS version acceptable from a client. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_max_version: + description: + - Highest SSL/TLS version acceptable from a client. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_http_match_host: + description: + - Enable/disable HTTP host matching for location conversion. + - choice | disable | Do not match HTTP host. + - choice | enable | Match HTTP host in response header. + required: false + choices: ["disable", "enable"] + + ssl_http_location_conversion: + description: + - Enable to replace HTTP with HTTPS in the reply's Location HTTP header field. + - choice | disable | Disable HTTP location conversion. + - choice | enable | Enable HTTP location conversion. + required: false + choices: ["disable", "enable"] + + ssl_hsts_include_subdomains: + description: + - Indicate that HSTS header applies to all subdomains. + - choice | disable | HSTS header does not apply to subdomains. + - choice | enable | HSTS header applies to subdomains. + required: false + choices: ["disable", "enable"] + + ssl_hsts_age: + description: + - Number of seconds the client should honour the HSTS setting. + required: false + + ssl_hsts: + description: + - Enable/disable including HSTS header in response. + - choice | disable | Do not add a HSTS header to each a HTTP response. + - choice | enable | Add a HSTS header to each HTTP response. + required: false + choices: ["disable", "enable"] + + ssl_hpkp_report_uri: + description: + - URL to report HPKP violations to. + required: false + + ssl_hpkp_primary: + description: + - Certificate to generate primary HPKP pin from. + required: false + + ssl_hpkp_include_subdomains: + description: + - Indicate that HPKP header applies to all subdomains. + - choice | disable | HPKP header does not apply to subdomains. + - choice | enable | HPKP header applies to subdomains. + required: false + choices: ["disable", "enable"] + + ssl_hpkp_backup: + description: + - Certificate to generate backup HPKP pin from. + required: false + + ssl_hpkp_age: + description: + - Number of seconds the client should honour the HPKP setting. + required: false + + ssl_hpkp: + description: + - Enable/disable including HPKP header in response. + - choice | disable | Do not add a HPKP header to each HTTP response. + - choice | enable | Add a HPKP header to each a HTTP response. + - choice | report-only | Add a HPKP Report-Only header to each HTTP response. + required: false + choices: ["disable", "enable", "report-only"] + + ssl_dh_bits: + description: + - Number of bits to use in the Diffie-Hellman exchange for RSA encryption of SSL sessions. + - choice | 768 | 768-bit Diffie-Hellman prime. + - choice | 1024 | 1024-bit Diffie-Hellman prime. + - choice | 1536 | 1536-bit Diffie-Hellman prime. + - choice | 2048 | 2048-bit Diffie-Hellman prime. + - choice | 3072 | 3072-bit Diffie-Hellman prime. + - choice | 4096 | 4096-bit Diffie-Hellman prime. + required: false + choices: ["768", "1024", "1536", "2048", "3072", "4096"] + + ssl_client_session_state_type: + description: + - How to expire SSL sessions for the segment of the SSL connection between the client and the FortiGate. + - choice | disable | Do not keep session states. + - choice | time | Expire session states after this many minutes. + - choice | count | Expire session states when this maximum is reached. + - choice | both | Expire session states based on time or count, whichever occurs first. + required: false + choices: ["disable", "time", "count", "both"] + + ssl_client_session_state_timeout: + description: + - Number of minutes to keep client to FortiGate SSL session state. + required: false + + ssl_client_session_state_max: + description: + - Maximum number of client to FortiGate SSL session states to keep. + required: false + + ssl_client_renegotiation: + description: + - Allow, deny, or require secure renegotiation of client sessions to comply with RFC 5746. + - choice | deny | Abort any client initiated SSL re-negotiation attempt. + - choice | allow | Allow a SSL client to renegotiate. + - choice | secure | Abort any client initiated SSL re-negotiation attempt that does not use RFC 5746. + required: false + choices: ["deny", "allow", "secure"] + + ssl_client_fallback: + description: + - Enable/disable support for preventing Downgrade Attacks on client connections (RFC 7507). + - choice | disable | Disable. + - choice | enable | Enable. + required: false + choices: ["disable", "enable"] + + ssl_certificate: + description: + - The name of the SSL certificate to use for SSL acceleration. + required: false + + ssl_algorithm: + description: + - Permitted encryption algorithms for SSL sessions according to encryption strength. + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + - choice | custom | Custom encryption. Use config ssl-cipher-suites to select the cipher suites that are allowed. + required: false + choices: ["high", "medium", "low", "custom"] + + srcintf_filter: + description: + - Interfaces to which the VIP applies. Separate the names with spaces. + required: false + + src_filter: + description: + - Source address filter. Each address must be either an IP/subnet (x.x.x.x/n) or a range (x.x.x.x-y.y.y.y). + - Separate addresses with spaces. + required: false + + service: + description: + - Service name. + required: false + + server_type: + description: + - Protocol to be load balanced by the virtual server (also called the server load balance virtual IP). + - choice | http | HTTP + - choice | https | HTTPS + - choice | ssl | SSL + - choice | tcp | TCP + - choice | udp | UDP + - choice | ip | IP + - choice | imaps | IMAPS + - choice | pop3s | POP3S + - choice | smtps | SMTPS + required: false + choices: ["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"] + + protocol: + description: + - Protocol to use when forwarding packets. + - choice | tcp | TCP. + - choice | udp | UDP. + - choice | sctp | SCTP. + - choice | icmp | ICMP. + required: false + choices: ["tcp", "udp", "sctp", "icmp"] + + portmapping_type: + description: + - Port mapping type. + - choice | 1-to-1 | One to one. + - choice | m-to-n | Many to many. + required: false + choices: ["1-to-1", "m-to-n"] + + portforward: + description: + - Enable/disable port forwarding. + - choice | disable | Disable port forward. + - choice | enable | Enable port forward. + required: false + choices: ["disable", "enable"] + + persistence: + description: + - Configure how to make sure that clients connect to the same server every time they make a request that is part + - of the same session. + - choice | none | None. + - choice | http-cookie | HTTP cookie. + - choice | ssl-session-id | SSL session ID. + required: false + choices: ["none", "http-cookie", "ssl-session-id"] + + outlook_web_access: + description: + - Enable to add the Front-End-Https header for Microsoft Outlook Web Access. + - choice | disable | Disable Outlook Web Access support. + - choice | enable | Enable Outlook Web Access support. + required: false + choices: ["disable", "enable"] + + nat_source_vip: + description: + - Enable to prevent unintended servers from using a virtual IP. + - Disable to use the actual IP address of the server as the source address. + - choice | disable | Do not force to NAT as VIP. + - choice | enable | Force to NAT as VIP. + required: false + choices: ["disable", "enable"] + + name: + description: + - Virtual IP name. + required: false + + monitor: + description: + - Name of the health check monitor to use when polling to determine a virtual server's connectivity status. + required: false + + max_embryonic_connections: + description: + - Maximum number of incomplete connections. + required: false + + mappedport: + description: + - Port number range on the destination network to which the external port number range is mapped. + required: false + + mappedip: + description: + - IP address or address range on the destination network to which the external IP address is mapped. + required: false + + mapped_addr: + description: + - Mapped FQDN address name. + required: false + + ldb_method: + description: + - Method used to distribute sessions to real servers. + - choice | static | Distribute to server based on source IP. + - choice | round-robin | Distribute to server based round robin order. + - choice | weighted | Distribute to server based on weight. + - choice | least-session | Distribute to server with lowest session count. + - choice | least-rtt | Distribute to server with lowest Round-Trip-Time. + - choice | first-alive | Distribute to the first server that is alive. + - choice | http-host | Distribute to server based on host field in HTTP header. + required: false + choices: ["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", "http-host"] + + https_cookie_secure: + description: + - Enable/disable verification that inserted HTTPS cookies are secure. + - choice | disable | Do not mark cookie as secure, allow sharing between an HTTP and HTTPS connection. + - choice | enable | Mark inserted cookie as secure, cookie can only be used for HTTPS a connection. + required: false + choices: ["disable", "enable"] + + http_multiplex: + description: + - Enable/disable HTTP multiplexing. + - choice | disable | Disable HTTP session multiplexing. + - choice | enable | Enable HTTP session multiplexing. + required: false + choices: ["disable", "enable"] + + http_ip_header_name: + description: + - For HTTP multiplexing, enter a custom HTTPS header name. The orig client IP address is added to this header. + - If empty, X-Forwarded-For is used. + required: false + + http_ip_header: + description: + - For HTTP multiplexing, enable to add the original client IP address in the XForwarded-For HTTP header. + - choice | disable | Disable adding HTTP header. + - choice | enable | Enable adding HTTP header. + required: false + choices: ["disable", "enable"] + + http_cookie_share: + description: + - Control sharing of cookies across virtual servers. same-ip means a cookie from one virtual server can be used + - by another. Disable stops cookie sharing. + - choice | disable | Only allow HTTP cookie to match this virtual server. + - choice | same-ip | Allow HTTP cookie to match any virtual server with same IP. + required: false + choices: ["disable", "same-ip"] + + http_cookie_path: + description: + - Limit HTTP cookie persistence to the specified path. + required: false + + http_cookie_generation: + description: + - Generation of HTTP cookie to be accepted. Changing invalidates all existing cookies. + required: false + + http_cookie_domain_from_host: + description: + - Enable/disable use of HTTP cookie domain from host field in HTTP. + - choice | disable | Disable use of HTTP cookie domain from host field in HTTP (use http-cooke-domain setting). + - choice | enable | Enable use of HTTP cookie domain from host field in HTTP. + required: false + choices: ["disable", "enable"] + + http_cookie_domain: + description: + - Domain that HTTP cookie persistence should apply to. + required: false + + http_cookie_age: + description: + - Time in minutes that client web browsers should keep a cookie. Default is 60 seconds. 0 = no time limit. + required: false + + gratuitous_arp_interval: + description: + - Enable to have the VIP send gratuitous ARPs. 0=disabled. Set from 5 up to 8640000 seconds to enable. + required: false + + extport: + description: + - Incoming port number range that you want to map to a port number range on the destination network. + required: false + + extip: + description: + - IP address or address range on the external interface that you want to map to an address or address range on t + - he destination network. + required: false + + extintf: + description: + - Interface connected to the source network that receives the packets that will be forwarded to the destination + - network. + required: false + + extaddr: + description: + - External FQDN address name. + required: false + + dns_mapping_ttl: + description: + - DNS mapping TTL (Set to zero to use TTL in DNS response, default = 0). + required: false + + comment: + description: + - Comment. + required: false + + color: + description: + - Color of icon on the GUI. + required: false + + arp_reply: + description: + - Enable to respond to ARP requests for this virtual IP address. Enabled by default. + - choice | disable | Disable ARP reply. + - choice | enable | Enable ARP reply. + required: false + choices: ["disable", "enable"] + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_arp_reply: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_color: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_comment: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_dns_mapping_ttl: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extaddr: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extintf: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extport: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_gratuitous_arp_interval: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_domain: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_domain_from_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_http_cookie_generation: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_path: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_share: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | same-ip | + required: false + choices: ["disable", "same-ip"] + + dynamic_mapping_http_ip_header: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_http_ip_header_name: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_multiplex: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_https_cookie_secure: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ldb_method: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | static | + - choice | round-robin | + - choice | weighted | + - choice | least-session | + - choice | least-rtt | + - choice | first-alive | + - choice | http-host | + required: false + choices: ["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", "http-host"] + + dynamic_mapping_mapped_addr: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_mappedip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_mappedport: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_max_embryonic_connections: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_monitor: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_nat_source_vip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_outlook_web_access: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_persistence: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | none | + - choice | http-cookie | + - choice | ssl-session-id | + required: false + choices: ["none", "http-cookie", "ssl-session-id"] + + dynamic_mapping_portforward: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_portmapping_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | 1-to-1 | + - choice | m-to-n | + required: false + choices: ["1-to-1", "m-to-n"] + + dynamic_mapping_protocol: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | tcp | + - choice | udp | + - choice | sctp | + - choice | icmp | + required: false + choices: ["tcp", "udp", "sctp", "icmp"] + + dynamic_mapping_server_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | http | + - choice | https | + - choice | ssl | + - choice | tcp | + - choice | udp | + - choice | ip | + - choice | imaps | + - choice | pop3s | + - choice | smtps | + required: false + choices: ["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"] + + dynamic_mapping_service: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_src_filter: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_srcintf_filter: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_algorithm: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | high | + - choice | medium | + - choice | low | + - choice | custom | + required: false + choices: ["high", "medium", "low", "custom"] + + dynamic_mapping_ssl_certificate: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_fallback: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_client_renegotiation: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | deny | + - choice | allow | + - choice | secure | + required: false + choices: ["deny", "allow", "secure"] + + dynamic_mapping_ssl_client_session_state_max: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_session_state_timeout: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_session_state_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | time | + - choice | count | + - choice | both | + required: false + choices: ["disable", "time", "count", "both"] + + dynamic_mapping_ssl_dh_bits: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | 768 | + - choice | 1024 | + - choice | 1536 | + - choice | 2048 | + - choice | 3072 | + - choice | 4096 | + required: false + choices: ["768", "1024", "1536", "2048", "3072", "4096"] + + dynamic_mapping_ssl_hpkp: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + - choice | report-only | + required: false + choices: ["disable", "enable", "report-only"] + + dynamic_mapping_ssl_hpkp_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_backup: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_include_subdomains: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_hpkp_primary: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_report_uri: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hsts: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_hsts_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hsts_include_subdomains: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_http_location_conversion: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_http_match_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_max_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + dynamic_mapping_ssl_min_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + dynamic_mapping_ssl_mode: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | half | + - choice | full | + required: false + choices: ["half", "full"] + + dynamic_mapping_ssl_pfs: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | require | + - choice | deny | + - choice | allow | + required: false + choices: ["require", "deny", "allow"] + + dynamic_mapping_ssl_send_empty_frags: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_server_algorithm: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | high | + - choice | low | + - choice | medium | + - choice | custom | + - choice | client | + required: false + choices: ["high", "low", "medium", "custom", "client"] + + dynamic_mapping_ssl_server_max_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + - choice | client | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + dynamic_mapping_ssl_server_min_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + - choice | client | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + dynamic_mapping_ssl_server_session_state_max: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_server_session_state_timeout: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_server_session_state_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | time | + - choice | count | + - choice | both | + required: false + choices: ["disable", "time", "count", "both"] + + dynamic_mapping_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | static-nat | + - choice | load-balance | + - choice | server-load-balance | + - choice | dns-translation | + - choice | fqdn | + required: false + choices: ["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"] + + dynamic_mapping_weblogic_server: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_websphere_server: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_realservers_client_ip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_healthcheck: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + - choice | vip | + required: false + choices: ["disable", "enable", "vip"] + + dynamic_mapping_realservers_holddown_interval: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_http_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_ip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_max_connections: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_monitor: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_port: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_seq: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_status: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | active | + - choice | standby | + - choice | disable | + required: false + choices: ["active", "standby", "disable"] + + dynamic_mapping_realservers_weight: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_cipher_suites_cipher: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | TLS-RSA-WITH-RC4-128-MD5 | + - choice | TLS-RSA-WITH-RC4-128-SHA | + - choice | TLS-RSA-WITH-DES-CBC-SHA | + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-RSA-WITH-SEED-CBC-SHA | + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + dynamic_mapping_ssl_cipher_suites_versions: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | + - flag | tls-1.0 | + - flag | tls-1.1 | + - flag | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + realservers: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + realservers_client_ip: + description: + - Only clients in this IP range can connect to this real server. + required: false + + realservers_healthcheck: + description: + - Enable to check the responsiveness of the real server before forwarding traffic. + - choice | disable | Disable per server health check. + - choice | enable | Enable per server health check. + - choice | vip | Use health check defined in VIP. + required: false + choices: ["disable", "enable", "vip"] + + realservers_holddown_interval: + description: + - Time in seconds that the health check monitor monitors an unresponsive server that should be active. + required: false + + realservers_http_host: + description: + - HTTP server domain name in HTTP header. + required: false + + realservers_ip: + description: + - IP address of the real server. + required: false + + realservers_max_connections: + description: + - Max number of active connections that can be directed to the real server. When reached, sessions are sent to + - their real servers. + required: false + + realservers_monitor: + description: + - Name of the health check monitor to use when polling to determine a virtual server's connectivity status. + required: false + + realservers_port: + description: + - Port for communicating with the real server. Required if port forwarding is enabled. + required: false + + realservers_seq: + description: + - Real Server Sequence Number + required: false + + realservers_status: + description: + - Set the status of the real server to active so that it can accept traffic. + - Or on standby or disabled so no traffic is sent. + - choice | active | Server status active. + - choice | standby | Server status standby. + - choice | disable | Server status disable. + required: false + choices: ["active", "standby", "disable"] + + realservers_weight: + description: + - Weight of the real server. If weighted load balancing is enabled, the server with the highest weight gets more + - connections. + required: false + + ssl_cipher_suites: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_cipher_suites_cipher: + description: + - Cipher suite name. + - choice | TLS-RSA-WITH-RC4-128-MD5 | Cipher suite TLS-RSA-WITH-RC4-128-MD5. + - choice | TLS-RSA-WITH-RC4-128-SHA | Cipher suite TLS-RSA-WITH-RC4-128-SHA. + - choice | TLS-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-RSA-WITH-DES-CBC-SHA. + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-DES-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | Cipher suite TLS-ECDHE-RSA-WITH-RC4-128-SHA. + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-DSS-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DSS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC_SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC_SHA384. + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-DES-CBC-SHA. + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + ssl_cipher_suites_versions: + description: + - SSL/TLS versions that the cipher suite can be used with. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | SSL 3.0. + - flag | tls-1.0 | TLS 1.0. + - flag | tls-1.1 | TLS 1.1. + - flag | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_server_cipher_suites: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_server_cipher_suites_cipher: + description: + - Cipher suite name. + - choice | TLS-RSA-WITH-RC4-128-MD5 | Cipher suite TLS-RSA-WITH-RC4-128-MD5. + - choice | TLS-RSA-WITH-RC4-128-SHA | Cipher suite TLS-RSA-WITH-RC4-128-SHA. + - choice | TLS-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-RSA-WITH-DES-CBC-SHA. + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-DES-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | Cipher suite TLS-ECDHE-RSA-WITH-RC4-128-SHA. + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | Suite TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-DSS-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DSS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC_SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC_SHA384. + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-DES-CBC-SHA. + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + ssl_server_cipher_suites_priority: + description: + - SSL/TLS cipher suites priority. + required: false + + ssl_server_cipher_suites_versions: + description: + - SSL/TLS versions that the cipher suite can be used with. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | SSL 3.0. + - flag | tls-1.0 | TLS 1.0. + - flag | tls-1.1 | TLS 1.1. + - flag | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + +''' + +EXAMPLES = ''' +# BASIC FULL STATIC NAT MAPPING +- name: EDIT FMGR_FIREWALL_VIP SNAT + community.network.fmgr_fwobj_vip: + name: "Basic StaticNAT Map" + mode: "set" + adom: "ansible" + type: "static-nat" + extip: "82.72.192.185" + extintf: "any" + mappedip: "10.7.220.25" + comment: "Created by Ansible" + color: "17" + +# BASIC PORT PNAT MAPPING +- name: EDIT FMGR_FIREWALL_VIP PNAT + community.network.fmgr_fwobj_vip: + name: "Basic PNAT Map Port 10443" + mode: "set" + adom: "ansible" + type: "static-nat" + extip: "82.72.192.185" + extport: "10443" + extintf: "any" + portforward: "enable" + protocol: "tcp" + mappedip: "10.7.220.25" + mappedport: "443" + comment: "Created by Ansible" + color: "17" + +# BASIC DNS TRANSLATION NAT +- name: EDIT FMGR_FIREWALL_DNST + community.network.fmgr_fwobj_vip: + name: "Basic DNS Translation" + mode: "set" + adom: "ansible" + type: "dns-translation" + extip: "192.168.0.1-192.168.0.100" + extintf: "dmz" + mappedip: "3.3.3.0/24, 4.0.0.0/24" + comment: "Created by Ansible" + color: "12" + +# BASIC FQDN NAT +- name: EDIT FMGR_FIREWALL_FQDN + community.network.fmgr_fwobj_vip: + name: "Basic FQDN Translation" + mode: "set" + adom: "ansible" + type: "fqdn" + mapped_addr: "google-play" + comment: "Created by Ansible" + color: "5" + +# DELETE AN ENTRY +- name: DELETE FMGR_FIREWALL_VIP PNAT + community.network.fmgr_fwobj_vip: + name: "Basic PNAT Map Port 10443" + mode: "delete" + adom: "ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_firewall_vip_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/vip'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/vip/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + websphere_server=dict(required=False, type="str", choices=["disable", "enable"]), + weblogic_server=dict(required=False, type="str", choices=["disable", "enable"]), + type=dict(required=False, type="str", + choices=["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"]), + ssl_server_session_state_type=dict(required=False, type="str", choices=["disable", "time", "count", "both"]), + ssl_server_session_state_timeout=dict(required=False, type="int"), + ssl_server_session_state_max=dict(required=False, type="int"), + ssl_server_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + ssl_server_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + ssl_server_algorithm=dict(required=False, type="str", choices=["high", "low", "medium", "custom", "client"]), + ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + ssl_mode=dict(required=False, type="str", choices=["half", "full"]), + ssl_min_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_max_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_http_match_host=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_http_location_conversion=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hsts_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hsts_age=dict(required=False, type="int"), + ssl_hsts=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hpkp_report_uri=dict(required=False, type="str"), + ssl_hpkp_primary=dict(required=False, type="str"), + ssl_hpkp_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hpkp_backup=dict(required=False, type="str"), + ssl_hpkp_age=dict(required=False, type="int"), + ssl_hpkp=dict(required=False, type="str", choices=["disable", "enable", "report-only"]), + ssl_dh_bits=dict(required=False, type="str", choices=["768", "1024", "1536", "2048", "3072", "4096"]), + ssl_client_session_state_type=dict(required=False, type="str", choices=["disable", "time", "count", "both"]), + ssl_client_session_state_timeout=dict(required=False, type="int"), + ssl_client_session_state_max=dict(required=False, type="int"), + ssl_client_renegotiation=dict(required=False, type="str", choices=["deny", "allow", "secure"]), + ssl_client_fallback=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_certificate=dict(required=False, type="str"), + ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low", "custom"]), + srcintf_filter=dict(required=False, type="str"), + src_filter=dict(required=False, type="str"), + service=dict(required=False, type="str"), + server_type=dict(required=False, type="str", + choices=["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"]), + protocol=dict(required=False, type="str", choices=["tcp", "udp", "sctp", "icmp"]), + portmapping_type=dict(required=False, type="str", choices=["1-to-1", "m-to-n"]), + portforward=dict(required=False, type="str", choices=["disable", "enable"]), + persistence=dict(required=False, type="str", choices=["none", "http-cookie", "ssl-session-id"]), + outlook_web_access=dict(required=False, type="str", choices=["disable", "enable"]), + nat_source_vip=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + monitor=dict(required=False, type="str"), + max_embryonic_connections=dict(required=False, type="int"), + mappedport=dict(required=False, type="str"), + mappedip=dict(required=False, type="str"), + mapped_addr=dict(required=False, type="str"), + ldb_method=dict(required=False, type="str", + choices=["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", + "http-host"]), + https_cookie_secure=dict(required=False, type="str", choices=["disable", "enable"]), + http_multiplex=dict(required=False, type="str", choices=["disable", "enable"]), + http_ip_header_name=dict(required=False, type="str"), + http_ip_header=dict(required=False, type="str", choices=["disable", "enable"]), + http_cookie_share=dict(required=False, type="str", choices=["disable", "same-ip"]), + http_cookie_path=dict(required=False, type="str"), + http_cookie_generation=dict(required=False, type="int"), + http_cookie_domain_from_host=dict(required=False, type="str", choices=["disable", "enable"]), + http_cookie_domain=dict(required=False, type="str"), + http_cookie_age=dict(required=False, type="int"), + gratuitous_arp_interval=dict(required=False, type="int"), + extport=dict(required=False, type="str"), + extip=dict(required=False, type="str"), + extintf=dict(required=False, type="str"), + extaddr=dict(required=False, type="str"), + dns_mapping_ttl=dict(required=False, type="int"), + comment=dict(required=False, type="str"), + color=dict(required=False, type="int"), + arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_color=dict(required=False, type="int"), + dynamic_mapping_comment=dict(required=False, type="str"), + dynamic_mapping_dns_mapping_ttl=dict(required=False, type="int"), + dynamic_mapping_extaddr=dict(required=False, type="str"), + dynamic_mapping_extintf=dict(required=False, type="str"), + dynamic_mapping_extip=dict(required=False, type="str"), + dynamic_mapping_extport=dict(required=False, type="str"), + dynamic_mapping_gratuitous_arp_interval=dict(required=False, type="int"), + dynamic_mapping_http_cookie_age=dict(required=False, type="int"), + dynamic_mapping_http_cookie_domain=dict(required=False, type="str"), + dynamic_mapping_http_cookie_domain_from_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_http_cookie_generation=dict(required=False, type="int"), + dynamic_mapping_http_cookie_path=dict(required=False, type="str"), + dynamic_mapping_http_cookie_share=dict(required=False, type="str", choices=["disable", "same-ip"]), + dynamic_mapping_http_ip_header=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_http_ip_header_name=dict(required=False, type="str"), + dynamic_mapping_http_multiplex=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_https_cookie_secure=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ldb_method=dict(required=False, type="str", choices=["static", + "round-robin", + "weighted", + "least-session", + "least-rtt", + "first-alive", + "http-host"]), + dynamic_mapping_mapped_addr=dict(required=False, type="str"), + dynamic_mapping_mappedip=dict(required=False, type="str"), + dynamic_mapping_mappedport=dict(required=False, type="str"), + dynamic_mapping_max_embryonic_connections=dict(required=False, type="int"), + dynamic_mapping_monitor=dict(required=False, type="str"), + dynamic_mapping_nat_source_vip=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_outlook_web_access=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_persistence=dict(required=False, type="str", choices=["none", "http-cookie", "ssl-session-id"]), + dynamic_mapping_portforward=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_portmapping_type=dict(required=False, type="str", choices=["1-to-1", "m-to-n"]), + dynamic_mapping_protocol=dict(required=False, type="str", choices=["tcp", "udp", "sctp", "icmp"]), + dynamic_mapping_server_type=dict(required=False, type="str", + choices=["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", + "smtps"]), + dynamic_mapping_service=dict(required=False, type="str"), + dynamic_mapping_src_filter=dict(required=False, type="str"), + dynamic_mapping_srcintf_filter=dict(required=False, type="str"), + dynamic_mapping_ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low", "custom"]), + dynamic_mapping_ssl_certificate=dict(required=False, type="str"), + dynamic_mapping_ssl_client_fallback=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_client_renegotiation=dict(required=False, type="str", choices=["deny", "allow", "secure"]), + dynamic_mapping_ssl_client_session_state_max=dict(required=False, type="int"), + dynamic_mapping_ssl_client_session_state_timeout=dict(required=False, type="int"), + dynamic_mapping_ssl_client_session_state_type=dict(required=False, type="str", + choices=["disable", "time", "count", "both"]), + dynamic_mapping_ssl_dh_bits=dict(required=False, type="str", + choices=["768", "1024", "1536", "2048", "3072", "4096"]), + dynamic_mapping_ssl_hpkp=dict(required=False, type="str", choices=["disable", "enable", "report-only"]), + dynamic_mapping_ssl_hpkp_age=dict(required=False, type="int"), + dynamic_mapping_ssl_hpkp_backup=dict(required=False, type="str"), + dynamic_mapping_ssl_hpkp_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_hpkp_primary=dict(required=False, type="str"), + dynamic_mapping_ssl_hpkp_report_uri=dict(required=False, type="str"), + dynamic_mapping_ssl_hsts=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_hsts_age=dict(required=False, type="int"), + dynamic_mapping_ssl_hsts_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_http_location_conversion=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_http_match_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + dynamic_mapping_ssl_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + dynamic_mapping_ssl_mode=dict(required=False, type="str", choices=["half", "full"]), + dynamic_mapping_ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + dynamic_mapping_ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_server_algorithm=dict(required=False, type="str", + choices=["high", "low", "medium", "custom", "client"]), + dynamic_mapping_ssl_server_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + dynamic_mapping_ssl_server_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + dynamic_mapping_ssl_server_session_state_max=dict(required=False, type="int"), + dynamic_mapping_ssl_server_session_state_timeout=dict(required=False, type="int"), + dynamic_mapping_ssl_server_session_state_type=dict(required=False, type="str", + choices=["disable", "time", "count", "both"]), + dynamic_mapping_type=dict(required=False, type="str", + choices=["static-nat", "load-balance", "server-load-balance", "dns-translation", + "fqdn"]), + dynamic_mapping_weblogic_server=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_websphere_server=dict(required=False, type="str", choices=["disable", "enable"]), + + dynamic_mapping_realservers_client_ip=dict(required=False, type="str"), + dynamic_mapping_realservers_healthcheck=dict(required=False, type="str", choices=["disable", "enable", "vip"]), + dynamic_mapping_realservers_holddown_interval=dict(required=False, type="int"), + dynamic_mapping_realservers_http_host=dict(required=False, type="str"), + dynamic_mapping_realservers_ip=dict(required=False, type="str"), + dynamic_mapping_realservers_max_connections=dict(required=False, type="int"), + dynamic_mapping_realservers_monitor=dict(required=False, type="str"), + dynamic_mapping_realservers_port=dict(required=False, type="int"), + dynamic_mapping_realservers_seq=dict(required=False, type="str"), + dynamic_mapping_realservers_status=dict(required=False, type="str", choices=["active", "standby", "disable"]), + dynamic_mapping_realservers_weight=dict(required=False, type="int"), + + dynamic_mapping_ssl_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + dynamic_mapping_ssl_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + realservers=dict(required=False, type="list"), + realservers_client_ip=dict(required=False, type="str"), + realservers_healthcheck=dict(required=False, type="str", choices=["disable", "enable", "vip"]), + realservers_holddown_interval=dict(required=False, type="int"), + realservers_http_host=dict(required=False, type="str"), + realservers_ip=dict(required=False, type="str"), + realservers_max_connections=dict(required=False, type="int"), + realservers_monitor=dict(required=False, type="str"), + realservers_port=dict(required=False, type="int"), + realservers_seq=dict(required=False, type="str"), + realservers_status=dict(required=False, type="str", choices=["active", "standby", "disable"]), + realservers_weight=dict(required=False, type="int"), + ssl_cipher_suites=dict(required=False, type="list"), + ssl_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + ssl_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_server_cipher_suites=dict(required=False, type="list"), + ssl_server_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + ssl_server_cipher_suites_priority=dict(required=False, type="str"), + ssl_server_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "websphere-server": module.params["websphere_server"], + "weblogic-server": module.params["weblogic_server"], + "type": module.params["type"], + "ssl-server-session-state-type": module.params["ssl_server_session_state_type"], + "ssl-server-session-state-timeout": module.params["ssl_server_session_state_timeout"], + "ssl-server-session-state-max": module.params["ssl_server_session_state_max"], + "ssl-server-min-version": module.params["ssl_server_min_version"], + "ssl-server-max-version": module.params["ssl_server_max_version"], + "ssl-server-algorithm": module.params["ssl_server_algorithm"], + "ssl-send-empty-frags": module.params["ssl_send_empty_frags"], + "ssl-pfs": module.params["ssl_pfs"], + "ssl-mode": module.params["ssl_mode"], + "ssl-min-version": module.params["ssl_min_version"], + "ssl-max-version": module.params["ssl_max_version"], + "ssl-http-match-host": module.params["ssl_http_match_host"], + "ssl-http-location-conversion": module.params["ssl_http_location_conversion"], + "ssl-hsts-include-subdomains": module.params["ssl_hsts_include_subdomains"], + "ssl-hsts-age": module.params["ssl_hsts_age"], + "ssl-hsts": module.params["ssl_hsts"], + "ssl-hpkp-report-uri": module.params["ssl_hpkp_report_uri"], + "ssl-hpkp-primary": module.params["ssl_hpkp_primary"], + "ssl-hpkp-include-subdomains": module.params["ssl_hpkp_include_subdomains"], + "ssl-hpkp-backup": module.params["ssl_hpkp_backup"], + "ssl-hpkp-age": module.params["ssl_hpkp_age"], + "ssl-hpkp": module.params["ssl_hpkp"], + "ssl-dh-bits": module.params["ssl_dh_bits"], + "ssl-client-session-state-type": module.params["ssl_client_session_state_type"], + "ssl-client-session-state-timeout": module.params["ssl_client_session_state_timeout"], + "ssl-client-session-state-max": module.params["ssl_client_session_state_max"], + "ssl-client-renegotiation": module.params["ssl_client_renegotiation"], + "ssl-client-fallback": module.params["ssl_client_fallback"], + "ssl-certificate": module.params["ssl_certificate"], + "ssl-algorithm": module.params["ssl_algorithm"], + "srcintf-filter": module.params["srcintf_filter"], + "src-filter": module.params["src_filter"], + "service": module.params["service"], + "server-type": module.params["server_type"], + "protocol": module.params["protocol"], + "portmapping-type": module.params["portmapping_type"], + "portforward": module.params["portforward"], + "persistence": module.params["persistence"], + "outlook-web-access": module.params["outlook_web_access"], + "nat-source-vip": module.params["nat_source_vip"], + "name": module.params["name"], + "monitor": module.params["monitor"], + "max-embryonic-connections": module.params["max_embryonic_connections"], + "mappedport": module.params["mappedport"], + "mappedip": module.params["mappedip"], + "mapped-addr": module.params["mapped_addr"], + "ldb-method": module.params["ldb_method"], + "https-cookie-secure": module.params["https_cookie_secure"], + "http-multiplex": module.params["http_multiplex"], + "http-ip-header-name": module.params["http_ip_header_name"], + "http-ip-header": module.params["http_ip_header"], + "http-cookie-share": module.params["http_cookie_share"], + "http-cookie-path": module.params["http_cookie_path"], + "http-cookie-generation": module.params["http_cookie_generation"], + "http-cookie-domain-from-host": module.params["http_cookie_domain_from_host"], + "http-cookie-domain": module.params["http_cookie_domain"], + "http-cookie-age": module.params["http_cookie_age"], + "gratuitous-arp-interval": module.params["gratuitous_arp_interval"], + "extport": module.params["extport"], + "extip": module.params["extip"], + "extintf": module.params["extintf"], + "extaddr": module.params["extaddr"], + "dns-mapping-ttl": module.params["dns_mapping_ttl"], + "comment": module.params["comment"], + "color": module.params["color"], + "arp-reply": module.params["arp_reply"], + "dynamic_mapping": { + "arp-reply": module.params["dynamic_mapping_arp_reply"], + "color": module.params["dynamic_mapping_color"], + "comment": module.params["dynamic_mapping_comment"], + "dns-mapping-ttl": module.params["dynamic_mapping_dns_mapping_ttl"], + "extaddr": module.params["dynamic_mapping_extaddr"], + "extintf": module.params["dynamic_mapping_extintf"], + "extip": module.params["dynamic_mapping_extip"], + "extport": module.params["dynamic_mapping_extport"], + "gratuitous-arp-interval": module.params["dynamic_mapping_gratuitous_arp_interval"], + "http-cookie-age": module.params["dynamic_mapping_http_cookie_age"], + "http-cookie-domain": module.params["dynamic_mapping_http_cookie_domain"], + "http-cookie-domain-from-host": module.params["dynamic_mapping_http_cookie_domain_from_host"], + "http-cookie-generation": module.params["dynamic_mapping_http_cookie_generation"], + "http-cookie-path": module.params["dynamic_mapping_http_cookie_path"], + "http-cookie-share": module.params["dynamic_mapping_http_cookie_share"], + "http-ip-header": module.params["dynamic_mapping_http_ip_header"], + "http-ip-header-name": module.params["dynamic_mapping_http_ip_header_name"], + "http-multiplex": module.params["dynamic_mapping_http_multiplex"], + "https-cookie-secure": module.params["dynamic_mapping_https_cookie_secure"], + "ldb-method": module.params["dynamic_mapping_ldb_method"], + "mapped-addr": module.params["dynamic_mapping_mapped_addr"], + "mappedip": module.params["dynamic_mapping_mappedip"], + "mappedport": module.params["dynamic_mapping_mappedport"], + "max-embryonic-connections": module.params["dynamic_mapping_max_embryonic_connections"], + "monitor": module.params["dynamic_mapping_monitor"], + "nat-source-vip": module.params["dynamic_mapping_nat_source_vip"], + "outlook-web-access": module.params["dynamic_mapping_outlook_web_access"], + "persistence": module.params["dynamic_mapping_persistence"], + "portforward": module.params["dynamic_mapping_portforward"], + "portmapping-type": module.params["dynamic_mapping_portmapping_type"], + "protocol": module.params["dynamic_mapping_protocol"], + "server-type": module.params["dynamic_mapping_server_type"], + "service": module.params["dynamic_mapping_service"], + "src-filter": module.params["dynamic_mapping_src_filter"], + "srcintf-filter": module.params["dynamic_mapping_srcintf_filter"], + "ssl-algorithm": module.params["dynamic_mapping_ssl_algorithm"], + "ssl-certificate": module.params["dynamic_mapping_ssl_certificate"], + "ssl-client-fallback": module.params["dynamic_mapping_ssl_client_fallback"], + "ssl-client-renegotiation": module.params["dynamic_mapping_ssl_client_renegotiation"], + "ssl-client-session-state-max": module.params["dynamic_mapping_ssl_client_session_state_max"], + "ssl-client-session-state-timeout": module.params["dynamic_mapping_ssl_client_session_state_timeout"], + "ssl-client-session-state-type": module.params["dynamic_mapping_ssl_client_session_state_type"], + "ssl-dh-bits": module.params["dynamic_mapping_ssl_dh_bits"], + "ssl-hpkp": module.params["dynamic_mapping_ssl_hpkp"], + "ssl-hpkp-age": module.params["dynamic_mapping_ssl_hpkp_age"], + "ssl-hpkp-backup": module.params["dynamic_mapping_ssl_hpkp_backup"], + "ssl-hpkp-include-subdomains": module.params["dynamic_mapping_ssl_hpkp_include_subdomains"], + "ssl-hpkp-primary": module.params["dynamic_mapping_ssl_hpkp_primary"], + "ssl-hpkp-report-uri": module.params["dynamic_mapping_ssl_hpkp_report_uri"], + "ssl-hsts": module.params["dynamic_mapping_ssl_hsts"], + "ssl-hsts-age": module.params["dynamic_mapping_ssl_hsts_age"], + "ssl-hsts-include-subdomains": module.params["dynamic_mapping_ssl_hsts_include_subdomains"], + "ssl-http-location-conversion": module.params["dynamic_mapping_ssl_http_location_conversion"], + "ssl-http-match-host": module.params["dynamic_mapping_ssl_http_match_host"], + "ssl-max-version": module.params["dynamic_mapping_ssl_max_version"], + "ssl-min-version": module.params["dynamic_mapping_ssl_min_version"], + "ssl-mode": module.params["dynamic_mapping_ssl_mode"], + "ssl-pfs": module.params["dynamic_mapping_ssl_pfs"], + "ssl-send-empty-frags": module.params["dynamic_mapping_ssl_send_empty_frags"], + "ssl-server-algorithm": module.params["dynamic_mapping_ssl_server_algorithm"], + "ssl-server-max-version": module.params["dynamic_mapping_ssl_server_max_version"], + "ssl-server-min-version": module.params["dynamic_mapping_ssl_server_min_version"], + "ssl-server-session-state-max": module.params["dynamic_mapping_ssl_server_session_state_max"], + "ssl-server-session-state-timeout": module.params["dynamic_mapping_ssl_server_session_state_timeout"], + "ssl-server-session-state-type": module.params["dynamic_mapping_ssl_server_session_state_type"], + "type": module.params["dynamic_mapping_type"], + "weblogic-server": module.params["dynamic_mapping_weblogic_server"], + "websphere-server": module.params["dynamic_mapping_websphere_server"], + "realservers": { + "client-ip": module.params["dynamic_mapping_realservers_client_ip"], + "healthcheck": module.params["dynamic_mapping_realservers_healthcheck"], + "holddown-interval": module.params["dynamic_mapping_realservers_holddown_interval"], + "http-host": module.params["dynamic_mapping_realservers_http_host"], + "ip": module.params["dynamic_mapping_realservers_ip"], + "max-connections": module.params["dynamic_mapping_realservers_max_connections"], + "monitor": module.params["dynamic_mapping_realservers_monitor"], + "port": module.params["dynamic_mapping_realservers_port"], + "seq": module.params["dynamic_mapping_realservers_seq"], + "status": module.params["dynamic_mapping_realservers_status"], + "weight": module.params["dynamic_mapping_realservers_weight"], + }, + "ssl-cipher-suites": { + "cipher": module.params["dynamic_mapping_ssl_cipher_suites_cipher"], + "versions": module.params["dynamic_mapping_ssl_cipher_suites_versions"], + }, + }, + "realservers": { + "client-ip": module.params["realservers_client_ip"], + "healthcheck": module.params["realservers_healthcheck"], + "holddown-interval": module.params["realservers_holddown_interval"], + "http-host": module.params["realservers_http_host"], + "ip": module.params["realservers_ip"], + "max-connections": module.params["realservers_max_connections"], + "monitor": module.params["realservers_monitor"], + "port": module.params["realservers_port"], + "seq": module.params["realservers_seq"], + "status": module.params["realservers_status"], + "weight": module.params["realservers_weight"], + }, + "ssl-cipher-suites": { + "cipher": module.params["ssl_cipher_suites_cipher"], + "versions": module.params["ssl_cipher_suites_versions"], + }, + "ssl-server-cipher-suites": { + "cipher": module.params["ssl_server_cipher_suites_cipher"], + "priority": module.params["ssl_server_cipher_suites_priority"], + "versions": module.params["ssl_server_cipher_suites_versions"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping', 'realservers', 'ssl-cipher-suites', 'ssl-server-cipher-suites'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_firewall_vip_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_ipv4.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_ipv4.py new file mode 100644 index 00000000..6cc057ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_ipv4.py @@ -0,0 +1,1355 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwpol_ipv4 +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the add/delete of Firewall Policies on Packages in FortiManager. +description: + - Allows the add/delete of Firewall Policies on Packages in FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + package_name: + description: + - The policy package you want to modify + required: false + default: "default" + + fail_on_missing_dependency: + description: + - Normal behavior is to "skip" tasks that fail dependency checks, so other tasks can run. + - If set to "enabled" if a failed dependency check happeens, Ansible will exit as with failure instead of skip. + required: false + default: "disable" + choices: ["enable", "disable"] + + wsso: + description: + - Enable/disable WiFi Single Sign On (WSSO). + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + webfilter_profile: + description: + - Name of an existing Web filter profile. + required: false + + webcache_https: + description: + - Enable/disable web cache for HTTPS. + - choice | disable | Disable web cache for HTTPS. + - choice | enable | Enable web cache for HTTPS. + required: false + choices: ["disable", "enable"] + + webcache: + description: + - Enable/disable web cache. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + wccp: + description: + - Enable/disable forwarding traffic matching this policy to a configured WCCP server. + - choice | disable | Disable WCCP setting. + - choice | enable | Enable WCCP setting. + required: false + choices: ["disable", "enable"] + + wanopt_profile: + description: + - WAN optimization profile. + required: false + + wanopt_peer: + description: + - WAN optimization peer. + required: false + + wanopt_passive_opt: + description: + - WAN optimization passive mode options. This option decides what IP address will be used to connect server. + - choice | default | Allow client side WAN opt peer to decide. + - choice | transparent | Use address of client to connect to server. + - choice | non-transparent | Use local FortiGate address to connect to server. + required: false + choices: ["default", "transparent", "non-transparent"] + + wanopt_detection: + description: + - WAN optimization auto-detection mode. + - choice | active | Active WAN optimization peer auto-detection. + - choice | passive | Passive WAN optimization peer auto-detection. + - choice | off | Turn off WAN optimization peer auto-detection. + required: false + choices: ["active", "passive", "off"] + + wanopt: + description: + - Enable/disable WAN optimization. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + waf_profile: + description: + - Name of an existing Web application firewall profile. + required: false + + vpntunnel: + description: + - Policy-based IPsec VPN | name of the IPsec VPN Phase 1. + required: false + + voip_profile: + description: + - Name of an existing VoIP profile. + required: false + + vlan_filter: + description: + - Set VLAN filters. + required: false + + vlan_cos_rev: + description: + - VLAN reverse direction user priority | 255 passthrough, 0 lowest, 7 highest.. + required: false + + vlan_cos_fwd: + description: + - VLAN forward direction user priority | 255 passthrough, 0 lowest, 7 highest. + required: false + + utm_status: + description: + - Enable to add one or more security profiles (AV, IPS, etc.) to the firewall policy. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + users: + description: + - Names of individual users that can authenticate with this policy. + required: false + + url_category: + description: + - URL category ID list. + required: false + + traffic_shaper_reverse: + description: + - Reverse traffic shaper. + required: false + + traffic_shaper: + description: + - Traffic shaper. + required: false + + timeout_send_rst: + description: + - Enable/disable sending RST packets when TCP sessions expire. + - choice | disable | Disable sending of RST packet upon TCP session expiration. + - choice | enable | Enable sending of RST packet upon TCP session expiration. + required: false + choices: ["disable", "enable"] + + tcp_session_without_syn: + description: + - Enable/disable creation of TCP session without SYN flag. + - choice | all | Enable TCP session without SYN. + - choice | data-only | Enable TCP session data only. + - choice | disable | Disable TCP session without SYN. + required: false + choices: ["all", "data-only", "disable"] + + tcp_mss_sender: + description: + - Sender TCP maximum segment size (MSS). + required: false + + tcp_mss_receiver: + description: + - Receiver TCP maximum segment size (MSS). + required: false + + status: + description: + - Enable or disable this policy. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ssl_ssh_profile: + description: + - Name of an existing SSL SSH profile. + required: false + + ssl_mirror_intf: + description: + - SSL mirror interface name. + required: false + + ssl_mirror: + description: + - Enable to copy decrypted SSL traffic to a FortiGate interface (called SSL mirroring). + - choice | disable | Disable SSL mirror. + - choice | enable | Enable SSL mirror. + required: false + choices: ["disable", "enable"] + + ssh_filter_profile: + description: + - Name of an existing SSH filter profile. + required: false + + srcintf: + description: + - Incoming (ingress) interface. + required: false + + srcaddr_negate: + description: + - When enabled srcaddr specifies what the source address must NOT be. + - choice | disable | Disable source address negate. + - choice | enable | Enable source address negate. + required: false + choices: ["disable", "enable"] + + srcaddr: + description: + - Source address and address group names. + required: false + + spamfilter_profile: + description: + - Name of an existing Spam filter profile. + required: false + + session_ttl: + description: + - TTL in seconds for sessions accepted by this policy (0 means use the system default session TTL). + required: false + + service_negate: + description: + - When enabled service specifies what the service must NOT be. + - choice | disable | Disable negated service match. + - choice | enable | Enable negated service match. + required: false + choices: ["disable", "enable"] + + service: + description: + - Service and service group names. + required: false + + send_deny_packet: + description: + - Enable to send a reply when a session is denied or blocked by a firewall policy. + - choice | disable | Disable deny-packet sending. + - choice | enable | Enable deny-packet sending. + required: false + choices: ["disable", "enable"] + + schedule_timeout: + description: + - Enable to force current sessions to end when the schedule object times out. + - choice | disable | Disable schedule timeout. + - choice | enable | Enable schedule timeout. + required: false + choices: ["disable", "enable"] + + schedule: + description: + - Schedule name. + required: false + + scan_botnet_connections: + description: + - Block or monitor connections to Botnet servers or disable Botnet scanning. + - choice | disable | Do not scan connections to botnet servers. + - choice | block | Block connections to botnet servers. + - choice | monitor | Log connections to botnet servers. + required: false + choices: ["disable", "block", "monitor"] + + rtp_nat: + description: + - Enable Real Time Protocol (RTP) NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + rtp_addr: + description: + - Address names if this is an RTP NAT policy. + required: false + + rsso: + description: + - Enable/disable RADIUS single sign-on (RSSO). + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + replacemsg_override_group: + description: + - Override the default replacement message group for this policy. + required: false + + redirect_url: + description: + - URL users are directed to after seeing and accepting the disclaimer or authenticating. + required: false + + radius_mac_auth_bypass: + description: + - Enable MAC authentication bypass. The bypassed MAC address must be received from RADIUS server. + - choice | disable | Disable MAC authentication bypass. + - choice | enable | Enable MAC authentication bypass. + required: false + choices: ["disable", "enable"] + + profile_type: + description: + - Determine whether the firewall policy allows security profile groups or single profiles only. + - choice | single | Do not allow security profile groups. + - choice | group | Allow security profile groups. + required: false + choices: ["single", "group"] + + profile_protocol_options: + description: + - Name of an existing Protocol options profile. + required: false + + profile_group: + description: + - Name of profile group. + required: false + + poolname: + description: + - IP Pool names. + required: false + + policyid: + description: + - Policy ID. + required: false + + permit_stun_host: + description: + - Accept UDP packets from any Session Traversal Utilities for NAT (STUN) host. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + permit_any_host: + description: + - Accept UDP packets from any host. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + per_ip_shaper: + description: + - Per-IP traffic shaper. + required: false + + outbound: + description: + - Policy-based IPsec VPN | only traffic from the internal network can initiate a VPN. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ntlm_guest: + description: + - Enable/disable NTLM guest user access. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ntlm_enabled_browsers: + description: + - HTTP-User-Agent value of supported browsers. + required: false + + ntlm: + description: + - Enable/disable NTLM authentication. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + np_acceleration: + description: + - Enable/disable UTM Network Processor acceleration. + - choice | disable | Disable UTM Network Processor acceleration. + - choice | enable | Enable UTM Network Processor acceleration. + required: false + choices: ["disable", "enable"] + + natoutbound: + description: + - Policy-based IPsec VPN | apply source NAT to outbound traffic. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + natip: + description: + - Policy-based IPsec VPN | source NAT IP address for outgoing traffic. + required: false + + natinbound: + description: + - Policy-based IPsec VPN | apply destination NAT to inbound traffic. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + nat: + description: + - Enable/disable source NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + name: + description: + - Policy name. + required: false + + mms_profile: + description: + - Name of an existing MMS profile. + required: false + + match_vip: + description: + - Enable to match packets that have had their destination addresses changed by a VIP. + - choice | disable | Do not match DNATed packet. + - choice | enable | Match DNATed packet. + required: false + choices: ["disable", "enable"] + + logtraffic_start: + description: + - Record logs when a session starts and ends. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + logtraffic: + description: + - Enable or disable logging. Log all sessions or security profile sessions. + - choice | disable | Disable all logging for this policy. + - choice | all | Log all sessions accepted or denied by this policy. + - choice | utm | Log traffic that has a security profile applied to it. + required: false + choices: ["disable", "all", "utm"] + + learning_mode: + description: + - Enable to allow everything, but log all of the meaningful data for security information gathering. + - choice | disable | Disable learning mode in firewall policy. + - choice | enable | Enable learning mode in firewall policy. + required: false + choices: ["disable", "enable"] + + label: + description: + - Label for the policy that appears when the GUI is in Section View mode. + required: false + + ips_sensor: + description: + - Name of an existing IPS sensor. + required: false + + ippool: + description: + - Enable to use IP Pools for source NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + internet_service_src_negate: + description: + - When enabled internet-service-src specifies what the service must NOT be. + - choice | disable | Disable negated Internet Service source match. + - choice | enable | Enable negated Internet Service source match. + required: false + choices: ["disable", "enable"] + + internet_service_src_id: + description: + - Internet Service source ID. + required: false + + internet_service_src_custom: + description: + - Custom Internet Service source name. + required: false + + internet_service_src: + description: + - Enable/disable use of Internet Services in source for this policy. If enabled, source address is not used. + - choice | disable | Disable use of Internet Services source in policy. + - choice | enable | Enable use of Internet Services source in policy. + required: false + choices: ["disable", "enable"] + + internet_service_negate: + description: + - When enabled internet-service specifies what the service must NOT be. + - choice | disable | Disable negated Internet Service match. + - choice | enable | Enable negated Internet Service match. + required: false + choices: ["disable", "enable"] + + internet_service_id: + description: + - Internet Service ID. + required: false + + internet_service_custom: + description: + - Custom Internet Service name. + required: false + + internet_service: + description: + - Enable/disable use of Internet Services for this policy. If enabled, dstaddr and service are not used. + - choice | disable | Disable use of Internet Services in policy. + - choice | enable | Enable use of Internet Services in policy. + required: false + choices: ["disable", "enable"] + + inbound: + description: + - Policy-based IPsec VPN | only traffic from the remote network can initiate a VPN. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + identity_based_route: + description: + - Name of identity-based routing rule. + required: false + + icap_profile: + description: + - Name of an existing ICAP profile. + required: false + + gtp_profile: + description: + - GTP profile. + required: false + + groups: + description: + - Names of user groups that can authenticate with this policy. + required: false + + global_label: + description: + - Label for the policy that appears when the GUI is in Global View mode. + required: false + + fsso_agent_for_ntlm: + description: + - FSSO agent to use for NTLM authentication. + required: false + + fsso: + description: + - Enable/disable Fortinet Single Sign-On. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + fixedport: + description: + - Enable to prevent source NAT from changing a session's source port. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + firewall_session_dirty: + description: + - How to handle sessions if the configuration of this firewall policy changes. + - choice | check-all | Flush all current sessions accepted by this policy. + - choice | check-new | Continue to allow sessions already accepted by this policy. + required: false + choices: ["check-all", "check-new"] + + dstintf: + description: + - Outgoing (egress) interface. + required: false + + dstaddr_negate: + description: + - When enabled dstaddr specifies what the destination address must NOT be. + - choice | disable | Disable destination address negate. + - choice | enable | Enable destination address negate. + required: false + choices: ["disable", "enable"] + + dstaddr: + description: + - Destination address and address group names. + required: false + + dsri: + description: + - Enable DSRI to ignore HTTP server responses. + - choice | disable | Disable DSRI. + - choice | enable | Enable DSRI. + required: false + choices: ["disable", "enable"] + + dscp_value: + description: + - DSCP value. + required: false + + dscp_negate: + description: + - Enable negated DSCP match. + - choice | disable | Disable DSCP negate. + - choice | enable | Enable DSCP negate. + required: false + choices: ["disable", "enable"] + + dscp_match: + description: + - Enable DSCP check. + - choice | disable | Disable DSCP check. + - choice | enable | Enable DSCP check. + required: false + choices: ["disable", "enable"] + + dnsfilter_profile: + description: + - Name of an existing DNS filter profile. + required: false + + dlp_sensor: + description: + - Name of an existing DLP sensor. + required: false + + disclaimer: + description: + - Enable/disable user authentication disclaimer. + - choice | disable | Disable user authentication disclaimer. + - choice | enable | Enable user authentication disclaimer. + required: false + choices: ["disable", "enable"] + + diffservcode_rev: + description: + - Change packet's reverse (reply) DiffServ to this value. + required: false + + diffservcode_forward: + description: + - Change packet's DiffServ to this value. + required: false + + diffserv_reverse: + description: + - Enable to change packet's reverse (reply) DiffServ values to the specified diffservcode-rev value. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + diffserv_forward: + description: + - Enable to change packet's DiffServ values to the specified diffservcode-forward value. + - choice | disable | Disable WAN optimization. + - choice | enable | Enable WAN optimization. + required: false + choices: ["disable", "enable"] + + devices: + description: + - Names of devices or device groups that can be matched by the policy. + required: false + + delay_tcp_npu_session: + description: + - Enable TCP NPU session delay to guarantee packet order of 3-way handshake. + - choice | disable | Disable TCP NPU session delay in order to guarantee packet order of 3-way handshake. + - choice | enable | Enable TCP NPU session delay in order to guarantee packet order of 3-way handshake. + required: false + choices: ["disable", "enable"] + + custom_log_fields: + description: + - Custom fields to append to log messages for this policy. + required: false + + comments: + description: + - Comment. + required: false + + capture_packet: + description: + - Enable/disable capture packets. + - choice | disable | Disable capture packets. + - choice | enable | Enable capture packets. + required: false + choices: ["disable", "enable"] + + captive_portal_exempt: + description: + - Enable to exempt some users from the captive portal. + - choice | disable | Disable exemption of captive portal. + - choice | enable | Enable exemption of captive portal. + required: false + choices: ["disable", "enable"] + + block_notification: + description: + - Enable/disable block notification. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + av_profile: + description: + - Name of an existing Antivirus profile. + required: false + + auto_asic_offload: + description: + - Enable/disable offloading security profile processing to CP processors. + - choice | disable | Disable ASIC offloading. + - choice | enable | Enable auto ASIC offloading. + required: false + choices: ["disable", "enable"] + + auth_redirect_addr: + description: + - HTTP-to-HTTPS redirect address for firewall authentication. + required: false + + auth_path: + description: + - Enable/disable authentication-based routing. + - choice | disable | Disable authentication-based routing. + - choice | enable | Enable authentication-based routing. + required: false + choices: ["disable", "enable"] + + auth_cert: + description: + - HTTPS server certificate for policy authentication. + required: false + + application_list: + description: + - Name of an existing Application list. + required: false + + application: + description: + - Application ID list. + required: false + + app_group: + description: + - Application group names. + required: false + + app_category: + description: + - Application category ID list. + required: false + + action: + description: + - Policy action (allow/deny/ipsec). + - choice | deny | Blocks sessions that match the firewall policy. + - choice | accept | Allows session that match the firewall policy. + - choice | ipsec | Firewall policy becomes a policy-based IPsec VPN policy. + required: false + choices: ["deny", "accept", "ipsec"] + + vpn_dst_node: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + required: false + + vpn_dst_node_host: + description: + - VPN Destination Node Host. + required: false + + vpn_dst_node_seq: + description: + - VPN Destination Node Seq. + required: false + + vpn_dst_node_subnet: + description: + - VPN Destination Node Seq. + required: false + + vpn_src_node: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + required: false + + vpn_src_node_host: + description: + - VPN Source Node Host. + required: false + + vpn_src_node_seq: + description: + - VPN Source Node Seq. + required: false + + vpn_src_node_subnet: + description: + - VPN Source Node. + required: false + + +''' + +EXAMPLES = ''' +- name: ADD VERY BASIC IPV4 POLICY WITH NO NAT (WIDE OPEN) + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy" + comments: "Created by Ansible" + action: "accept" + dstaddr: "all" + srcaddr: "all" + dstintf: "any" + srcintf: "any" + logtraffic: "utm" + service: "ALL" + schedule: "always" + +- name: ADD VERY BASIC IPV4 POLICY WITH NAT AND MULTIPLE ENTRIES + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy_2" + comments: "Created by Ansible" + action: "accept" + dstaddr: "google-play" + srcaddr: "all" + dstintf: "any" + srcintf: "any" + logtraffic: "utm" + service: "HTTP, HTTPS" + schedule: "always" + nat: "enable" + users: "karen, kevin" + +- name: ADD VERY BASIC IPV4 POLICY WITH NAT AND MULTIPLE ENTRIES AND SEC PROFILES + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy_3" + comments: "Created by Ansible" + action: "accept" + dstaddr: "google-play, autoupdate.opera.com" + srcaddr: "corp_internal" + dstintf: "zone_wan1, zone_wan2" + srcintf: "zone_int1" + logtraffic: "utm" + service: "HTTP, HTTPS" + schedule: "always" + nat: "enable" + users: "karen, kevin" + av_profile: "sniffer-profile" + ips_sensor: "default" + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_firewall_policy_modify(fmgr, paramgram): + """ + fmgr_firewall_policy -- Add/Set/Deletes Firewall Policy Objects defined in the "paramgram" + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall/policy'.format(adom=adom, pkg=paramgram["package_name"]) + datagram = scrub_dict((prepare_dict(paramgram))) + del datagram["package_name"] + datagram = fmgr._tools.split_comma_strings_into_lists(datagram) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall' \ + '/policy/{policyid}'.format(adom=paramgram["adom"], + pkg=paramgram["package_name"], + policyid=paramgram["policyid"]) + datagram = { + "policyid": paramgram["policyid"] + } + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + package_name=dict(type="str", required=False, default="default"), + fail_on_missing_dependency=dict(type="str", required=False, default="disable", choices=["enable", + "disable"]), + wsso=dict(required=False, type="str", choices=["disable", "enable"]), + webfilter_profile=dict(required=False, type="str"), + webcache_https=dict(required=False, type="str", choices=["disable", "enable"]), + webcache=dict(required=False, type="str", choices=["disable", "enable"]), + wccp=dict(required=False, type="str", choices=["disable", "enable"]), + wanopt_profile=dict(required=False, type="str"), + wanopt_peer=dict(required=False, type="str"), + wanopt_passive_opt=dict(required=False, type="str", choices=["default", "transparent", "non-transparent"]), + wanopt_detection=dict(required=False, type="str", choices=["active", "passive", "off"]), + wanopt=dict(required=False, type="str", choices=["disable", "enable"]), + waf_profile=dict(required=False, type="str"), + vpntunnel=dict(required=False, type="str"), + voip_profile=dict(required=False, type="str"), + vlan_filter=dict(required=False, type="str"), + vlan_cos_rev=dict(required=False, type="int"), + vlan_cos_fwd=dict(required=False, type="int"), + utm_status=dict(required=False, type="str", choices=["disable", "enable"]), + users=dict(required=False, type="str"), + url_category=dict(required=False, type="str"), + traffic_shaper_reverse=dict(required=False, type="str"), + traffic_shaper=dict(required=False, type="str"), + timeout_send_rst=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_session_without_syn=dict(required=False, type="str", choices=["all", "data-only", "disable"]), + tcp_mss_sender=dict(required=False, type="int"), + tcp_mss_receiver=dict(required=False, type="int"), + status=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_ssh_profile=dict(required=False, type="str"), + ssl_mirror_intf=dict(required=False, type="str"), + ssl_mirror=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_filter_profile=dict(required=False, type="str"), + srcintf=dict(required=False, type="str"), + srcaddr_negate=dict(required=False, type="str", choices=["disable", "enable"]), + srcaddr=dict(required=False, type="str"), + spamfilter_profile=dict(required=False, type="str"), + session_ttl=dict(required=False, type="int"), + service_negate=dict(required=False, type="str", choices=["disable", "enable"]), + service=dict(required=False, type="str"), + send_deny_packet=dict(required=False, type="str", choices=["disable", "enable"]), + schedule_timeout=dict(required=False, type="str", choices=["disable", "enable"]), + schedule=dict(required=False, type="str"), + scan_botnet_connections=dict(required=False, type="str", choices=["disable", "block", "monitor"]), + rtp_nat=dict(required=False, type="str", choices=["disable", "enable"]), + rtp_addr=dict(required=False, type="str"), + rsso=dict(required=False, type="str", choices=["disable", "enable"]), + replacemsg_override_group=dict(required=False, type="str"), + redirect_url=dict(required=False, type="str"), + radius_mac_auth_bypass=dict(required=False, type="str", choices=["disable", "enable"]), + profile_type=dict(required=False, type="str", choices=["single", "group"]), + profile_protocol_options=dict(required=False, type="str"), + profile_group=dict(required=False, type="str"), + poolname=dict(required=False, type="str"), + policyid=dict(required=False, type="str"), + permit_stun_host=dict(required=False, type="str", choices=["disable", "enable"]), + permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + per_ip_shaper=dict(required=False, type="str"), + outbound=dict(required=False, type="str", choices=["disable", "enable"]), + ntlm_guest=dict(required=False, type="str", choices=["disable", "enable"]), + ntlm_enabled_browsers=dict(required=False, type="str"), + ntlm=dict(required=False, type="str", choices=["disable", "enable"]), + np_acceleration=dict(required=False, type="str", choices=["disable", "enable"]), + natoutbound=dict(required=False, type="str", choices=["disable", "enable"]), + natip=dict(required=False, type="str"), + natinbound=dict(required=False, type="str", choices=["disable", "enable"]), + nat=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + mms_profile=dict(required=False, type="str"), + match_vip=dict(required=False, type="str", choices=["disable", "enable"]), + logtraffic_start=dict(required=False, type="str", choices=["disable", "enable"]), + logtraffic=dict(required=False, type="str", choices=["disable", "all", "utm"]), + learning_mode=dict(required=False, type="str", choices=["disable", "enable"]), + label=dict(required=False, type="str"), + ips_sensor=dict(required=False, type="str"), + ippool=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_src_negate=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_src_id=dict(required=False, type="str"), + internet_service_src_custom=dict(required=False, type="str"), + internet_service_src=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_negate=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_id=dict(required=False, type="str"), + internet_service_custom=dict(required=False, type="str"), + internet_service=dict(required=False, type="str", choices=["disable", "enable"]), + inbound=dict(required=False, type="str", choices=["disable", "enable"]), + identity_based_route=dict(required=False, type="str"), + icap_profile=dict(required=False, type="str"), + gtp_profile=dict(required=False, type="str"), + groups=dict(required=False, type="str"), + global_label=dict(required=False, type="str"), + fsso_agent_for_ntlm=dict(required=False, type="str"), + fsso=dict(required=False, type="str", choices=["disable", "enable"]), + fixedport=dict(required=False, type="str", choices=["disable", "enable"]), + firewall_session_dirty=dict(required=False, type="str", choices=["check-all", "check-new"]), + dstintf=dict(required=False, type="str"), + dstaddr_negate=dict(required=False, type="str", choices=["disable", "enable"]), + dstaddr=dict(required=False, type="str"), + dsri=dict(required=False, type="str", choices=["disable", "enable"]), + dscp_value=dict(required=False, type="str"), + dscp_negate=dict(required=False, type="str", choices=["disable", "enable"]), + dscp_match=dict(required=False, type="str", choices=["disable", "enable"]), + dnsfilter_profile=dict(required=False, type="str"), + dlp_sensor=dict(required=False, type="str"), + disclaimer=dict(required=False, type="str", choices=["disable", "enable"]), + diffservcode_rev=dict(required=False, type="str"), + diffservcode_forward=dict(required=False, type="str"), + diffserv_reverse=dict(required=False, type="str", choices=["disable", "enable"]), + diffserv_forward=dict(required=False, type="str", choices=["disable", "enable"]), + devices=dict(required=False, type="str"), + delay_tcp_npu_session=dict(required=False, type="str", choices=["disable", "enable"]), + custom_log_fields=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + capture_packet=dict(required=False, type="str", choices=["disable", "enable"]), + captive_portal_exempt=dict(required=False, type="str", choices=["disable", "enable"]), + block_notification=dict(required=False, type="str", choices=["disable", "enable"]), + av_profile=dict(required=False, type="str"), + auto_asic_offload=dict(required=False, type="str", choices=["disable", "enable"]), + auth_redirect_addr=dict(required=False, type="str"), + auth_path=dict(required=False, type="str", choices=["disable", "enable"]), + auth_cert=dict(required=False, type="str"), + application_list=dict(required=False, type="str"), + application=dict(required=False, type="str"), + app_group=dict(required=False, type="str"), + app_category=dict(required=False, type="str"), + action=dict(required=False, type="str", choices=["deny", "accept", "ipsec"]), + vpn_dst_node=dict(required=False, type="list"), + vpn_dst_node_host=dict(required=False, type="str"), + vpn_dst_node_seq=dict(required=False, type="str"), + vpn_dst_node_subnet=dict(required=False, type="str"), + vpn_src_node=dict(required=False, type="list"), + vpn_src_node_host=dict(required=False, type="str"), + vpn_src_node_seq=dict(required=False, type="str"), + vpn_src_node_subnet=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "package_name": module.params["package_name"], + "wsso": module.params["wsso"], + "webfilter-profile": module.params["webfilter_profile"], + "webcache-https": module.params["webcache_https"], + "webcache": module.params["webcache"], + "wccp": module.params["wccp"], + "wanopt-profile": module.params["wanopt_profile"], + "wanopt-peer": module.params["wanopt_peer"], + "wanopt-passive-opt": module.params["wanopt_passive_opt"], + "wanopt-detection": module.params["wanopt_detection"], + "wanopt": module.params["wanopt"], + "waf-profile": module.params["waf_profile"], + "vpntunnel": module.params["vpntunnel"], + "voip-profile": module.params["voip_profile"], + "vlan-filter": module.params["vlan_filter"], + "vlan-cos-rev": module.params["vlan_cos_rev"], + "vlan-cos-fwd": module.params["vlan_cos_fwd"], + "utm-status": module.params["utm_status"], + "users": module.params["users"], + "url-category": module.params["url_category"], + "traffic-shaper-reverse": module.params["traffic_shaper_reverse"], + "traffic-shaper": module.params["traffic_shaper"], + "timeout-send-rst": module.params["timeout_send_rst"], + "tcp-session-without-syn": module.params["tcp_session_without_syn"], + "tcp-mss-sender": module.params["tcp_mss_sender"], + "tcp-mss-receiver": module.params["tcp_mss_receiver"], + "status": module.params["status"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "ssl-mirror-intf": module.params["ssl_mirror_intf"], + "ssl-mirror": module.params["ssl_mirror"], + "ssh-filter-profile": module.params["ssh_filter_profile"], + "srcintf": module.params["srcintf"], + "srcaddr-negate": module.params["srcaddr_negate"], + "srcaddr": module.params["srcaddr"], + "spamfilter-profile": module.params["spamfilter_profile"], + "session-ttl": module.params["session_ttl"], + "service-negate": module.params["service_negate"], + "service": module.params["service"], + "send-deny-packet": module.params["send_deny_packet"], + "schedule-timeout": module.params["schedule_timeout"], + "schedule": module.params["schedule"], + "scan-botnet-connections": module.params["scan_botnet_connections"], + "rtp-nat": module.params["rtp_nat"], + "rtp-addr": module.params["rtp_addr"], + "rsso": module.params["rsso"], + "replacemsg-override-group": module.params["replacemsg_override_group"], + "redirect-url": module.params["redirect_url"], + "radius-mac-auth-bypass": module.params["radius_mac_auth_bypass"], + "profile-type": module.params["profile_type"], + "profile-protocol-options": module.params["profile_protocol_options"], + "profile-group": module.params["profile_group"], + "poolname": module.params["poolname"], + "policyid": module.params["policyid"], + "permit-stun-host": module.params["permit_stun_host"], + "permit-any-host": module.params["permit_any_host"], + "per-ip-shaper": module.params["per_ip_shaper"], + "outbound": module.params["outbound"], + "ntlm-guest": module.params["ntlm_guest"], + "ntlm-enabled-browsers": module.params["ntlm_enabled_browsers"], + "ntlm": module.params["ntlm"], + "np-acceleration": module.params["np_acceleration"], + "natoutbound": module.params["natoutbound"], + "natip": module.params["natip"], + "natinbound": module.params["natinbound"], + "nat": module.params["nat"], + "name": module.params["name"], + "mms-profile": module.params["mms_profile"], + "match-vip": module.params["match_vip"], + "logtraffic-start": module.params["logtraffic_start"], + "logtraffic": module.params["logtraffic"], + "learning-mode": module.params["learning_mode"], + "label": module.params["label"], + "ips-sensor": module.params["ips_sensor"], + "ippool": module.params["ippool"], + "internet-service-src-negate": module.params["internet_service_src_negate"], + "internet-service-src-id": module.params["internet_service_src_id"], + "internet-service-src-custom": module.params["internet_service_src_custom"], + "internet-service-src": module.params["internet_service_src"], + "internet-service-negate": module.params["internet_service_negate"], + "internet-service-id": module.params["internet_service_id"], + "internet-service-custom": module.params["internet_service_custom"], + "internet-service": module.params["internet_service"], + "inbound": module.params["inbound"], + "identity-based-route": module.params["identity_based_route"], + "icap-profile": module.params["icap_profile"], + "gtp-profile": module.params["gtp_profile"], + "groups": module.params["groups"], + "global-label": module.params["global_label"], + "fsso-agent-for-ntlm": module.params["fsso_agent_for_ntlm"], + "fsso": module.params["fsso"], + "fixedport": module.params["fixedport"], + "firewall-session-dirty": module.params["firewall_session_dirty"], + "dstintf": module.params["dstintf"], + "dstaddr-negate": module.params["dstaddr_negate"], + "dstaddr": module.params["dstaddr"], + "dsri": module.params["dsri"], + "dscp-value": module.params["dscp_value"], + "dscp-negate": module.params["dscp_negate"], + "dscp-match": module.params["dscp_match"], + "dnsfilter-profile": module.params["dnsfilter_profile"], + "dlp-sensor": module.params["dlp_sensor"], + "disclaimer": module.params["disclaimer"], + "diffservcode-rev": module.params["diffservcode_rev"], + "diffservcode-forward": module.params["diffservcode_forward"], + "diffserv-reverse": module.params["diffserv_reverse"], + "diffserv-forward": module.params["diffserv_forward"], + "devices": module.params["devices"], + "delay-tcp-npu-session": module.params["delay_tcp_npu_session"], + "custom-log-fields": module.params["custom_log_fields"], + "comments": module.params["comments"], + "capture-packet": module.params["capture_packet"], + "captive-portal-exempt": module.params["captive_portal_exempt"], + "block-notification": module.params["block_notification"], + "av-profile": module.params["av_profile"], + "auto-asic-offload": module.params["auto_asic_offload"], + "auth-redirect-addr": module.params["auth_redirect_addr"], + "auth-path": module.params["auth_path"], + "auth-cert": module.params["auth_cert"], + "application-list": module.params["application_list"], + "application": module.params["application"], + "app-group": module.params["app_group"], + "app-category": module.params["app_category"], + "action": module.params["action"], + "vpn_dst_node": { + "host": module.params["vpn_dst_node_host"], + "seq": module.params["vpn_dst_node_seq"], + "subnet": module.params["vpn_dst_node_subnet"], + }, + "vpn_src_node": { + "host": module.params["vpn_src_node_host"], + "seq": module.params["vpn_src_node_seq"], + "subnet": module.params["vpn_src_node_subnet"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['vpn_dst_node', 'vpn_src_node'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["mode"] == "delete": + # WE NEED TO GET THE POLICY ID FROM THE NAME OF THE POLICY TO DELETE IT + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall' \ + '/policy/'.format(adom=paramgram["adom"], + pkg=paramgram["package_name"]) + datagram = { + "filter": ["name", "==", paramgram["name"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + try: + if response[1][0]["policyid"]: + policy_id = response[1][0]["policyid"] + paramgram["policyid"] = policy_id + except BaseException: + fmgr.return_response(module=module, results=response, good_codes=[0, ], stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram), + msg="Couldn't find policy ID number for policy name specified.") + except Exception as err: + raise FMGBaseException(err) + + try: + results = fmgr_firewall_policy_modify(fmgr, paramgram) + if module.params["fail_on_missing_dependency"] == "disable": + fmgr.govern_response(module=module, results=results, good_codes=[0, -9998], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + if module.params["fail_on_missing_dependency"] == "enable" and results[0] == -10131: + fmgr.govern_response(module=module, results=results, good_codes=[0, ], failed=True, skipped=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_package.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_package.py new file mode 100644 index 00000000..37e9bc9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_fwpol_package.py @@ -0,0 +1,479 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwpol_package +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages FortiManager Firewall Policies Packages. +description: + - Manages FortiManager Firewall Policies Packages. Policy Packages contain one or more Firewall Policies/Rules and + are distritbuted via FortiManager to Fortigates. + - This module controls the creation/edit/delete/assign of these packages. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + + name: + description: + - Name of the FortiManager package or folder. + required: True + + object_type: + description: + - Are we managing packages or folders, or installing packages? + required: True + choices: ['pkg','folder','install'] + + package_folder: + description: + - Name of the folder you want to put the package into. + required: false + + central_nat: + description: + - Central NAT setting. + required: false + choices: ['enable', 'disable'] + default: disable + + fwpolicy_implicit_log: + description: + - Implicit Log setting for all IPv4 policies in package. + required: false + choices: ['enable', 'disable'] + default: disable + + fwpolicy6_implicit_log: + description: + - Implicit Log setting for all IPv6 policies in package. + required: false + choices: ['enable', 'disable'] + default: disable + + inspection_mode: + description: + - Inspection mode setting for the policies flow or proxy. + required: false + choices: ['flow', 'proxy'] + default: flow + + ngfw_mode: + description: + - NGFW mode setting for the policies flow or proxy. + required: false + choices: ['profile-based', 'policy-based'] + default: profile-based + + ssl_ssh_profile: + description: + - if policy-based ngfw-mode, refer to firewall ssl-ssh-profile. + required: false + + scope_members: + description: + - The devices or scope that you want to assign this policy package to. + required: false + + scope_members_vdom: + description: + - The members VDOM you want to assign the package to. + required: false + default: root + + parent_folder: + description: + - The parent folder name you want to add this object under. + required: false + +''' + + +EXAMPLES = ''' +- name: CREATE BASIC POLICY PACKAGE + community.network.fmgr_fwpol_package: + adom: "ansible" + mode: "add" + name: "testPackage" + object_type: "pkg" + +- name: ADD PACKAGE WITH TARGETS + community.network.fmgr_fwpol_package: + mode: "add" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "pkg" + inspection_mode: "flow" + ngfw_mode: "profile-based" + scope_members: "seattle-fgt02, seattle-fgt03" + +- name: ADD FOLDER + community.network.fmgr_fwpol_package: + mode: "add" + adom: "ansible" + name: "ansibleTestFolder1" + object_type: "folder" + +- name: ADD PACKAGE INTO PARENT FOLDER + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestPackage2" + object_type: "pkg" + parent_folder: "ansibleTestFolder1" + +- name: ADD FOLDER INTO PARENT FOLDER + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestFolder2" + object_type: "folder" + parent_folder: "ansibleTestFolder1" + +- name: INSTALL PACKAGE + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "install" + scope_members: "seattle-fgt03, seattle-fgt02" + +- name: REMOVE PACKAGE + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "pkg" + +- name: REMOVE NESTED PACKAGE + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestPackage2" + object_type: "pkg" + parent_folder: "ansibleTestFolder1" + +- name: REMOVE NESTED FOLDER + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestFolder2" + object_type: "folder" + parent_folder: "ansibleTestFolder1" + +- name: REMOVE FOLDER + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestFolder1" + object_type: "folder" +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods + + +def fmgr_fwpol_package(fmgr, paramgram): + """ + This function will create FMGR Firewall Policy Packages, or delete them. It is also capable of assigning packages. + This function DOES NOT install the package. See the function fmgr_fwpol_package_install() + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"]) + members_list = [] + + # CHECK FOR SCOPE MEMBERS AND CREATE THAT DICT + if paramgram["scope_members"] is not None: + members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"]) + for member in members: + scope_dict = { + "name": member, + "vdom": paramgram["scope_members_vdom"], + } + members_list.append(scope_dict) + + # IF PARENT FOLDER IS NOT DEFINED + if paramgram["parent_folder"] is None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["name"], + "scope member": members_list, + "package settings": { + "central-nat": paramgram["central-nat"], + "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"], + "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"], + "inspection-mode": paramgram["inspection-mode"], + "ngfw-mode": paramgram["ngfw-mode"], + } + } + + if paramgram["ngfw-mode"] == "policy-based" and paramgram["ssl-ssh-profile"] is not None: + datagram["package settings"]["ssl-ssh-profile"] = paramgram["ssl-ssh-profile"] + + # IF PARENT FOLDER IS DEFINED + if paramgram["parent_folder"] is not None: + datagram = { + "type": "folder", + "name": paramgram["parent_folder"], + "subobj": [{ + "name": paramgram["name"], + "scope member": members_list, + "type": "pkg", + "package settings": { + "central-nat": paramgram["central-nat"], + "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"], + "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"], + "inspection-mode": paramgram["inspection-mode"], + "ngfw-mode": paramgram["ngfw-mode"], + } + }] + } + + # NORMAL DELETE NO PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + # DELETE WITH PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"], + parent_folder=paramgram["parent_folder"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwpol_package_folder(fmgr, paramgram): + """ + This function will create folders for firewall packages. It can create down to two levels deep. + We haven't yet tested for any more layers below two levels. + parent_folders for multiple levels may need to defined as "level1/level2/level3" for the URL parameters and such. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"]) + # IF PARENT FOLDER IS NOT DEFINED + if paramgram["parent_folder"] is None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["name"], + } + + # IF PARENT FOLDER IS DEFINED + if paramgram["parent_folder"] is not None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["parent_folder"], + "subobj": [{ + "name": paramgram["name"], + "type": paramgram["object_type"], + + }] + } + # NORMAL DELETE NO PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + # DELETE WITH PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"], + parent_folder=paramgram["parent_folder"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwpol_package_install(fmgr, paramgram): + """ + This method/function installs FMGR FW Policy Packages to the scope members defined in the playbook. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + # INIT BLANK MEMBERS LIST + members_list = [] + # USE THE PARSE CSV FUNCTION TO GET A LIST FORMAT OF THE MEMBERS + members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"]) + # USE THAT LIST TO BUILD THE DICTIONARIES NEEDED, AND ADD TO THE BLANK MEMBERS LIST + for member in members: + scope_dict = { + "name": member, + "vdom": paramgram["scope_members_vdom"], + } + members_list.append(scope_dict) + # THEN FOR THE DATAGRAM, USING THE MEMBERS LIST CREATED ABOVE + datagram = { + "adom": paramgram["adom"], + "pkg": paramgram["name"], + "scope": members_list + } + # EXECUTE THE INSTALL REQUEST + url = '/securityconsole/install/package' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete"], type="str", default="add"), + + name=dict(required=False, type="str"), + object_type=dict(required=True, type="str", choices=['pkg', 'folder', 'install']), + package_folder=dict(required=False, type="str"), + central_nat=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + fwpolicy_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + fwpolicy6_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + inspection_mode=dict(required=False, type="str", default="flow", choices=['flow', 'proxy']), + ngfw_mode=dict(required=False, type="str", default="profile-based", choices=['profile-based', 'policy-based']), + ssl_ssh_profile=dict(required=False, type="str"), + scope_members=dict(required=False, type="str"), + scope_members_vdom=dict(required=False, type="str", default="root"), + parent_folder=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE DATAGRAM + paramgram = { + "adom": module.params["adom"], + "name": module.params["name"], + "mode": module.params["mode"], + "object_type": module.params["object_type"], + "package-folder": module.params["package_folder"], + "central-nat": module.params["central_nat"], + "fwpolicy-implicit-log": module.params["fwpolicy_implicit_log"], + "fwpolicy6-implicit-log": module.params["fwpolicy6_implicit_log"], + "inspection-mode": module.params["inspection_mode"], + "ngfw-mode": module.params["ngfw_mode"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "scope_members": module.params["scope_members"], + "scope_members_vdom": module.params["scope_members_vdom"], + "parent_folder": module.params["parent_folder"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["object_type"] == "pkg": + results = fmgr_fwpol_package(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE object_type IS FOLDER LETS RUN THAT METHOD + if paramgram["object_type"] == "folder": + results = fmgr_fwpol_package_folder(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE object_type IS INSTALL AND NEEDED PARAMETERS ARE DEFINED INSTALL THE PACKAGE + if paramgram["scope_members"] is not None and paramgram["name"] is not None and\ + paramgram["object_type"] == "install": + results = fmgr_fwpol_package_install(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_ha.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_ha.py new file mode 100644 index 00000000..e0f7a7a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_ha.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_ha +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages the High-Availability State of FortiManager Clusters and Nodes. +description: Change HA state or settings of FortiManager nodes (Standalone/Master/Slave). + +options: + fmgr_ha_mode: + description: + - Sets the role of the FortiManager host for HA. + required: false + choices: ["standalone", "master", "slave"] + + fmgr_ha_peer_ipv4: + description: + - Sets the IPv4 address of a HA peer. + required: false + + fmgr_ha_peer_ipv6: + description: + - Sets the IPv6 address of a HA peer. + required: false + + fmgr_ha_peer_sn: + description: + - Sets the HA Peer Serial Number. + required: false + + fmgr_ha_peer_status: + description: + - Sets the peer status to enable or disable. + required: false + choices: ["enable", "disable"] + + fmgr_ha_cluster_pw: + description: + - Sets the password for the HA cluster. Only required once. System remembers between HA mode switches. + required: false + + fmgr_ha_cluster_id: + description: + - Sets the ID number of the HA cluster. Defaults to 1. + required: false + default: 1 + + fmgr_ha_hb_threshold: + description: + - Sets heartbeat lost threshold (1-255). + required: false + default: 3 + + fmgr_ha_hb_interval: + description: + - Sets the heartbeat interval (1-255). + required: false + default: 5 + + fmgr_ha_file_quota: + description: + - Sets the File quota in MB (2048-20480). + required: false + default: 4096 +''' + + +EXAMPLES = ''' +- name: SET FORTIMANAGER HA NODE TO MASTER + community.network.fmgr_ha: + fmgr_ha_mode: "master" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + +- name: SET FORTIMANAGER HA NODE TO SLAVE + community.network.fmgr_ha: + fmgr_ha_mode: "slave" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + +- name: SET FORTIMANAGER HA NODE TO STANDALONE + community.network.fmgr_ha: + fmgr_ha_mode: "standalone" + +- name: ADD FORTIMANAGER HA PEER + community.network.fmgr_ha: + fmgr_ha_peer_ipv4: "192.168.1.254" + fmgr_ha_peer_sn: "FMG-VM1234567890" + fmgr_ha_peer_status: "enable" + +- name: CREATE CLUSTER ON MASTER + community.network.fmgr_ha: + fmgr_ha_mode: "master" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + fmgr_ha_hb_threshold: "10" + fmgr_ha_hb_interval: "15" + fmgr_ha_file_quota: "2048" +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_set_ha_mode(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + if paramgram["fmgr_ha_cluster_pw"] is not None and str(paramgram["fmgr_ha_mode"].lower()) != "standalone": + datagram = { + "mode": paramgram["fmgr_ha_mode"], + "file-quota": paramgram["fmgr_ha_file_quota"], + "hb-interval": paramgram["fmgr_ha_hb_interval"], + "hb-lost-threshold": paramgram["fmgr_ha_hb_threshold"], + "password": paramgram["fmgr_ha_cluster_pw"], + "clusterid": paramgram["fmgr_ha_cluster_id"] + } + elif str(paramgram["fmgr_ha_mode"].lower()) == "standalone": + datagram = { + "mode": paramgram["fmgr_ha_mode"], + "file-quota": paramgram["fmgr_ha_file_quota"], + "hb-interval": paramgram["fmgr_ha_hb_interval"], + "hb-lost-threshold": paramgram["fmgr_ha_hb_threshold"], + "clusterid": paramgram["fmgr_ha_cluster_id"] + } + + url = '/cli/global/system/ha' + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def fmgr_get_ha_peer_list(fmgr): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + + datagram = {} + paramgram = {} + + url = '/cli/global/system/ha/peer/' + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_set_ha_peer(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "ip": paramgram["fmgr_ha_peer_ipv4"], + "ip6": paramgram["fmgr_ha_peer_ipv6"], + "serial-number": paramgram["fmgr_ha_peer_sn"], + "status": paramgram["fmgr_ha_peer_status"], + "id": paramgram["peer_id"] + } + + url = '/cli/global/system/ha/peer/' + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def main(): + argument_spec = dict( + fmgr_ha_mode=dict(required=False, type="str", choices=["standalone", "master", "slave"]), + fmgr_ha_cluster_pw=dict(required=False, type="str", no_log=True), + fmgr_ha_peer_status=dict(required=False, type="str", choices=["enable", "disable"]), + fmgr_ha_peer_sn=dict(required=False, type="str"), + fmgr_ha_peer_ipv4=dict(required=False, type="str"), + fmgr_ha_peer_ipv6=dict(required=False, type="str"), + fmgr_ha_hb_threshold=dict(required=False, type="int", default=3), + fmgr_ha_hb_interval=dict(required=False, type="int", default=5), + fmgr_ha_file_quota=dict(required=False, type="int", default=4096), + fmgr_ha_cluster_id=dict(required=False, type="int", default=1) + ) + + required_if = [ + ['fmgr_ha_peer_ipv4', 'present', ['fmgr_ha_peer_sn', 'fmgr_ha_peer_status']], + ['fmgr_ha_peer_ipv6', 'present', ['fmgr_ha_peer_sn', 'fmgr_ha_peer_status']], + ['fmgr_ha_mode', 'master', ['fmgr_ha_cluster_pw', 'fmgr_ha_cluster_id']], + ['fmgr_ha_mode', 'slave', ['fmgr_ha_cluster_pw', 'fmgr_ha_cluster_id']], + ] + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_if=required_if) + paramgram = { + "fmgr_ha_mode": module.params["fmgr_ha_mode"], + "fmgr_ha_cluster_pw": module.params["fmgr_ha_cluster_pw"], + "fmgr_ha_peer_status": module.params["fmgr_ha_peer_status"], + "fmgr_ha_peer_sn": module.params["fmgr_ha_peer_sn"], + "fmgr_ha_peer_ipv4": module.params["fmgr_ha_peer_ipv4"], + "fmgr_ha_peer_ipv6": module.params["fmgr_ha_peer_ipv6"], + "fmgr_ha_hb_threshold": module.params["fmgr_ha_hb_threshold"], + "fmgr_ha_hb_interval": module.params["fmgr_ha_hb_interval"], + "fmgr_ha_file_quota": module.params["fmgr_ha_file_quota"], + "fmgr_ha_cluster_id": module.params["fmgr_ha_cluster_id"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # INIT FLAGS AND COUNTERS + get_ha_peers = 0 + results = DEFAULT_RESULT_OBJ + try: + if any(v is not None for v in (paramgram["fmgr_ha_peer_sn"], paramgram["fmgr_ha_peer_ipv4"], + paramgram["fmgr_ha_peer_ipv6"], paramgram["fmgr_ha_peer_status"])): + get_ha_peers = 1 + except Exception as err: + raise FMGBaseException(err) + try: + # IF HA MODE IS NOT NULL, SWITCH THAT + if paramgram["fmgr_ha_mode"] is not None: + if (str.lower(paramgram["fmgr_ha_mode"]) != "standalone" and paramgram["fmgr_ha_cluster_pw"] is not None)\ + or str.lower(paramgram["fmgr_ha_mode"]) == "standalone": + results = fmgr_set_ha_mode(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + elif str.lower(paramgram["fmgr_ha_mode"]) != "standalone" and\ + paramgram["fmgr_ha_mode"] is not None and\ + paramgram["fmgr_ha_cluster_pw"] is None: + module.exit_json(msg="If setting HA Mode of MASTER or SLAVE, you must specify a cluster password") + + except Exception as err: + raise FMGBaseException(err) + # IF GET_HA_PEERS IS ENABLED, LETS PROCESS THE PEERS + try: + if get_ha_peers == 1: + # GET THE CURRENT LIST OF PEERS FROM THE NODE + peers = fmgr_get_ha_peer_list(fmgr) + # GET LENGTH OF RETURNED PEERS LIST AND ADD ONE FOR THE NEXT ID + paramgram["next_peer_id"] = len(peers[1]) + 1 + # SET THE ACTUAL NUMBER OF PEERS + num_of_peers = len(peers[1]) + # SET THE PEER ID FOR DISABLE METHOD + paramgram["peer_id"] = len(peers) - 1 + # SET THE PEER LOOPCOUNT TO 1 TO START THE LOOP + peer_loopcount = 1 + + # LOOP THROUGH PEERS TO FIND THE SERIAL NUMBER MATCH TO GET THE RIGHT PEER ID + # IDEA BEING WE DON'T WANT TO SUBMIT A BAD peer_id THAT DOESN'T JIVE WITH CURRENT DB ON FMG + # SO LETS SEARCH FOR IT, AND IF WE FIND IT, WE WILL CHANGE THE PEER ID VARIABLES TO MATCH + # IF NOT FOUND, LIFE GOES ON AND WE ASSUME THAT WE'RE ADDING A PEER + # AT WHICH POINT THE next_peer_id VARIABLE WILL HAVE THE RIGHT PRIMARY KEY + + if paramgram["fmgr_ha_peer_sn"] is not None: + while peer_loopcount <= num_of_peers: + # GET THE SERIAL NUMBER FOR CURRENT PEER IN LOOP TO COMPARE TO SN IN PLAYBOOK + try: + sn_compare = peers[1][peer_loopcount - 1]["serial-number"] + # IF THE SN IN THE PEERS MATCHES THE PLAYBOOK SN, SET THE IDS + if sn_compare == paramgram["fmgr_ha_peer_sn"]: + paramgram["peer_id"] = peer_loopcount + paramgram["next_peer_id"] = paramgram["peer_id"] + except Exception as err: + raise FMGBaseException(err) + # ADVANCE THE LOOP AND REPEAT UNTIL DONE + peer_loopcount += 1 + + # IF THE PEER STATUS ISN'T IN THE PLAYBOOK, ASSUME ITS ENABLE + if paramgram["fmgr_ha_peer_status"] is None: + paramgram["fmgr_ha_peer_status"] = "enable" + + # IF THE PEER STATUS IS ENABLE, USE THE next_peer_id IN THE API CALL FOR THE ID + if paramgram["fmgr_ha_peer_status"] == "enable": + results = fmgr_set_ha_peer(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, + module.params, paramgram)) + + # IF THE PEER STATUS IS DISABLE, WE HAVE TO HANDLE THAT A BIT DIFFERENTLY + # JUST USING TWO DIFFERENT peer_id 's HERE + if paramgram["fmgr_ha_peer_status"] == "disable": + results = fmgr_set_ha_peer(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_provisioning.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_provisioning.py new file mode 100644 index 00000000..3884a12d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_provisioning.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_provisioning +author: Andrew Welsh (@Ghilli3) +short_description: Provision devices via FortiMananger +description: + - Add model devices on the FortiManager using jsonrpc API and have them pre-configured, + so when central management is configured, the configuration is pushed down to the + registering devices + +options: + adom: + description: + - The administrative domain (admon) the configuration belongs to + required: true + vdom: + description: + - The virtual domain (vdom) the configuration belongs to + host: + description: + - The FortiManager's Address. + required: true + username: + description: + - The username to log into the FortiManager + required: true + password: + description: + - The password associated with the username account. + required: false + + policy_package: + description: + - The name of the policy package to be assigned to the device. + required: True + name: + description: + - The name of the device to be provisioned. + required: True + group: + description: + - The name of the device group the provisioned device can belong to. + required: False + serial: + description: + - The serial number of the device that will be provisioned. + required: True + platform: + description: + - The platform of the device, such as model number or VM. + required: True + description: + description: + - Description of the device to be provisioned. + required: False + os_version: + description: + - The Fortinet OS version to be used for the device, such as 5.0 or 6.0. + required: True + minor_release: + description: + - The minor release number such as 6.X.1, as X being the minor release. + required: False + patch_release: + description: + - The patch release number such as 6.0.X, as X being the patch release. + required: False + os_type: + description: + - The Fortinet OS type to be pushed to the device, such as 'FOS' for FortiOS. + required: True +''' + +EXAMPLES = ''' +- name: Create FGT1 Model Device + community.network.fmgr_provisioning: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "default" + name: "FGT1" + group: "Ansible" + serial: "FGVM000000117994" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '6.0' + minor_release: 0 + patch_release: 0 + os_type: 'fos' + + +- name: Create FGT2 Model Device + community.network.fmgr_provisioning: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "test_pp" + name: "FGT2" + group: "Ansible" + serial: "FGVM000000117992" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '5.0' + minor_release: 6 + patch_release: 0 + os_type: 'fos' + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import AnsibleFortiManager + +# check for pyFMG lib +try: + from pyFMG.fortimgr import FortiManager + HAS_PYFMGR = True +except ImportError: + HAS_PYFMGR = False + + +def dev_group_exists(fmg, dev_grp_name, adom): + datagram = { + 'adom': adom, + 'name': dev_grp_name, + } + + url = '/dvmdb/adom/{adom}/group/{dev_grp_name}'.format(adom=adom, dev_grp_name=dev_grp_name) + response = fmg.get(url, datagram) + return response + + +def prov_template_exists(fmg, prov_template, adom, vdom): + datagram = { + 'name': prov_template, + 'adom': adom, + } + + url = '/pm/devprof/adom/{adom}/devprof/{name}'.format(adom=adom, name=prov_template) + response = fmg.get(url, datagram) + return response + + +def create_model_device(fmg, name, serial, group, platform, os_version, + os_type, minor_release, patch_release=0, adom='root'): + datagram = { + 'adom': adom, + 'flags': ['create_task', 'nonblocking'], + 'groups': [{'name': group, 'vdom': 'root'}], + 'device': { + 'mr': minor_release, + 'name': name, + 'sn': serial, + 'mgmt_mode': 'fmg', + 'device action': 'add_model', + 'platform_str': platform, + 'os_ver': os_version, + 'os_type': os_type, + 'patch': patch_release, + 'desc': 'Provisioned by Ansible', + } + } + + url = '/dvm/cmd/add/device' + response = fmg.execute(url, datagram) + return response + + +def update_flags(fmg, name): + datagram = { + 'flags': ['is_model', 'linked_to_model'] + } + url = 'dvmdb/device/{name}'.format(name=name) + response = fmg.update(url, datagram) + return response + + +def assign_provision_template(fmg, template, adom, target): + datagram = { + 'name': template, + 'type': 'devprof', + 'description': 'Provisioned by Ansible', + 'scope member': [{'name': target}] + } + url = "/pm/devprof/adom/{adom}".format(adom=adom) + response = fmg.update(url, datagram) + return response + + +def set_devprof_scope(self, provisioning_template, adom, provision_targets): + """ + GET the DevProf (check to see if exists) + """ + fields = dict() + targets = [] + fields["name"] = provisioning_template + fields["type"] = "devprof" + fields["description"] = "CreatedByAnsible" + + for target in provision_targets.strip().split(","): + # split the host on the space to get the mask out + new_target = {"name": target} + targets.append(new_target) + + fields["scope member"] = targets + + body = {"method": "set", "params": [{"url": "/pm/devprof/adom/{adom}".format(adom=adom), + "data": fields, "session": self.session}]} + response = self.make_request(body).json() + return response + + +def assign_dev_grp(fmg, grp_name, device_name, vdom, adom): + datagram = { + 'name': device_name, + 'vdom': vdom, + } + url = "/dvmdb/adom/{adom}/group/{grp_name}/object member".format(adom=adom, grp_name=grp_name) + response = fmg.set(url, datagram) + return response + + +def update_install_target(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'scope member': [{'name': device, 'vdom': vdom}], + 'type': 'pkg' + } + url = '/pm/pkg/adom/{adom}/{pkg_name}'.format(adom=adom, pkg_name=pp) + response = fmg.update(url, datagram) + return response + + +def install_pp(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'adom': adom, + 'flags': 'nonblocking', + 'pkg': pp, + 'scope': [{'name': device, 'vdom': vdom}], + } + url = 'securityconsole/install/package' + response = fmg.execute(url, datagram) + return response + + +def main(): + + argument_spec = dict( + adom=dict(required=False, type="str"), + vdom=dict(required=False, type="str"), + host=dict(required=True, type="str"), + password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True), + username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"]), no_log=True), + + policy_package=dict(required=False, type="str"), + name=dict(required=False, type="str"), + group=dict(required=False, type="str"), + serial=dict(required=True, type="str"), + platform=dict(required=True, type="str"), + description=dict(required=False, type="str"), + os_version=dict(required=True, type="str"), + minor_release=dict(required=False, type="str"), + patch_release=dict(required=False, type="str"), + os_type=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True, ) + + # check if params are set + if module.params["host"] is None or module.params["username"] is None: + module.fail_json(msg="Host and username are required for connection") + + # check if login failed + fmg = AnsibleFortiManager(module, module.params["host"], module.params["username"], module.params["password"]) + response = fmg.login() + + if "FortiManager instance connnected" not in str(response): + module.fail_json(msg="Connection to FortiManager Failed") + else: + + if module.params["policy_package"] is None: + module.params["policy_package"] = 'default' + if module.params["adom"] is None: + module.params["adom"] = 'root' + if module.params["vdom"] is None: + module.params["vdom"] = 'root' + if module.params["platform"] is None: + module.params["platform"] = 'FortiGate-VM64' + if module.params["os_type"] is None: + module.params["os_type"] = 'fos' + + results = create_model_device(fmg, + module.params["name"], + module.params["serial"], + module.params["group"], + module.params["platform"], + module.params["os_ver"], + module.params["os_type"], + module.params["minor_release"], + module.params["patch_release"], + module.params["adom"]) + if results[0] != 0: + module.fail_json(msg="Create model failed", **results) + + results = update_flags(fmg, module.params["name"]) + if results[0] != 0: + module.fail_json(msg="Update device flags failed", **results) + + # results = assign_dev_grp(fmg, 'Ansible', 'FGVM000000117992', 'root', 'root') + # if not results[0] == 0: + # module.fail_json(msg="Setting device group failed", **results) + + results = update_install_target(fmg, module.params["name"], module.params["policy_package"]) + if results[0] != 0: + module.fail_json(msg="Adding device target to package failed", **results) + + results = install_pp(fmg, module.params["name"], module.params["policy_package"]) + if results[0] != 0: + module.fail_json(msg="Installing policy package failed", **results) + + fmg.logout() + + # results is returned as a tuple + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_query.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_query.py new file mode 100644 index 00000000..661cf1cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_query.py @@ -0,0 +1,424 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_query +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: Luke Weighall (@lweighall) +short_description: Query FortiManager data objects for use in Ansible workflows. +description: + - Provides information on data objects within FortiManager so that playbooks can perform conditionals. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + object: + description: + - The data object we wish to query (device, package, rule, etc). Will expand choices as improves. + required: true + choices: + - device + - cluster_nodes + - task + - custom + + custom_endpoint: + description: + - ADVANCED USERS ONLY! REQUIRES KNOWLEDGE OF FMGR JSON API! + - The HTTP Endpoint on FortiManager you wish to GET from. + required: false + + custom_dict: + description: + - ADVANCED USERS ONLY! REQUIRES KNOWLEDGE OF FMGR JSON API! + - DICTIONARY JSON FORMAT ONLY -- Custom dictionary/datagram to send to the endpoint. + required: false + + device_ip: + description: + - The IP of the device you want to query. + required: false + + device_unique_name: + description: + - The desired "friendly" name of the device you want to query. + required: false + + device_serial: + description: + - The serial number of the device you want to query. + required: false + + task_id: + description: + - The ID of the task you wish to query status on. If left blank and object = 'task' a list of tasks are returned. + required: false + + nodes: + description: + - A LIST of firewalls in the cluster you want to verify i.e. ["firewall_A","firewall_B"]. + required: false +''' + + +EXAMPLES = ''' +- name: QUERY FORTIGATE DEVICE BY IP + community.network.fmgr_query: + object: "device" + adom: "ansible" + device_ip: "10.7.220.41" + +- name: QUERY FORTIGATE DEVICE BY SERIAL + community.network.fmgr_query: + adom: "ansible" + object: "device" + device_serial: "FGVM000000117992" + +- name: QUERY FORTIGATE DEVICE BY FRIENDLY NAME + community.network.fmgr_query: + adom: "ansible" + object: "device" + device_unique_name: "ansible-fgt01" + +- name: VERIFY CLUSTER MEMBERS AND STATUS + community.network.fmgr_query: + adom: "ansible" + object: "cluster_nodes" + device_unique_name: "fgt-cluster01" + nodes: ["ansible-fgt01", "ansible-fgt02", "ansible-fgt03"] + +- name: GET STATUS OF TASK ID + community.network.fmgr_query: + adom: "ansible" + object: "task" + task_id: "3" + +- name: USE CUSTOM TYPE TO QUERY AVAILABLE SCRIPTS + community.network.fmgr_query: + adom: "ansible" + object: "custom" + custom_endpoint: "/dvmdb/adom/ansible/script" + custom_dict: { "type": "cli" } +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_get_custom(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # IF THE CUSTOM DICTIONARY (OFTEN CONTAINING FILTERS) IS DEFINED CREATED THAT + if paramgram["custom_dict"] is not None: + datagram = paramgram["custom_dict"] + else: + datagram = dict() + + # SET THE CUSTOM ENDPOINT PROVIDED + url = paramgram["custom_endpoint"] + # MAKE THE CALL AND RETURN RESULTS + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_get_task_status(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # IF THE TASK_ID IS DEFINED, THEN GET THAT SPECIFIC TASK + # OTHERWISE, GET ALL RECENT TASKS IN A LIST + if paramgram["task_id"] is not None: + + datagram = { + "adom": paramgram["adom"] + } + url = '/task/task/{task_id}'.format(task_id=paramgram["task_id"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + else: + datagram = { + "adom": paramgram["adom"] + } + url = '/task/task' + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_get_device(fmgr, paramgram): + """ + This method is used to get information on devices. This will not work on HA_SLAVE nodes, only top level devices. + Such as cluster objects and standalone devices. + + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # FIRST TRY TO RUN AN UPDATE ON THE DEVICE + # RUN A QUICK CLUSTER REFRESH/UPDATE ATTEMPT TO ENSURE WE'RE GETTING THE LATEST INFORMOATION + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + update_url = '/dvm/cmd/update/device' + update_dict = { + "adom": paramgram['adom'], + "device": paramgram['device_unique_name'], + "flags": "create_task" + } + # DO THE UPDATE CALL + fmgr.process_request(update_url, update_dict, FMGRMethods.EXEC) + + # SET THE URL + url = '/dvmdb/adom/{adom}/device'.format(adom=paramgram["adom"]) + device_found = 0 + response = [] + + # TRY TO FIND IT FIRST BY SERIAL NUMBER + if paramgram["device_serial"] is not None: + datagram = { + "filter": ["sn", "==", paramgram["device_serial"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + # CHECK IF ANYTHING WAS RETURNED, IF NOT TRY DEVICE NAME PARAMETER + if device_found == 0 and paramgram["device_unique_name"] is not None: + datagram = { + "filter": ["name", "==", paramgram["device_unique_name"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + # CHECK IF ANYTHING WAS RETURNED, IF NOT TRY DEVICE IP ADDRESS + if device_found == 0 and paramgram["device_ip"] is not None: + datagram = { + "filter": ["ip", "==", paramgram["device_ip"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + return response + + +def fmgr_get_cluster_nodes(fmgr, paramgram): + """ + This method is used to get information on devices. This WILL work on HA_SLAVE nodes, but NOT top level standalone + devices. + Such as cluster objects and standalone devices. + + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + # USE THE DEVICE METHOD TO GET THE CLUSTER INFORMATION SO WE CAN SEE THE HA_SLAVE NODES + response = fmgr_get_device(fmgr, paramgram) + # CHECK FOR HA_SLAVE NODES, IF CLUSTER IS MISSING COMPLETELY THEN QUIT + try: + returned_nodes = response[1][0]["ha_slave"] + num_of_nodes = len(returned_nodes) + except Exception: + error_msg = {"cluster_status": "MISSING"} + return error_msg + + # INIT LOOP RESOURCES + loop_count = 0 + good_nodes = [] + expected_nodes = list(paramgram["nodes"]) + missing_nodes = list(paramgram["nodes"]) + bad_status_nodes = [] + + # LOOP THROUGH THE NODES AND GET THEIR STATUS TO BUILD THE RETURN JSON OBJECT + # WE'RE ALSO CHECKING THE NODES IF THEY ARE BAD STATUS, OR PLAIN MISSING + while loop_count < num_of_nodes: + node_append = { + "node_name": returned_nodes[loop_count]["name"], + "node_serial": returned_nodes[loop_count]["sn"], + "node_parent": returned_nodes[loop_count]["did"], + "node_status": returned_nodes[loop_count]["status"], + } + # IF THE NODE IS IN THE EXPECTED NODES LIST AND WORKING THEN ADD IT TO GOOD NODES LIST + if node_append["node_name"] in expected_nodes and node_append["node_status"] == 1: + good_nodes.append(node_append["node_name"]) + # IF THE NODE IS IN THE EXPECTED NODES LIST BUT NOT WORKING THEN ADDED IT TO BAD_STATUS_NODES + # IF THE NODE STATUS IS NOT 1 THEN ITS BAD + if node_append["node_name"] in expected_nodes and node_append["node_status"] != 1: + bad_status_nodes.append(node_append["node_name"]) + # REMOVE THE NODE FROM MISSING NODES LIST IF NOTHING IS WRONG WITH NODE -- LEAVING US A LIST OF + # NOT WORKING NODES + missing_nodes.remove(node_append["node_name"]) + loop_count += 1 + + # BUILD RETURN OBJECT FROM NODE LISTS + nodes = { + "good_nodes": good_nodes, + "expected_nodes": expected_nodes, + "missing_nodes": missing_nodes, + "bad_nodes": bad_status_nodes, + "query_status": "good", + } + if len(nodes["good_nodes"]) == len(nodes["expected_nodes"]): + nodes["cluster_status"] = "OK" + else: + nodes["cluster_status"] = "NOT-COMPLIANT" + return nodes + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + object=dict(required=True, type="str", choices=["device", "cluster_nodes", "task", "custom"]), + custom_endpoint=dict(required=False, type="str"), + custom_dict=dict(required=False, type="dict"), + device_ip=dict(required=False, type="str"), + device_unique_name=dict(required=False, type="str"), + device_serial=dict(required=False, type="str"), + nodes=dict(required=False, type="list"), + task_id=dict(required=False, type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "adom": module.params["adom"], + "object": module.params["object"], + "device_ip": module.params["device_ip"], + "device_unique_name": module.params["device_unique_name"], + "device_serial": module.params["device_serial"], + "nodes": module.params["nodes"], + "task_id": module.params["task_id"], + "custom_endpoint": module.params["custom_endpoint"], + "custom_dict": module.params["custom_dict"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + # IF OBJECT IS DEVICE + if paramgram["object"] == "device" and any(v is not None for v in [paramgram["device_unique_name"], + paramgram["device_serial"], + paramgram["device_ip"]]): + results = fmgr_get_device(fmgr, paramgram) + if results[0] not in [0]: + module.fail_json(msg="Device query failed!") + elif len(results[1]) == 0: + module.exit_json(msg="Device NOT FOUND!") + else: + module.exit_json(msg="Device Found", **results[1][0]) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS CLUSTER_NODES + if paramgram["object"] == "cluster_nodes" and paramgram["nodes"] is not None: + results = fmgr_get_cluster_nodes(fmgr, paramgram) + if results["cluster_status"] == "MISSING": + module.exit_json(msg="No cluster device found!", **results) + elif results["query_status"] == "good": + module.exit_json(msg="Cluster Found - Showing Nodes", **results) + elif results is None: + module.fail_json(msg="Query FAILED -- Check module or playbook syntax") + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS TASK + if paramgram["object"] == "task": + results = fmgr_get_task_status(fmgr, paramgram) + if results[0] != 0: + module.fail_json(**results[1]) + if results[0] == 0: + module.exit_json(**results[1]) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS CUSTOM + if paramgram["object"] == "custom": + results = fmgr_get_custom(fmgr, paramgram) + if results[0] != 0: + module.fail_json(msg="QUERY FAILED -- Please check syntax check JSON guide if needed.") + if results[0] == 0: + results_len = len(results[1]) + if results_len > 0: + results_combine = dict() + if isinstance(results[1], dict): + results_combine["results"] = results[1] + if isinstance(results[1], list): + results_combine["results"] = results[1][0:results_len] + module.exit_json(msg="Custom Query Success", **results_combine) + else: + module.exit_json(msg="NO RESULTS") + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_script.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_script.py new file mode 100644 index 00000000..80061826 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_script.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_script +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: Andrew Welsh (@Ghilli3) +short_description: Add/Edit/Delete and execute scripts +description: Create/edit/delete scripts and execute the scripts on the FortiManager using jsonrpc API + +options: + adom: + description: + - The administrative domain (admon) the configuration belongs to + required: true + + vdom: + description: + - The virtual domain (vdom) the configuration belongs to + + mode: + description: + - The desired mode of the specified object. Execute will run the script. + required: false + default: "add" + choices: ["add", "delete", "execute", "set"] + + script_name: + description: + - The name of the script. + required: True + + script_type: + description: + - The type of script (CLI or TCL). + required: false + + script_target: + description: + - The target of the script to be run. + required: false + + script_description: + description: + - The description of the script. + required: false + + script_content: + description: + - The script content that will be executed. + required: false + + script_scope: + description: + - (datasource) The devices that the script will run on, can have both device member and device group member. + required: false + + script_package: + description: + - (datasource) Policy package object to run the script against + required: false +''' + +EXAMPLES = ''' +- name: CREATE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + script_type: "cli" + script_target: "remote_device" + script_description: "Create by Ansible" + script_content: "get system status" + +- name: EXECUTE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + mode: "execute" + script_scope: "FGT1,FGT2" + +- name: DELETE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + mode: "delete" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def set_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + 'content': paramgram["script_content"], + 'desc': paramgram["script_description"], + 'name': paramgram["script_name"], + 'target': paramgram["script_target"], + 'type': paramgram["script_type"], + } + + url = '/dvmdb/adom/{adom}/script/'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def delete_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + 'name': paramgram["script_name"], + } + + url = '/dvmdb/adom/{adom}/script/{script_name}'.format(adom=paramgram["adom"], script_name=paramgram["script_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + return response + + +def execute_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + scope_list = list() + scope = paramgram["script_scope"].replace(' ', '') + scope = scope.split(',') + for dev_name in scope: + scope_list.append({'name': dev_name, 'vdom': paramgram["vdom"]}) + + datagram = { + 'adom': paramgram["adom"], + 'script': paramgram["script_name"], + 'package': paramgram["script_package"], + 'scope': scope_list, + } + + url = '/dvmdb/adom/{adom}/script/execute'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + vdom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "execute", "set", "delete"], type="str", default="add"), + script_name=dict(required=True, type="str"), + script_type=dict(required=False, type="str"), + script_target=dict(required=False, type="str"), + script_description=dict(required=False, type="str"), + script_content=dict(required=False, type="str"), + script_scope=dict(required=False, type="str"), + script_package=dict(required=False, type="str"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "script_name": module.params["script_name"], + "script_type": module.params["script_type"], + "script_target": module.params["script_target"], + "script_description": module.params["script_description"], + "script_content": module.params["script_content"], + "script_scope": module.params["script_scope"], + "script_package": module.params["script_package"], + "adom": module.params["adom"], + "vdom": module.params["vdom"], + "mode": module.params["mode"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["mode"] in ['add', 'set']: + results = set_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + try: + if paramgram["mode"] == "execute": + results = execute_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + try: + if paramgram["mode"] == "delete": + results = delete_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_appctrl.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_appctrl.py new file mode 100644 index 00000000..477ab7d5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_appctrl.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_appctrl +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage application control security profiles +description: + - Manage application control security profiles within FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + unknown_application_log: + description: + - Enable/disable logging for unknown applications. + - choice | disable | Disable logging for unknown applications. + - choice | enable | Enable logging for unknown applications. + required: false + choices: ["disable", "enable"] + + unknown_application_action: + description: + - Pass or block traffic from unknown applications. + - choice | pass | Pass or allow unknown applications. + - choice | block | Drop or block unknown applications. + required: false + choices: ["pass", "block"] + + replacemsg_group: + description: + - Replacement message group. + required: false + + p2p_black_list: + description: + - NO DESCRIPTION PARSED ENTER MANUALLY + - FLAG Based Options. Specify multiple in list form. + - flag | skype | Skype. + - flag | edonkey | Edonkey. + - flag | bittorrent | Bit torrent. + required: false + choices: ["skype", "edonkey", "bittorrent"] + + other_application_log: + description: + - Enable/disable logging for other applications. + - choice | disable | Disable logging for other applications. + - choice | enable | Enable logging for other applications. + required: false + choices: ["disable", "enable"] + + other_application_action: + description: + - Action for other applications. + - choice | pass | Allow sessions matching an application in this application list. + - choice | block | Block sessions matching an application in this application list. + required: false + choices: ["pass", "block"] + + options: + description: + - NO DESCRIPTION PARSED ENTER MANUALLY + - FLAG Based Options. Specify multiple in list form. + - flag | allow-dns | Allow DNS. + - flag | allow-icmp | Allow ICMP. + - flag | allow-http | Allow generic HTTP web browsing. + - flag | allow-ssl | Allow generic SSL communication. + - flag | allow-quic | Allow QUIC. + required: false + choices: ["allow-dns", "allow-icmp", "allow-http", "allow-ssl", "allow-quic"] + + name: + description: + - List name. + required: false + + extended_log: + description: + - Enable/disable extended logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + deep_app_inspection: + description: + - Enable/disable deep application inspection. + - choice | disable | Disable deep application inspection. + - choice | enable | Enable deep application inspection. + required: false + choices: ["disable", "enable"] + + comment: + description: + - comments + required: false + + app_replacemsg: + description: + - Enable/disable replacement messages for blocked applications. + - choice | disable | Disable replacement messages for blocked applications. + - choice | enable | Enable replacement messages for blocked applications. + required: false + choices: ["disable", "enable"] + + entries: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, OMIT THE USE OF THIS PARAMETER + - AND USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + entries_action: + description: + - Pass or block traffic, or reset connection for traffic from this application. + - choice | pass | Pass or allow matching traffic. + - choice | block | Block or drop matching traffic. + - choice | reset | Reset sessions for matching traffic. + required: false + choices: ["pass", "block", "reset"] + + entries_application: + description: + - ID of allowed applications. + required: false + + entries_behavior: + description: + - Application behavior filter. + required: false + + entries_category: + description: + - Category ID list. + required: false + + entries_log: + description: + - Enable/disable logging for this application list. + - choice | disable | Disable logging. + - choice | enable | Enable logging. + required: false + choices: ["disable", "enable"] + + entries_log_packet: + description: + - Enable/disable packet logging. + - choice | disable | Disable packet logging. + - choice | enable | Enable packet logging. + required: false + choices: ["disable", "enable"] + + entries_per_ip_shaper: + description: + - Per-IP traffic shaper. + required: false + + entries_popularity: + description: + - Application popularity filter (1 - 5, from least to most popular). + - FLAG Based Options. Specify multiple in list form. + - flag | 1 | Popularity level 1. + - flag | 2 | Popularity level 2. + - flag | 3 | Popularity level 3. + - flag | 4 | Popularity level 4. + - flag | 5 | Popularity level 5. + required: false + choices: ["1", "2", "3", "4", "5"] + + entries_protocols: + description: + - Application protocol filter. + required: false + + entries_quarantine: + description: + - Quarantine method. + - choice | none | Quarantine is disabled. + - choice | attacker | Block all traffic sent from attacker's IP address. + - The attacker's IP address is also added to the banned user list. The target's address is not affected. + required: false + choices: ["none", "attacker"] + + entries_quarantine_expiry: + description: + - Duration of quarantine. (Format ###d##h##m, minimum 1m, maximum 364d23h59m, default = 5m). + - Requires quarantine set to attacker. + required: false + + entries_quarantine_log: + description: + - Enable/disable quarantine logging. + - choice | disable | Disable quarantine logging. + - choice | enable | Enable quarantine logging. + required: false + choices: ["disable", "enable"] + + entries_rate_count: + description: + - Count of the rate. + required: false + + entries_rate_duration: + description: + - Duration (sec) of the rate. + required: false + + entries_rate_mode: + description: + - Rate limit mode. + - choice | periodical | Allow configured number of packets every rate-duration. + - choice | continuous | Block packets once the rate is reached. + required: false + choices: ["periodical", "continuous"] + + entries_rate_track: + description: + - Track the packet protocol field. + - choice | none | + - choice | src-ip | Source IP. + - choice | dest-ip | Destination IP. + - choice | dhcp-client-mac | DHCP client. + - choice | dns-domain | DNS domain. + required: false + choices: ["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"] + + entries_risk: + description: + - Risk, or impact, of allowing traffic from this application to occur 1 - 5; + - (Low, Elevated, Medium, High, and Critical). + required: false + + entries_session_ttl: + description: + - Session TTL (0 = default). + required: false + + entries_shaper: + description: + - Traffic shaper. + required: false + + entries_shaper_reverse: + description: + - Reverse traffic shaper. + required: false + + entries_sub_category: + description: + - Application Sub-category ID list. + required: false + + entries_technology: + description: + - Application technology filter. + required: false + + entries_vendor: + description: + - Application vendor filter. + required: false + + entries_parameters_value: + description: + - Parameter value. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_appctrl: + name: "Ansible_Application_Control_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_appctrl: + name: "Ansible_Application_Control_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + entries: [{ + action: "block", + log: "enable", + log-packet: "enable", + protocols: ["1"], + quarantine: "attacker", + quarantine-log: "enable", + }, + {action: "pass", + category: ["2","3","4"]}, + ] +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_application_list_modify(fmgr, paramgram): + """ + fmgr_application_list -- Modifies Application Control Profiles on FortiManager + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/application/list'.format(adom=paramgram["adom"]) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif paramgram["mode"] == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/application/list/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + unknown_application_log=dict(required=False, type="str", choices=["disable", "enable"]), + unknown_application_action=dict(required=False, type="str", choices=["pass", "block"]), + replacemsg_group=dict(required=False, type="str"), + p2p_black_list=dict(required=False, type="str", choices=["skype", "edonkey", "bittorrent"]), + other_application_log=dict(required=False, type="str", choices=["disable", "enable"]), + other_application_action=dict(required=False, type="str", choices=["pass", "block"]), + options=dict(required=False, type="str", + choices=["allow-dns", "allow-icmp", "allow-http", "allow-ssl", "allow-quic"]), + name=dict(required=False, type="str"), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + deep_app_inspection=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + app_replacemsg=dict(required=False, type="str", choices=["disable", "enable"]), + entries=dict(required=False, type="list"), + entries_action=dict(required=False, type="str", choices=["pass", "block", "reset"]), + entries_application=dict(required=False, type="str"), + entries_behavior=dict(required=False, type="str"), + entries_category=dict(required=False, type="str"), + entries_log=dict(required=False, type="str", choices=["disable", "enable"]), + entries_log_packet=dict(required=False, type="str", choices=["disable", "enable"]), + entries_per_ip_shaper=dict(required=False, type="str"), + entries_popularity=dict(required=False, type="str", choices=["1", "2", "3", "4", "5"]), + entries_protocols=dict(required=False, type="str"), + entries_quarantine=dict(required=False, type="str", choices=["none", "attacker"]), + entries_quarantine_expiry=dict(required=False, type="str"), + entries_quarantine_log=dict(required=False, type="str", choices=["disable", "enable"]), + entries_rate_count=dict(required=False, type="int"), + entries_rate_duration=dict(required=False, type="int"), + entries_rate_mode=dict(required=False, type="str", choices=["periodical", "continuous"]), + entries_rate_track=dict(required=False, type="str", + choices=["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"]), + entries_risk=dict(required=False, type="str"), + entries_session_ttl=dict(required=False, type="int"), + entries_shaper=dict(required=False, type="str"), + entries_shaper_reverse=dict(required=False, type="str"), + entries_sub_category=dict(required=False, type="str"), + entries_technology=dict(required=False, type="str"), + entries_vendor=dict(required=False, type="str"), + + entries_parameters_value=dict(required=False, type="str"), + + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "unknown-application-log": module.params["unknown_application_log"], + "unknown-application-action": module.params["unknown_application_action"], + "replacemsg-group": module.params["replacemsg_group"], + "p2p-black-list": module.params["p2p_black_list"], + "other-application-log": module.params["other_application_log"], + "other-application-action": module.params["other_application_action"], + "options": module.params["options"], + "name": module.params["name"], + "extended-log": module.params["extended_log"], + "deep-app-inspection": module.params["deep_app_inspection"], + "comment": module.params["comment"], + "app-replacemsg": module.params["app_replacemsg"], + "entries": { + "action": module.params["entries_action"], + "application": module.params["entries_application"], + "behavior": module.params["entries_behavior"], + "category": module.params["entries_category"], + "log": module.params["entries_log"], + "log-packet": module.params["entries_log_packet"], + "per-ip-shaper": module.params["entries_per_ip_shaper"], + "popularity": module.params["entries_popularity"], + "protocols": module.params["entries_protocols"], + "quarantine": module.params["entries_quarantine"], + "quarantine-expiry": module.params["entries_quarantine_expiry"], + "quarantine-log": module.params["entries_quarantine_log"], + "rate-count": module.params["entries_rate_count"], + "rate-duration": module.params["entries_rate_duration"], + "rate-mode": module.params["entries_rate_mode"], + "rate-track": module.params["entries_rate_track"], + "risk": module.params["entries_risk"], + "session-ttl": module.params["entries_session_ttl"], + "shaper": module.params["entries_shaper"], + "shaper-reverse": module.params["entries_shaper_reverse"], + "sub-category": module.params["entries_sub_category"], + "technology": module.params["entries_technology"], + "vendor": module.params["entries_vendor"], + "parameters": { + "value": module.params["entries_parameters_value"], + } + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['entries'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_application_list_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_av.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_av.py new file mode 100644 index 00000000..bf2768b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_av.py @@ -0,0 +1,1386 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_av +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage security profile +description: + - Manage security profile groups for FortiManager objects + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + scan_mode: + description: + - Choose between full scan mode and quick scan mode. + required: false + choices: + - quick + - full + + replacemsg_group: + description: + - Replacement message group customized for this profile. + required: false + + name: + description: + - Profile name. + required: false + + mobile_malware_db: + description: + - Enable/disable using the mobile malware signature database. + required: false + choices: + - disable + - enable + + inspection_mode: + description: + - Inspection mode. + required: false + choices: + - proxy + - flow-based + + ftgd_analytics: + description: + - Settings to control which files are uploaded to FortiSandbox. + required: false + choices: + - disable + - suspicious + - everything + + extended_log: + description: + - Enable/disable extended logging for antivirus. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + av_virus_log: + description: + - Enable/disable AntiVirus logging. + required: false + choices: + - disable + - enable + + av_block_log: + description: + - Enable/disable logging for AntiVirus file blocking. + required: false + choices: + - disable + - enable + + analytics_wl_filetype: + description: + - Do not submit files matching this DLP file-pattern to FortiSandbox. + required: false + + analytics_max_upload: + description: + - Maximum size of files that can be uploaded to FortiSandbox (1 - 395 MBytes, default = 10). + required: false + + analytics_db: + description: + - Enable/disable using the FortiSandbox signature database to supplement the AV signature databases. + required: false + choices: + - disable + - enable + + analytics_bl_filetype: + description: + - Only submit files matching this DLP file-pattern to FortiSandbox. + required: false + + content_disarm: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + content_disarm_cover_page: + description: + - Enable/disable inserting a cover page into the disarmed document. + required: false + choices: + - disable + - enable + + content_disarm_detect_only: + description: + - Enable/disable only detect disarmable files, do not alter content. + required: false + choices: + - disable + - enable + + content_disarm_office_embed: + description: + - Enable/disable stripping of embedded objects in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_hylink: + description: + - Enable/disable stripping of hyperlinks in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_linked: + description: + - Enable/disable stripping of linked objects in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_macro: + description: + - Enable/disable stripping of macros in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_original_file_destination: + description: + - Destination to send original file if active content is removed. + required: false + choices: + - fortisandbox + - quarantine + - discard + + content_disarm_pdf_act_form: + description: + - Enable/disable stripping of actions that submit data to other targets in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_gotor: + description: + - Enable/disable stripping of links to other PDFs in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_java: + description: + - Enable/disable stripping of actions that execute JavaScript code in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_launch: + description: + - Enable/disable stripping of links to external applications in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_movie: + description: + - Enable/disable stripping of embedded movies in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_sound: + description: + - Enable/disable stripping of embedded sound files in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_embedfile: + description: + - Enable/disable stripping of embedded files in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_hyperlink: + description: + - Enable/disable stripping of hyperlinks from PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_javacode: + description: + - Enable/disable stripping of JavaScript code in PDF documents. + required: false + choices: + - disable + - enable + + ftp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + ftp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + ftp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + ftp_options: + description: + - Enable/disable FTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + ftp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + http: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + http_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + http_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + http_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + http_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + http_options: + description: + - Enable/disable HTTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + http_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + imap: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imap_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + imap_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + imap_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + imap_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + imap_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + imap_options: + description: + - Enable/disable IMAP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + imap_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + mapi_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + mapi_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + mapi_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + mapi_options: + description: + - Enable/disable MAPI AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + mapi_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + nac_quar: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + nac_quar_expiry: + description: + - Duration of quarantine. + required: false + + nac_quar_infected: + description: + - Enable/Disable quarantining infected hosts to the banned user list. + required: false + choices: + - none + - quar-src-ip + + nac_quar_log: + description: + - Enable/disable AntiVirus quarantine logging. + required: false + choices: + - disable + - enable + + nntp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + nntp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + nntp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + nntp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + nntp_options: + description: + - Enable/disable NNTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + nntp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + pop3: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + pop3_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + pop3_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + pop3_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + pop3_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + pop3_options: + description: + - Enable/disable POP3 AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + pop3_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + smb: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smb_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smb_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smb_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + smb_options: + description: + - Enable/disable SMB AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + smb_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + smtp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smtp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smtp_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + smtp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + smtp_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + smtp_options: + description: + - Enable/disable SMTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + smtp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_av: + name: "Ansible_AV_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_av: + name: "Ansible_AV_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + inspection_mode: "proxy" + ftgd_analytics: "everything" + av_block_log: "enable" + av_virus_log: "enable" + scan_mode: "full" + mobile_malware_db: "enable" + ftp_archive_block: "encrypted" + ftp_outbreak_prevention: "files" + ftp_archive_log: "timeout" + ftp_emulator: "disable" + ftp_options: "scan" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_antivirus_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/antivirus/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + else: + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/antivirus/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + scan_mode=dict(required=False, type="str", choices=["quick", "full"]), + replacemsg_group=dict(required=False, type="dict"), + name=dict(required=False, type="str"), + mobile_malware_db=dict(required=False, type="str", choices=["disable", "enable"]), + inspection_mode=dict(required=False, type="str", choices=["proxy", "flow-based"]), + ftgd_analytics=dict(required=False, type="str", choices=["disable", "suspicious", "everything"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + av_virus_log=dict(required=False, type="str", choices=["disable", "enable"]), + av_block_log=dict(required=False, type="str", choices=["disable", "enable"]), + analytics_wl_filetype=dict(required=False, type="dict"), + analytics_max_upload=dict(required=False, type="int"), + analytics_db=dict(required=False, type="str", choices=["disable", "enable"]), + analytics_bl_filetype=dict(required=False, type="dict"), + content_disarm=dict(required=False, type="list"), + content_disarm_cover_page=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_detect_only=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_embed=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_hylink=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_linked=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_macro=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_original_file_destination=dict(required=False, type="str", choices=["fortisandbox", + "quarantine", + "discard"]), + content_disarm_pdf_act_form=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_gotor=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_java=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_launch=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_movie=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_sound=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_embedfile=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_hyperlink=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_javacode=dict(required=False, type="str", choices=["disable", "enable"]), + ftp=dict(required=False, type="list"), + ftp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + ftp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + ftp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + ftp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + http=dict(required=False, type="list"), + http_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + http_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + http_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + http_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + http_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + http_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + imap=dict(required=False, type="list"), + imap_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + imap_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + imap_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + imap_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + imap_executables=dict(required=False, type="str", choices=["default", "virus"]), + imap_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + imap_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + mapi=dict(required=False, type="list"), + mapi_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + mapi_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + mapi_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_executables=dict(required=False, type="str", choices=["default", "virus"]), + mapi_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + mapi_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + nac_quar=dict(required=False, type="list"), + nac_quar_expiry=dict(required=False, type="str"), + nac_quar_infected=dict(required=False, type="str", choices=["none", "quar-src-ip"]), + nac_quar_log=dict(required=False, type="str", choices=["disable", "enable"]), + nntp=dict(required=False, type="list"), + nntp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + nntp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + nntp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + nntp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + nntp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + pop3=dict(required=False, type="list"), + pop3_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + pop3_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + pop3_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_executables=dict(required=False, type="str", choices=["default", "virus"]), + pop3_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + pop3_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + smb=dict(required=False, type="list"), + smb_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smb_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smb_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + smb_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + smb_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + smtp=dict(required=False, type="list"), + smtp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smtp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smtp_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_executables=dict(required=False, type="str", choices=["default", "virus"]), + smtp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + smtp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "scan-mode": module.params["scan_mode"], + "replacemsg-group": module.params["replacemsg_group"], + "name": module.params["name"], + "mobile-malware-db": module.params["mobile_malware_db"], + "inspection-mode": module.params["inspection_mode"], + "ftgd-analytics": module.params["ftgd_analytics"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "av-virus-log": module.params["av_virus_log"], + "av-block-log": module.params["av_block_log"], + "analytics-wl-filetype": module.params["analytics_wl_filetype"], + "analytics-max-upload": module.params["analytics_max_upload"], + "analytics-db": module.params["analytics_db"], + "analytics-bl-filetype": module.params["analytics_bl_filetype"], + "content-disarm": { + "cover-page": module.params["content_disarm_cover_page"], + "detect-only": module.params["content_disarm_detect_only"], + "office-embed": module.params["content_disarm_office_embed"], + "office-hylink": module.params["content_disarm_office_hylink"], + "office-linked": module.params["content_disarm_office_linked"], + "office-macro": module.params["content_disarm_office_macro"], + "original-file-destination": module.params["content_disarm_original_file_destination"], + "pdf-act-form": module.params["content_disarm_pdf_act_form"], + "pdf-act-gotor": module.params["content_disarm_pdf_act_gotor"], + "pdf-act-java": module.params["content_disarm_pdf_act_java"], + "pdf-act-launch": module.params["content_disarm_pdf_act_launch"], + "pdf-act-movie": module.params["content_disarm_pdf_act_movie"], + "pdf-act-sound": module.params["content_disarm_pdf_act_sound"], + "pdf-embedfile": module.params["content_disarm_pdf_embedfile"], + "pdf-hyperlink": module.params["content_disarm_pdf_hyperlink"], + "pdf-javacode": module.params["content_disarm_pdf_javacode"], + }, + "ftp": { + "archive-block": module.params["ftp_archive_block"], + "archive-log": module.params["ftp_archive_log"], + "emulator": module.params["ftp_emulator"], + "options": module.params["ftp_options"], + "outbreak-prevention": module.params["ftp_outbreak_prevention"], + }, + "http": { + "archive-block": module.params["http_archive_block"], + "archive-log": module.params["http_archive_log"], + "content-disarm": module.params["http_content_disarm"], + "emulator": module.params["http_emulator"], + "options": module.params["http_options"], + "outbreak-prevention": module.params["http_outbreak_prevention"], + }, + "imap": { + "archive-block": module.params["imap_archive_block"], + "archive-log": module.params["imap_archive_log"], + "content-disarm": module.params["imap_content_disarm"], + "emulator": module.params["imap_emulator"], + "executables": module.params["imap_executables"], + "options": module.params["imap_options"], + "outbreak-prevention": module.params["imap_outbreak_prevention"], + }, + "mapi": { + "archive-block": module.params["mapi_archive_block"], + "archive-log": module.params["mapi_archive_log"], + "emulator": module.params["mapi_emulator"], + "executables": module.params["mapi_executables"], + "options": module.params["mapi_options"], + "outbreak-prevention": module.params["mapi_outbreak_prevention"], + }, + "nac-quar": { + "expiry": module.params["nac_quar_expiry"], + "infected": module.params["nac_quar_infected"], + "log": module.params["nac_quar_log"], + }, + "nntp": { + "archive-block": module.params["nntp_archive_block"], + "archive-log": module.params["nntp_archive_log"], + "emulator": module.params["nntp_emulator"], + "options": module.params["nntp_options"], + "outbreak-prevention": module.params["nntp_outbreak_prevention"], + }, + "pop3": { + "archive-block": module.params["pop3_archive_block"], + "archive-log": module.params["pop3_archive_log"], + "content-disarm": module.params["pop3_content_disarm"], + "emulator": module.params["pop3_emulator"], + "executables": module.params["pop3_executables"], + "options": module.params["pop3_options"], + "outbreak-prevention": module.params["pop3_outbreak_prevention"], + }, + "smb": { + "archive-block": module.params["smb_archive_block"], + "archive-log": module.params["smb_archive_log"], + "emulator": module.params["smb_emulator"], + "options": module.params["smb_options"], + "outbreak-prevention": module.params["smb_outbreak_prevention"], + }, + "smtp": { + "archive-block": module.params["smtp_archive_block"], + "archive-log": module.params["smtp_archive_log"], + "content-disarm": module.params["smtp_content_disarm"], + "emulator": module.params["smtp_emulator"], + "executables": module.params["smtp_executables"], + "options": module.params["smtp_options"], + "outbreak-prevention": module.params["smtp_outbreak_prevention"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ["content-disarm", "ftp", "http", "imap", "mapi", "nac-quar", "nntp", "pop3", "smb", "smtp"] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_antivirus_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_dns.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_dns.py new file mode 100644 index 00000000..80558e15 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_dns.py @@ -0,0 +1,339 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_dns +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage DNS security profiles in FortiManager +description: + - Manage DNS security profiles in FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + youtube_restrict: + type: str + description: + - Set safe search for YouTube restriction level. + - choice | strict | Enable strict safe seach for YouTube. + - choice | moderate | Enable moderate safe search for YouTube. + required: false + choices: ["strict", "moderate"] + + sdns_ftgd_err_log: + type: str + description: + - Enable/disable FortiGuard SDNS rating error logging. + - choice | disable | Disable FortiGuard SDNS rating error logging. + - choice | enable | Enable FortiGuard SDNS rating error logging. + required: false + choices: ["disable", "enable"] + + sdns_domain_log: + type: str + description: + - Enable/disable domain filtering and botnet domain logging. + - choice | disable | Disable domain filtering and botnet domain logging. + - choice | enable | Enable domain filtering and botnet domain logging. + required: false + choices: ["disable", "enable"] + + safe_search: + type: str + description: + - Enable/disable Google, Bing, and YouTube safe search. + - choice | disable | Disable Google, Bing, and YouTube safe search. + - choice | enable | Enable Google, Bing, and YouTube safe search. + required: false + choices: ["disable", "enable"] + + redirect_portal: + type: str + description: + - IP address of the SDNS redirect portal. + required: false + + name: + type: str + description: + - Profile name. + required: false + + log_all_domain: + type: str + description: + - Enable/disable logging of all domains visited (detailed DNS logging). + - choice | disable | Disable logging of all domains visited. + - choice | enable | Enable logging of all domains visited. + required: false + choices: ["disable", "enable"] + + external_ip_blocklist: + type: str + description: + - One or more external IP block lists. + required: false + + comment: + type: str + description: + - Comment for the security profile to show in the FortiManager GUI. + required: false + + block_botnet: + type: str + description: + - Enable/disable blocking botnet C&C; DNS lookups. + - choice | disable | Disable blocking botnet C&C; DNS lookups. + - choice | enable | Enable blocking botnet C&C; DNS lookups. + required: false + choices: ["disable", "enable"] + + block_action: + type: str + description: + - Action to take for blocked domains. + - choice | block | Return NXDOMAIN for blocked domains. + - choice | redirect | Redirect blocked domains to SDNS portal. + required: false + choices: ["block", "redirect"] + + domain_filter_domain_filter_table: + type: str + description: + - DNS domain filter table ID. + required: false + + ftgd_dns_options: + type: str + description: + - FortiGuard DNS filter options. + - FLAG Based Options. Specify multiple in list form. + - flag | error-allow | Allow all domains when FortiGuard DNS servers fail. + - flag | ftgd-disable | Disable FortiGuard DNS domain rating. + required: false + choices: ["error-allow", "ftgd-disable"] + + ftgd_dns_filters_action: + type: str + description: + - Action to take for DNS requests matching the category. + - choice | monitor | Allow DNS requests matching the category and log the result. + - choice | block | Block DNS requests matching the category. + required: false + choices: ["monitor", "block"] + + ftgd_dns_filters_category: + type: str + description: + - Category number. + required: false + + ftgd_dns_filters_log: + type: str + description: + - Enable/disable DNS filter logging for this DNS profile. + - choice | disable | Disable DNS filter logging. + - choice | enable | Enable DNS filter logging. + required: false + choices: ["disable", "enable"] + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_dns: + name: "Ansible_DNS_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_dns: + name: "Ansible_DNS_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + block_action: "block" + + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_dnsfilter_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + url = "" + datagram = {} + + response = DEFAULT_RESULT_OBJ + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/dnsfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/dnsfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + youtube_restrict=dict(required=False, type="str", choices=["strict", "moderate"]), + sdns_ftgd_err_log=dict(required=False, type="str", choices=["disable", "enable"]), + sdns_domain_log=dict(required=False, type="str", choices=["disable", "enable"]), + safe_search=dict(required=False, type="str", choices=["disable", "enable"]), + redirect_portal=dict(required=False, type="str"), + name=dict(required=False, type="str"), + log_all_domain=dict(required=False, type="str", choices=["disable", "enable"]), + external_ip_blocklist=dict(required=False, type="str"), + comment=dict(required=False, type="str"), + block_botnet=dict(required=False, type="str", choices=["disable", "enable"]), + block_action=dict(required=False, type="str", choices=["block", "redirect"]), + + domain_filter_domain_filter_table=dict(required=False, type="str"), + + ftgd_dns_options=dict(required=False, type="str", choices=["error-allow", "ftgd-disable"]), + + ftgd_dns_filters_action=dict(required=False, type="str", choices=["monitor", "block"]), + ftgd_dns_filters_category=dict(required=False, type="str"), + ftgd_dns_filters_log=dict(required=False, type="str", choices=["disable", "enable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "youtube-restrict": module.params["youtube_restrict"], + "sdns-ftgd-err-log": module.params["sdns_ftgd_err_log"], + "sdns-domain-log": module.params["sdns_domain_log"], + "safe-search": module.params["safe_search"], + "redirect-portal": module.params["redirect_portal"], + "name": module.params["name"], + "log-all-domain": module.params["log_all_domain"], + "external-ip-blocklist": module.params["external_ip_blocklist"], + "comment": module.params["comment"], + "block-botnet": module.params["block_botnet"], + "block-action": module.params["block_action"], + "domain-filter": { + "domain-filter-table": module.params["domain_filter_domain_filter_table"], + }, + "ftgd-dns": { + "options": module.params["ftgd_dns_options"], + "filters": { + "action": module.params["ftgd_dns_filters_action"], + "category": module.params["ftgd_dns_filters_category"], + "log": module.params["ftgd_dns_filters_log"], + } + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_dnsfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ips.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ips.py new file mode 100644 index 00000000..4aab6346 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ips.py @@ -0,0 +1,664 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_ips +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Managing IPS security profiles in FortiManager +description: + - Managing IPS security profiles in FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + replacemsg_group: + description: + - Replacement message group. + required: false + + name: + description: + - Sensor name. + required: false + + extended_log: + description: + - Enable/disable extended logging. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + block_malicious_url: + description: + - Enable/disable malicious URL blocking. + required: false + choices: + - disable + - enable + + entries: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + entries_action: + description: + - Action taken with traffic in which signatures are detected. + required: false + choices: + - pass + - block + - reset + - default + + entries_application: + description: + - Applications to be protected. set application ? lists available applications. all includes + all applications. other includes all unlisted applications. + required: false + + entries_location: + description: + - Protect client or server traffic. + required: false + + entries_log: + description: + - Enable/disable logging of signatures included in filter. + required: false + choices: + - disable + - enable + + entries_log_attack_context: + description: + - Enable/disable logging of attack context| URL buffer, header buffer, body buffer, packet buffer. + required: false + choices: + - disable + - enable + + entries_log_packet: + description: + - Enable/disable packet logging. Enable to save the packet that triggers the filter. You can + download the packets in pcap format for diagnostic use. + required: false + choices: + - disable + - enable + + entries_os: + description: + - Operating systems to be protected. all includes all operating systems. other includes all + unlisted operating systems. + required: false + + entries_protocol: + description: + - Protocols to be examined. set protocol ? lists available protocols. all includes all protocols. + other includes all unlisted protocols. + required: false + + entries_quarantine: + description: + - Quarantine method. + required: false + choices: + - none + - attacker + + entries_quarantine_expiry: + description: + - Duration of quarantine. + required: false + + entries_quarantine_log: + description: + - Enable/disable quarantine logging. + required: false + choices: + - disable + - enable + + entries_rate_count: + description: + - Count of the rate. + required: false + + entries_rate_duration: + description: + - Duration (sec) of the rate. + required: false + + entries_rate_mode: + description: + - Rate limit mode. + required: false + choices: + - periodical + - continuous + + entries_rate_track: + description: + - Track the packet protocol field. + required: false + choices: + - none + - src-ip + - dest-ip + - dhcp-client-mac + - dns-domain + + entries_rule: + description: + - Identifies the predefined or custom IPS signatures to add to the sensor. + required: false + + entries_severity: + description: + - Relative severity of the signature, from info to critical. Log messages generated by the signature + include the severity. + required: false + + entries_status: + description: + - Status of the signatures included in filter. default enables the filter and only use filters + with default status of enable. Filters with default status of disable will not be used. + required: false + choices: + - disable + - enable + - default + + entries_exempt_ip_dst_ip: + description: + - Destination IP address and netmask. + required: false + + entries_exempt_ip_src_ip: + description: + - Source IP address and netmask. + required: false + + filter: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + filter_action: + description: + - Action of selected rules. + required: false + choices: + - pass + - block + - default + - reset + + filter_application: + description: + - Vulnerable application filter. + required: false + + filter_location: + description: + - Vulnerability location filter. + required: false + + filter_log: + description: + - Enable/disable logging of selected rules. + required: false + choices: + - disable + - enable + + filter_log_packet: + description: + - Enable/disable packet logging of selected rules. + required: false + choices: + - disable + - enable + + filter_name: + description: + - Filter name. + required: false + + filter_os: + description: + - Vulnerable OS filter. + required: false + + filter_protocol: + description: + - Vulnerable protocol filter. + required: false + + filter_quarantine: + description: + - Quarantine IP or interface. + required: false + choices: + - none + - attacker + + filter_quarantine_expiry: + description: + - Duration of quarantine in minute. + required: false + + filter_quarantine_log: + description: + - Enable/disable logging of selected quarantine. + required: false + choices: + - disable + - enable + + filter_severity: + description: + - Vulnerability severity filter. + required: false + + filter_status: + description: + - Selected rules status. + required: false + choices: + - disable + - enable + - default + + override: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + override_action: + description: + - Action of override rule. + required: false + choices: + - pass + - block + - reset + + override_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + override_log_packet: + description: + - Enable/disable packet logging. + required: false + choices: + - disable + - enable + + override_quarantine: + description: + - Quarantine IP or interface. + required: false + choices: + - none + - attacker + + override_quarantine_expiry: + description: + - Duration of quarantine in minute. + required: false + + override_quarantine_log: + description: + - Enable/disable logging of selected quarantine. + required: false + choices: + - disable + - enable + + override_rule_id: + description: + - Override rule ID. + required: false + + override_status: + description: + - Enable/disable status of override rule. + required: false + choices: + - disable + - enable + + override_exempt_ip_dst_ip: + description: + - Destination IP address and netmask. + required: false + + override_exempt_ip_src_ip: + description: + - Source IP address and netmask. + required: false +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_ips: + name: "Ansible_IPS_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_ips: + name: "Ansible_IPS_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + block_malicious_url: "enable" + entries: [{severity: "high", action: "block", log-packet: "enable"}, {severity: "medium", action: "pass"}] +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_ips_sensor_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/ips/sensor'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/ips/sensor/{name}'.format( + adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], + type="str", default="add"), + + replacemsg_group=dict(required=False, type="str"), + name=dict(required=False, type="str"), + extended_log=dict(required=False, type="str", + choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + block_malicious_url=dict(required=False, type="str", choices=[ + "disable", "enable"]), + entries=dict(required=False, type="list"), + entries_action=dict(required=False, type="str", choices=[ + "pass", "block", "reset", "default"]), + entries_application=dict(required=False, type="str"), + entries_location=dict(required=False, type="str"), + entries_log=dict(required=False, type="str", + choices=["disable", "enable"]), + entries_log_attack_context=dict( + required=False, type="str", choices=["disable", "enable"]), + entries_log_packet=dict(required=False, type="str", choices=[ + "disable", "enable"]), + entries_os=dict(required=False, type="str"), + entries_protocol=dict(required=False, type="str"), + entries_quarantine=dict(required=False, type="str", choices=[ + "none", "attacker"]), + entries_quarantine_expiry=dict(required=False, type="str"), + entries_quarantine_log=dict( + required=False, type="str", choices=["disable", "enable"]), + entries_rate_count=dict(required=False, type="int"), + entries_rate_duration=dict(required=False, type="int"), + entries_rate_mode=dict(required=False, type="str", choices=[ + "periodical", "continuous"]), + entries_rate_track=dict(required=False, type="str", + choices=["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"]), + entries_rule=dict(required=False, type="str"), + entries_severity=dict(required=False, type="str"), + entries_status=dict(required=False, type="str", choices=[ + "disable", "enable", "default"]), + + entries_exempt_ip_dst_ip=dict(required=False, type="str"), + entries_exempt_ip_src_ip=dict(required=False, type="str"), + filter=dict(required=False, type="list"), + filter_action=dict(required=False, type="str", choices=[ + "pass", "block", "default", "reset"]), + filter_application=dict(required=False, type="str"), + filter_location=dict(required=False, type="str"), + filter_log=dict(required=False, type="str", + choices=["disable", "enable"]), + filter_log_packet=dict(required=False, type="str", + choices=["disable", "enable"]), + filter_name=dict(required=False, type="str"), + filter_os=dict(required=False, type="str"), + filter_protocol=dict(required=False, type="str"), + filter_quarantine=dict(required=False, type="str", + choices=["none", "attacker"]), + filter_quarantine_expiry=dict(required=False, type="int"), + filter_quarantine_log=dict(required=False, type="str", choices=[ + "disable", "enable"]), + filter_severity=dict(required=False, type="str"), + filter_status=dict(required=False, type="str", choices=[ + "disable", "enable", "default"]), + override=dict(required=False, type="list"), + override_action=dict(required=False, type="str", + choices=["pass", "block", "reset"]), + override_log=dict(required=False, type="str", + choices=["disable", "enable"]), + override_log_packet=dict(required=False, type="str", choices=[ + "disable", "enable"]), + override_quarantine=dict(required=False, type="str", choices=[ + "none", "attacker"]), + override_quarantine_expiry=dict(required=False, type="int"), + override_quarantine_log=dict( + required=False, type="str", choices=["disable", "enable"]), + override_rule_id=dict(required=False, type="str"), + override_status=dict(required=False, type="str", + choices=["disable", "enable"]), + + override_exempt_ip_dst_ip=dict(required=False, type="str"), + override_exempt_ip_src_ip=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "replacemsg-group": module.params["replacemsg_group"], + "name": module.params["name"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "block-malicious-url": module.params["block_malicious_url"], + "entries": { + "action": module.params["entries_action"], + "application": module.params["entries_application"], + "location": module.params["entries_location"], + "log": module.params["entries_log"], + "log-attack-context": module.params["entries_log_attack_context"], + "log-packet": module.params["entries_log_packet"], + "os": module.params["entries_os"], + "protocol": module.params["entries_protocol"], + "quarantine": module.params["entries_quarantine"], + "quarantine-expiry": module.params["entries_quarantine_expiry"], + "quarantine-log": module.params["entries_quarantine_log"], + "rate-count": module.params["entries_rate_count"], + "rate-duration": module.params["entries_rate_duration"], + "rate-mode": module.params["entries_rate_mode"], + "rate-track": module.params["entries_rate_track"], + "rule": module.params["entries_rule"], + "severity": module.params["entries_severity"], + "status": module.params["entries_status"], + "exempt-ip": { + "dst-ip": module.params["entries_exempt_ip_dst_ip"], + "src-ip": module.params["entries_exempt_ip_src_ip"], + }, + }, + "filter": { + "action": module.params["filter_action"], + "application": module.params["filter_application"], + "location": module.params["filter_location"], + "log": module.params["filter_log"], + "log-packet": module.params["filter_log_packet"], + "name": module.params["filter_name"], + "os": module.params["filter_os"], + "protocol": module.params["filter_protocol"], + "quarantine": module.params["filter_quarantine"], + "quarantine-expiry": module.params["filter_quarantine_expiry"], + "quarantine-log": module.params["filter_quarantine_log"], + "severity": module.params["filter_severity"], + "status": module.params["filter_status"], + }, + "override": { + "action": module.params["override_action"], + "log": module.params["override_log"], + "log-packet": module.params["override_log_packet"], + "quarantine": module.params["override_quarantine"], + "quarantine-expiry": module.params["override_quarantine_expiry"], + "quarantine-log": module.params["override_quarantine_log"], + "rule-id": module.params["override_rule_id"], + "status": module.params["override_status"], + "exempt-ip": { + "dst-ip": module.params["override_exempt_ip_dst_ip"], + "src-ip": module.params["override_exempt_ip_src_ip"], + } + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['entries', 'filter', 'override'] + + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_ips_sensor_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_profile_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_profile_group.py new file mode 100644 index 00000000..cef9d6d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_profile_group.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_profile_group +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage security profiles within FortiManager +description: + - Manage security profile group which allows you to create a group of security profiles and apply that to a policy. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + webfilter_profile: + type: str + description: + - Name of an existing Web filter profile. + required: false + + waf_profile: + type: str + description: + - Name of an existing Web application firewall profile. + required: false + + voip_profile: + type: str + description: + - Name of an existing VoIP profile. + required: false + + ssl_ssh_profile: + type: str + description: + - Name of an existing SSL SSH profile. + required: false + + ssh_filter_profile: + type: str + description: + - Name of an existing SSH filter profile. + required: false + + spamfilter_profile: + type: str + description: + - Name of an existing Spam filter profile. + required: false + + profile_protocol_options: + type: str + description: + - Name of an existing Protocol options profile. + required: false + + name: + type: str + description: + - Profile group name. + required: false + + mms_profile: + type: str + description: + - Name of an existing MMS profile. + required: false + + ips_sensor: + type: str + description: + - Name of an existing IPS sensor. + required: false + + icap_profile: + type: str + description: + - Name of an existing ICAP profile. + required: false + + dnsfilter_profile: + type: str + description: + - Name of an existing DNS filter profile. + required: false + + dlp_sensor: + type: str + description: + - Name of an existing DLP sensor. + required: false + + av_profile: + type: str + description: + - Name of an existing Antivirus profile. + required: false + + application_list: + type: str + description: + - Name of an existing Application list. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_profile_group: + name: "Ansible_TEST_Profile_Group" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_profile_group: + name: "Ansible_TEST_Profile_Group" + mode: "set" + av_profile: "Ansible_AV_Profile" + profile_protocol_options: "default" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_firewall_profile_group_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + url = "" + datagram = {} + + response = DEFAULT_RESULT_OBJ + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/profile-group'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/profile-group/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + webfilter_profile=dict(required=False, type="str"), + waf_profile=dict(required=False, type="str"), + voip_profile=dict(required=False, type="str"), + ssl_ssh_profile=dict(required=False, type="str"), + ssh_filter_profile=dict(required=False, type="str"), + spamfilter_profile=dict(required=False, type="str"), + profile_protocol_options=dict(required=False, type="str"), + name=dict(required=False, type="str"), + mms_profile=dict(required=False, type="str"), + ips_sensor=dict(required=False, type="str"), + icap_profile=dict(required=False, type="str"), + dnsfilter_profile=dict(required=False, type="str"), + dlp_sensor=dict(required=False, type="str"), + av_profile=dict(required=False, type="str"), + application_list=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "webfilter-profile": module.params["webfilter_profile"], + "waf-profile": module.params["waf_profile"], + "voip-profile": module.params["voip_profile"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "ssh-filter-profile": module.params["ssh_filter_profile"], + "spamfilter-profile": module.params["spamfilter_profile"], + "profile-protocol-options": module.params["profile_protocol_options"], + "name": module.params["name"], + "mms-profile": module.params["mms_profile"], + "ips-sensor": module.params["ips_sensor"], + "icap-profile": module.params["icap_profile"], + "dnsfilter-profile": module.params["dnsfilter_profile"], + "dlp-sensor": module.params["dlp_sensor"], + "av-profile": module.params["av_profile"], + "application-list": module.params["application_list"], + + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_firewall_profile_group_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_proxy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_proxy.py new file mode 100644 index 00000000..e7b6473a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_proxy.py @@ -0,0 +1,332 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_proxy +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage proxy security profiles in FortiManager +description: + - Manage proxy security profiles for FortiGates via FortiManager using the FMG API with playbooks + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + strip_encoding: + description: + - Enable/disable stripping unsupported encoding from the request header. + - choice | disable | Disable stripping of unsupported encoding from the request header. + - choice | enable | Enable stripping of unsupported encoding from the request header. + required: false + choices: ["disable", "enable"] + + name: + description: + - Profile name. + required: false + + log_header_change: + description: + - Enable/disable logging HTTP header changes. + - choice | disable | Disable Enable/disable logging HTTP header changes. + - choice | enable | Enable Enable/disable logging HTTP header changes. + required: false + choices: ["disable", "enable"] + + header_x_forwarded_for: + description: + - Action to take on the HTTP x-forwarded-for header in forwarded requests| forwards (pass), adds, or removes the + - HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_x_authenticated_user: + description: + - Action to take on the HTTP x-authenticated-user header in forwarded requests| forwards (pass), adds, or remove + - s the HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_x_authenticated_groups: + description: + - Action to take on the HTTP x-authenticated-groups header in forwarded requests| forwards (pass), adds, or remo + - ves the HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_via_response: + description: + - Action to take on the HTTP via header in forwarded responses| forwards (pass), adds, or removes the HTTP heade + - r. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_via_request: + description: + - Action to take on the HTTP via header in forwarded requests| forwards (pass), adds, or removes the HTTP header + - . + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_front_end_https: + description: + - Action to take on the HTTP front-end-HTTPS header in forwarded requests| forwards (pass), adds, or removes the + - HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_client_ip: + description: + - Actions to take on the HTTP client-IP header in forwarded requests| forwards (pass), adds, or removes the HTTP + - header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + headers: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + headers_action: + description: + - Action when HTTP the header forwarded. + - choice | add-to-request | Add the HTTP header to request. + - choice | add-to-response | Add the HTTP header to response. + - choice | remove-from-request | Remove the HTTP header from request. + - choice | remove-from-response | Remove the HTTP header from response. + required: false + choices: ["add-to-request", "add-to-response", "remove-from-request", "remove-from-response"] + + headers_content: + description: + - HTTP header's content. + required: false + + headers_name: + description: + - HTTP forwarded header name. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_proxy: + name: "Ansible_Web_Proxy_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_proxy: + name: "Ansible_Web_Proxy_Profile" + mode: "set" + header_client_ip: "pass" + header_front_end_https: "add" + header_via_request: "remove" + header_via_response: "pass" + header_x_authenticated_groups: "add" + header_x_authenticated_user: "remove" + strip_encoding: "enable" + log_header_change: "enable" + header_x_forwarded_for: "pass" + headers_action: "add-to-request" + headers_content: "test" + headers_name: "test_header" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_web_proxy_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/web-proxy/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/web-proxy/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + strip_encoding=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + log_header_change=dict(required=False, type="str", choices=["disable", "enable"]), + header_x_forwarded_for=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_x_authenticated_user=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_x_authenticated_groups=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_via_response=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_via_request=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_front_end_https=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_client_ip=dict(required=False, type="str", choices=["pass", "add", "remove"]), + headers=dict(required=False, type="list"), + headers_action=dict(required=False, type="str", choices=["add-to-request", "add-to-response", + "remove-from-request", "remove-from-response"]), + headers_content=dict(required=False, type="str"), + headers_name=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "strip-encoding": module.params["strip_encoding"], + "name": module.params["name"], + "log-header-change": module.params["log_header_change"], + "header-x-forwarded-for": module.params["header_x_forwarded_for"], + "header-x-authenticated-user": module.params["header_x_authenticated_user"], + "header-x-authenticated-groups": module.params["header_x_authenticated_groups"], + "header-via-response": module.params["header_via_response"], + "header-via-request": module.params["header_via_request"], + "header-front-end-https": module.params["header_front_end_https"], + "header-client-ip": module.params["header_client_ip"], + "headers": { + "action": module.params["headers_action"], + "content": module.params["headers_content"], + "name": module.params["headers_name"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['headers'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_web_proxy_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_spam.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_spam.py new file mode 100644 index 00000000..c2f9f819 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_spam.py @@ -0,0 +1,607 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_spam +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: spam filter profile for FMG +description: + - Manage spam filter security profiles within FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + spam_rbl_table: + description: + - Anti-spam DNSBL table ID. + required: false + + spam_mheader_table: + description: + - Anti-spam MIME header table ID. + required: false + + spam_log_fortiguard_response: + description: + - Enable/disable logging FortiGuard spam response. + required: false + choices: + - disable + - enable + + spam_log: + description: + - Enable/disable spam logging for email filtering. + required: false + choices: + - disable + - enable + + spam_iptrust_table: + description: + - Anti-spam IP trust table ID. + required: false + + spam_filtering: + description: + - Enable/disable spam filtering. + required: false + choices: + - disable + - enable + + spam_bword_threshold: + description: + - Spam banned word threshold. + required: false + + spam_bword_table: + description: + - Anti-spam banned word table ID. + required: false + + spam_bwl_table: + description: + - Anti-spam black/white list table ID. + required: false + + replacemsg_group: + description: + - Replacement message group. + required: false + + options: + description: + - None + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - bannedword + - spamfsip + - spamfssubmit + - spamfschksum + - spamfsurl + - spamhelodns + - spamraddrdns + - spamrbl + - spamhdrcheck + - spamfsphish + - spambwl + + name: + description: + - Profile name. + required: false + + flow_based: + description: + - Enable/disable flow-based spam filtering. + required: false + choices: + - disable + - enable + + external: + description: + - Enable/disable external Email inspection. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + gmail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + gmail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + imap: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imap_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + + imap_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + imap_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + imap_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_action: + description: + - Action for spam email. + required: false + choices: + - pass + - discard + + mapi_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + msn_hotmail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + msn_hotmail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + pop3: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + + pop3_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + pop3_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + pop3_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + smtp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtp_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + - discard + + smtp_hdrip: + description: + - Enable/disable SMTP email header IP checks for spamfsip, spamrbl and spambwl filters. + required: false + choices: + - disable + - enable + + smtp_local_override: + description: + - Enable/disable local filter to override SMTP remote check result. + required: false + choices: + - disable + - enable + + smtp_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + smtp_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + smtp_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + yahoo_mail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + yahoo_mail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_spam: + name: "Ansible_Spam_Filter_Profile" + mode: "delete" + + - name: Create FMGR_SPAMFILTER_PROFILE + community.network.fmgr_secprof_spam: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + mode: "set" + adom: "root" + spam_log_fortiguard_response: "enable" + spam_iptrust_table: + spam_filtering: "enable" + spam_bword_threshold: 10 + options: ["bannedword", "spamfsip", "spamfsurl", "spamrbl", "spamfsphish", "spambwl"] + name: "Ansible_Spam_Filter_Profile" + flow_based: "enable" + external: "enable" + comment: "Created by Ansible" + gmail_log: "enable" + spam_log: "enable" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_spamfilter_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/spamfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/spamfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + spam_rbl_table=dict(required=False, type="str"), + spam_mheader_table=dict(required=False, type="str"), + spam_log_fortiguard_response=dict(required=False, type="str", choices=["disable", "enable"]), + spam_log=dict(required=False, type="str", choices=["disable", "enable"]), + spam_iptrust_table=dict(required=False, type="str"), + spam_filtering=dict(required=False, type="str", choices=["disable", "enable"]), + spam_bword_threshold=dict(required=False, type="int"), + spam_bword_table=dict(required=False, type="str"), + spam_bwl_table=dict(required=False, type="str"), + replacemsg_group=dict(required=False, type="str"), + options=dict(required=False, type="list", choices=["bannedword", + "spamfsip", + "spamfssubmit", + "spamfschksum", + "spamfsurl", + "spamhelodns", + "spamraddrdns", + "spamrbl", + "spamhdrcheck", + "spamfsphish", + "spambwl"]), + name=dict(required=False, type="str"), + flow_based=dict(required=False, type="str", choices=["disable", "enable"]), + external=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + gmail=dict(required=False, type="dict"), + gmail_log=dict(required=False, type="str", choices=["disable", "enable"]), + imap=dict(required=False, type="dict"), + imap_action=dict(required=False, type="str", choices=["pass", "tag"]), + imap_log=dict(required=False, type="str", choices=["disable", "enable"]), + imap_tag_msg=dict(required=False, type="str"), + imap_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + mapi=dict(required=False, type="dict"), + mapi_action=dict(required=False, type="str", choices=["pass", "discard"]), + mapi_log=dict(required=False, type="str", choices=["disable", "enable"]), + msn_hotmail=dict(required=False, type="dict"), + msn_hotmail_log=dict(required=False, type="str", choices=["disable", "enable"]), + pop3=dict(required=False, type="dict"), + pop3_action=dict(required=False, type="str", choices=["pass", "tag"]), + pop3_log=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_tag_msg=dict(required=False, type="str"), + pop3_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + smtp=dict(required=False, type="dict"), + smtp_action=dict(required=False, type="str", choices=["pass", "tag", "discard"]), + smtp_hdrip=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_local_override=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_log=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_tag_msg=dict(required=False, type="str"), + smtp_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + yahoo_mail=dict(required=False, type="dict"), + yahoo_mail_log=dict(required=False, type="str", choices=["disable", "enable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "spam-rbl-table": module.params["spam_rbl_table"], + "spam-mheader-table": module.params["spam_mheader_table"], + "spam-log-fortiguard-response": module.params["spam_log_fortiguard_response"], + "spam-log": module.params["spam_log"], + "spam-iptrust-table": module.params["spam_iptrust_table"], + "spam-filtering": module.params["spam_filtering"], + "spam-bword-threshold": module.params["spam_bword_threshold"], + "spam-bword-table": module.params["spam_bword_table"], + "spam-bwl-table": module.params["spam_bwl_table"], + "replacemsg-group": module.params["replacemsg_group"], + "options": module.params["options"], + "name": module.params["name"], + "flow-based": module.params["flow_based"], + "external": module.params["external"], + "comment": module.params["comment"], + "gmail": { + "log": module.params["gmail_log"], + }, + "imap": { + "action": module.params["imap_action"], + "log": module.params["imap_log"], + "tag-msg": module.params["imap_tag_msg"], + "tag-type": module.params["imap_tag_type"], + }, + "mapi": { + "action": module.params["mapi_action"], + "log": module.params["mapi_log"], + }, + "msn-hotmail": { + "log": module.params["msn_hotmail_log"], + }, + "pop3": { + "action": module.params["pop3_action"], + "log": module.params["pop3_log"], + "tag-msg": module.params["pop3_tag_msg"], + "tag-type": module.params["pop3_tag_type"], + }, + "smtp": { + "action": module.params["smtp_action"], + "hdrip": module.params["smtp_hdrip"], + "local-override": module.params["smtp_local_override"], + "log": module.params["smtp_log"], + "tag-msg": module.params["smtp_tag_msg"], + "tag-type": module.params["smtp_tag_type"], + }, + "yahoo-mail": { + "log": module.params["yahoo_mail_log"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['gmail', 'imap', 'mapi', 'msn-hotmail', 'pop3', 'smtp', 'yahoo-mail'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + + results = fmgr_spamfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ssl_ssh.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ssl_ssh.py new file mode 100644 index 00000000..ec421b5e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_ssl_ssh.py @@ -0,0 +1,954 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_ssl_ssh +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage SSL and SSH security profiles in FortiManager +description: + - Manage SSL and SSH security profiles in FortiManager via the FMG API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + whitelist: + description: + - Enable/disable exempting servers by FortiGuard whitelist. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + use_ssl_server: + description: + - Enable/disable the use of SSL server table for SSL offloading. + - choice | disable | Don't use SSL server configuration. + - choice | enable | Use SSL server configuration. + required: false + choices: ["disable", "enable"] + + untrusted_caname: + description: + - Untrusted CA certificate used by SSL Inspection. + required: false + + ssl_exemptions_log: + description: + - Enable/disable logging SSL exemptions. + - choice | disable | Disable logging SSL exemptions. + - choice | enable | Enable logging SSL exemptions. + required: false + choices: ["disable", "enable"] + + ssl_anomalies_log: + description: + - Enable/disable logging SSL anomalies. + - choice | disable | Disable logging SSL anomalies. + - choice | enable | Enable logging SSL anomalies. + required: false + choices: ["disable", "enable"] + + server_cert_mode: + description: + - Re-sign or replace the server's certificate. + - choice | re-sign | Multiple clients connecting to multiple servers. + - choice | replace | Protect an SSL server. + required: false + choices: ["re-sign", "replace"] + + server_cert: + description: + - Certificate used by SSL Inspection to replace server certificate. + required: false + + rpc_over_https: + description: + - Enable/disable inspection of RPC over HTTPS. + - choice | disable | Disable inspection of RPC over HTTPS. + - choice | enable | Enable inspection of RPC over HTTPS. + required: false + choices: ["disable", "enable"] + + name: + description: + - Name. + required: false + + mapi_over_https: + description: + - Enable/disable inspection of MAPI over HTTPS. + - choice | disable | Disable inspection of MAPI over HTTPS. + - choice | enable | Enable inspection of MAPI over HTTPS. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Optional comments. + required: false + + caname: + description: + - CA certificate used by SSL Inspection. + required: false + + ftps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ftps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ftps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + ftps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ftps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ftps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + https: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + https_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + https_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + https_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + https_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | certificate-inspection | Inspect SSL handshake only. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "certificate-inspection", "deep-inspection"] + + https_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + https_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + imaps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imaps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + imaps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + imaps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + imaps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + imaps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + imaps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + pop3s: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3s_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + pop3s_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + pop3s_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + pop3s_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + pop3s_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + pop3s_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + smtps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + smtps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + smtps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + smtps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + smtps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + smtps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + ssh: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssh_inspect_all: + description: + - Level of SSL inspection. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ssh_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + ssh_ssh_algorithm: + description: + - Relative strength of encryption algorithms accepted during negotiation. + - choice | compatible | Allow a broader set of encryption algorithms for best compatibility. + - choice | high-encryption | Allow only AES-CTR, AES-GCM ciphers and high encryption algorithms. + required: false + choices: ["compatible", "high-encryption"] + + ssh_ssh_policy_check: + description: + - Enable/disable SSH policy check. + - choice | disable | Disable SSH policy check. + - choice | enable | Enable SSH policy check. + required: false + choices: ["disable", "enable"] + + ssh_ssh_tun_policy_check: + description: + - Enable/disable SSH tunnel policy check. + - choice | disable | Disable SSH tunnel policy check. + - choice | enable | Enable SSH tunnel policy check. + required: false + choices: ["disable", "enable"] + + ssh_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ssh_unsupported_version: + description: + - Action based on SSH version being unsupported. + - choice | block | Block. + - choice | bypass | Bypass. + required: false + choices: ["block", "bypass"] + + ssl: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ssl_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_inspect_all: + description: + - Level of SSL inspection. + - choice | disable | Disable. + - choice | certificate-inspection | Inspect SSL handshake only. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "certificate-inspection", "deep-inspection"] + + ssl_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + ssl_exempt: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_exempt_address: + description: + - IPv4 address object. + required: false + + ssl_exempt_address6: + description: + - IPv6 address object. + required: false + + ssl_exempt_fortiguard_category: + description: + - FortiGuard category ID. + required: false + + ssl_exempt_regex: + description: + - Exempt servers by regular expression. + required: false + + ssl_exempt_type: + description: + - Type of address object (IPv4 or IPv6) or FortiGuard category. + - choice | fortiguard-category | FortiGuard category. + - choice | address | Firewall IPv4 address. + - choice | address6 | Firewall IPv6 address. + - choice | wildcard-fqdn | Fully Qualified Domain Name with wildcard characters. + - choice | regex | Regular expression FQDN. + required: false + choices: ["fortiguard-category", "address", "address6", "wildcard-fqdn", "regex"] + + ssl_exempt_wildcard_fqdn: + description: + - Exempt servers by wildcard FQDN. + required: false + + ssl_server: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_server_ftps_client_cert_request: + description: + - Action based on client certificate request failure during the FTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_https_client_cert_request: + description: + - Action based on client certificate request failure during the HTTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_imaps_client_cert_request: + description: + - Action based on client certificate request failure during the IMAPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_ip: + description: + - IPv4 address of the SSL server. + required: false + + ssl_server_pop3s_client_cert_request: + description: + - Action based on client certificate request failure during the POP3S handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_smtps_client_cert_request: + description: + - Action based on client certificate request failure during the SMTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_ssl_other_client_cert_request: + description: + - Action based on client certificate request failure during an SSL protocol handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_ssl_ssh: + name: Ansible_SSL_SSH_Profile + mode: delete + + - name: CREATE Profile + community.network.fmgr_secprof_ssl_ssh: + name: Ansible_SSL_SSH_Profile + comment: "Created by Ansible Module TEST" + mode: set + mapi_over_https: enable + rpc_over_https: enable + server_cert_mode: replace + ssl_anomalies_log: enable + ssl_exemptions_log: enable + use_ssl_server: enable + whitelist: enable +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_firewall_ssl_ssh_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ssl-ssh-profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ssl-ssh-profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + whitelist=dict(required=False, type="str", choices=["disable", "enable"]), + use_ssl_server=dict(required=False, type="str", choices=["disable", "enable"]), + untrusted_caname=dict(required=False, type="str"), + ssl_exemptions_log=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_anomalies_log=dict(required=False, type="str", choices=["disable", "enable"]), + server_cert_mode=dict(required=False, type="str", choices=["re-sign", "replace"]), + server_cert=dict(required=False, type="str"), + rpc_over_https=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + mapi_over_https=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + caname=dict(required=False, type="str"), + ftps=dict(required=False, type="list"), + ftps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + ftps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ftps_ports=dict(required=False, type="str"), + ftps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ftps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ftps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + https=dict(required=False, type="list"), + https_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + https_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + https_ports=dict(required=False, type="str"), + https_status=dict(required=False, type="str", choices=["disable", "certificate-inspection", "deep-inspection"]), + https_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + https_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + imaps=dict(required=False, type="list"), + imaps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + imaps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + imaps_ports=dict(required=False, type="str"), + imaps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + imaps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + imaps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + pop3s=dict(required=False, type="list"), + pop3s_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + pop3s_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + pop3s_ports=dict(required=False, type="str"), + pop3s_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + pop3s_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + pop3s_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + smtps=dict(required=False, type="list"), + smtps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + smtps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + smtps_ports=dict(required=False, type="str"), + smtps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + smtps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + smtps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + ssh=dict(required=False, type="list"), + ssh_inspect_all=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ssh_ports=dict(required=False, type="str"), + ssh_ssh_algorithm=dict(required=False, type="str", choices=["compatible", "high-encryption"]), + ssh_ssh_policy_check=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_ssh_tun_policy_check=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ssh_unsupported_version=dict(required=False, type="str", choices=["block", "bypass"]), + ssl=dict(required=False, type="list"), + ssl_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_inspect_all=dict(required=False, type="str", choices=["disable", "certificate-inspection", + "deep-inspection"]), + ssl_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + ssl_exempt=dict(required=False, type="list"), + ssl_exempt_address=dict(required=False, type="str"), + ssl_exempt_address6=dict(required=False, type="str"), + ssl_exempt_fortiguard_category=dict(required=False, type="str"), + ssl_exempt_regex=dict(required=False, type="str"), + ssl_exempt_type=dict(required=False, type="str", choices=["fortiguard-category", "address", "address6", + "wildcard-fqdn", "regex"]), + ssl_exempt_wildcard_fqdn=dict(required=False, type="str"), + ssl_server=dict(required=False, type="list"), + ssl_server_ftps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_https_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_imaps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_ip=dict(required=False, type="str"), + ssl_server_pop3s_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_smtps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_ssl_other_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", + "block"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "whitelist": module.params["whitelist"], + "use-ssl-server": module.params["use_ssl_server"], + "untrusted-caname": module.params["untrusted_caname"], + "ssl-exemptions-log": module.params["ssl_exemptions_log"], + "ssl-anomalies-log": module.params["ssl_anomalies_log"], + "server-cert-mode": module.params["server_cert_mode"], + "server-cert": module.params["server_cert"], + "rpc-over-https": module.params["rpc_over_https"], + "name": module.params["name"], + "mapi-over-https": module.params["mapi_over_https"], + "comment": module.params["comment"], + "caname": module.params["caname"], + "ftps": { + "allow-invalid-server-cert": module.params["ftps_allow_invalid_server_cert"], + "client-cert-request": module.params["ftps_client_cert_request"], + "ports": module.params["ftps_ports"], + "status": module.params["ftps_status"], + "unsupported-ssl": module.params["ftps_unsupported_ssl"], + "untrusted-cert": module.params["ftps_untrusted_cert"], + }, + "https": { + "allow-invalid-server-cert": module.params["https_allow_invalid_server_cert"], + "client-cert-request": module.params["https_client_cert_request"], + "ports": module.params["https_ports"], + "status": module.params["https_status"], + "unsupported-ssl": module.params["https_unsupported_ssl"], + "untrusted-cert": module.params["https_untrusted_cert"], + }, + "imaps": { + "allow-invalid-server-cert": module.params["imaps_allow_invalid_server_cert"], + "client-cert-request": module.params["imaps_client_cert_request"], + "ports": module.params["imaps_ports"], + "status": module.params["imaps_status"], + "unsupported-ssl": module.params["imaps_unsupported_ssl"], + "untrusted-cert": module.params["imaps_untrusted_cert"], + }, + "pop3s": { + "allow-invalid-server-cert": module.params["pop3s_allow_invalid_server_cert"], + "client-cert-request": module.params["pop3s_client_cert_request"], + "ports": module.params["pop3s_ports"], + "status": module.params["pop3s_status"], + "unsupported-ssl": module.params["pop3s_unsupported_ssl"], + "untrusted-cert": module.params["pop3s_untrusted_cert"], + }, + "smtps": { + "allow-invalid-server-cert": module.params["smtps_allow_invalid_server_cert"], + "client-cert-request": module.params["smtps_client_cert_request"], + "ports": module.params["smtps_ports"], + "status": module.params["smtps_status"], + "unsupported-ssl": module.params["smtps_unsupported_ssl"], + "untrusted-cert": module.params["smtps_untrusted_cert"], + }, + "ssh": { + "inspect-all": module.params["ssh_inspect_all"], + "ports": module.params["ssh_ports"], + "ssh-algorithm": module.params["ssh_ssh_algorithm"], + "ssh-policy-check": module.params["ssh_ssh_policy_check"], + "ssh-tun-policy-check": module.params["ssh_ssh_tun_policy_check"], + "status": module.params["ssh_status"], + "unsupported-version": module.params["ssh_unsupported_version"], + }, + "ssl": { + "allow-invalid-server-cert": module.params["ssl_allow_invalid_server_cert"], + "client-cert-request": module.params["ssl_client_cert_request"], + "inspect-all": module.params["ssl_inspect_all"], + "unsupported-ssl": module.params["ssl_unsupported_ssl"], + "untrusted-cert": module.params["ssl_untrusted_cert"], + }, + "ssl-exempt": { + "address": module.params["ssl_exempt_address"], + "address6": module.params["ssl_exempt_address6"], + "fortiguard-category": module.params["ssl_exempt_fortiguard_category"], + "regex": module.params["ssl_exempt_regex"], + "type": module.params["ssl_exempt_type"], + "wildcard-fqdn": module.params["ssl_exempt_wildcard_fqdn"], + }, + "ssl-server": { + "ftps-client-cert-request": module.params["ssl_server_ftps_client_cert_request"], + "https-client-cert-request": module.params["ssl_server_https_client_cert_request"], + "imaps-client-cert-request": module.params["ssl_server_imaps_client_cert_request"], + "ip": module.params["ssl_server_ip"], + "pop3s-client-cert-request": module.params["ssl_server_pop3s_client_cert_request"], + "smtps-client-cert-request": module.params["ssl_server_smtps_client_cert_request"], + "ssl-other-client-cert-request": module.params["ssl_server_ssl_other_client_cert_request"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['ftps', 'https', 'imaps', 'pop3s', 'smtps', 'ssh', 'ssl', 'ssl-exempt', 'ssl-server'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + + results = fmgr_firewall_ssl_ssh_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_voip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_voip.py new file mode 100644 index 00000000..99ef71e9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_voip.py @@ -0,0 +1,1198 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_voip +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: VOIP security profiles in FMG +description: + - Manage VOIP security profiles in FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + name: + description: + - Profile name. + required: false + + comment: + description: + - Comment. + required: false + + sccp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + sccp_block_mcast: + description: + - Enable/disable block multicast RTP connections. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_log_call_summary: + description: + - Enable/disable log summary of SCCP calls. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_log_violations: + description: + - Enable/disable logging of SCCP violations. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_max_calls: + description: + - Maximum calls per minute per SCCP client (max 65535). + required: false + + sccp_status: + description: + - Enable/disable SCCP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_verify_header: + description: + - Enable/disable verify SCCP header content. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + sip_ack_rate: + description: + - ACK request rate limit (per second, per policy). + required: false + + sip_block_ack: + description: + - Enable/disable block ACK requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_bye: + description: + - Enable/disable block BYE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_cancel: + description: + - Enable/disable block CANCEL requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_geo_red_options: + description: + - Enable/disable block OPTIONS requests, but OPTIONS requests still notify for redundancy. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_info: + description: + - Enable/disable block INFO requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_invite: + description: + - Enable/disable block INVITE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_long_lines: + description: + - Enable/disable block requests with headers exceeding max-line-length. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_message: + description: + - Enable/disable block MESSAGE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_notify: + description: + - Enable/disable block NOTIFY requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_options: + description: + - Enable/disable block OPTIONS requests and no OPTIONS as notifying message for redundancy either. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_prack: + description: + - Enable/disable block prack requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_publish: + description: + - Enable/disable block PUBLISH requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_refer: + description: + - Enable/disable block REFER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_register: + description: + - Enable/disable block REGISTER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_subscribe: + description: + - Enable/disable block SUBSCRIBE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_unknown: + description: + - Block unrecognized SIP requests (enabled by default). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_update: + description: + - Enable/disable block UPDATE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_bye_rate: + description: + - BYE request rate limit (per second, per policy). + required: false + + sip_call_keepalive: + description: + - Continue tracking calls with no RTP for this many minutes. + required: false + + sip_cancel_rate: + description: + - CANCEL request rate limit (per second, per policy). + required: false + + sip_contact_fixup: + description: + - Fixup contact anyway even if contact's IP|port doesn't match session's IP|port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_hnt_restrict_source_ip: + description: + - Enable/disable restrict RTP source IP to be the same as SIP source IP when HNT is enabled. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_hosted_nat_traversal: + description: + - Hosted NAT Traversal (HNT). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_info_rate: + description: + - INFO request rate limit (per second, per policy). + required: false + + sip_invite_rate: + description: + - INVITE request rate limit (per second, per policy). + required: false + + sip_ips_rtp: + description: + - Enable/disable allow IPS on RTP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_log_call_summary: + description: + - Enable/disable logging of SIP call summary. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_log_violations: + description: + - Enable/disable logging of SIP violations. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_malformed_header_allow: + description: + - Action for malformed Allow header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_call_id: + description: + - Action for malformed Call-ID header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_contact: + description: + - Action for malformed Contact header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_content_length: + description: + - Action for malformed Content-Length header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_content_type: + description: + - Action for malformed Content-Type header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_cseq: + description: + - Action for malformed CSeq header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_expires: + description: + - Action for malformed Expires header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_from: + description: + - Action for malformed From header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_max_forwards: + description: + - Action for malformed Max-Forwards header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_p_asserted_identity: + description: + - Action for malformed P-Asserted-Identity header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_rack: + description: + - Action for malformed RAck header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_record_route: + description: + - Action for malformed Record-Route header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_route: + description: + - Action for malformed Route header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_rseq: + description: + - Action for malformed RSeq header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_a: + description: + - Action for malformed SDP a line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_b: + description: + - Action for malformed SDP b line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_c: + description: + - Action for malformed SDP c line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_i: + description: + - Action for malformed SDP i line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_k: + description: + - Action for malformed SDP k line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_m: + description: + - Action for malformed SDP m line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_o: + description: + - Action for malformed SDP o line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_r: + description: + - Action for malformed SDP r line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_s: + description: + - Action for malformed SDP s line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_t: + description: + - Action for malformed SDP t line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_v: + description: + - Action for malformed SDP v line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_z: + description: + - Action for malformed SDP z line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_to: + description: + - Action for malformed To header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_via: + description: + - Action for malformed VIA header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_request_line: + description: + - Action for malformed request line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_max_body_length: + description: + - Maximum SIP message body length (0 meaning no limit). + required: false + + sip_max_dialogs: + description: + - Maximum number of concurrent calls/dialogs (per policy). + required: false + + sip_max_idle_dialogs: + description: + - Maximum number established but idle dialogs to retain (per policy). + required: false + + sip_max_line_length: + description: + - Maximum SIP header line length (78-4096). + required: false + + sip_message_rate: + description: + - MESSAGE request rate limit (per second, per policy). + required: false + + sip_nat_trace: + description: + - Enable/disable preservation of original IP in SDP i line. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_no_sdp_fixup: + description: + - Enable/disable no SDP fix-up. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_notify_rate: + description: + - NOTIFY request rate limit (per second, per policy). + required: false + + sip_open_contact_pinhole: + description: + - Enable/disable open pinhole for non-REGISTER Contact port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_record_route_pinhole: + description: + - Enable/disable open pinhole for Record-Route port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_register_pinhole: + description: + - Enable/disable open pinhole for REGISTER Contact port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_via_pinhole: + description: + - Enable/disable open pinhole for Via port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_options_rate: + description: + - OPTIONS request rate limit (per second, per policy). + required: false + + sip_prack_rate: + description: + - PRACK request rate limit (per second, per policy). + required: false + + sip_preserve_override: + description: + - Override i line to preserve original IPS (default| append). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_provisional_invite_expiry_time: + description: + - Expiry time for provisional INVITE (10 - 3600 sec). + required: false + + sip_publish_rate: + description: + - PUBLISH request rate limit (per second, per policy). + required: false + + sip_refer_rate: + description: + - REFER request rate limit (per second, per policy). + required: false + + sip_register_contact_trace: + description: + - Enable/disable trace original IP/port within the contact header of REGISTER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_register_rate: + description: + - REGISTER request rate limit (per second, per policy). + required: false + + sip_rfc2543_branch: + description: + - Enable/disable support via branch compliant with RFC 2543. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_rtp: + description: + - Enable/disable create pinholes for RTP traffic to traverse firewall. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_ssl_algorithm: + description: + - Relative strength of encryption algorithms accepted in negotiation. + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + required: false + choices: ["high", "medium", "low"] + + sip_ssl_auth_client: + description: + - Require a client certificate and authenticate it with the peer/peergrp. + required: false + + sip_ssl_auth_server: + description: + - Authenticate the server's certificate with the peer/peergrp. + required: false + + sip_ssl_client_certificate: + description: + - Name of Certificate to offer to server if requested. + required: false + + sip_ssl_client_renegotiation: + description: + - Allow/block client renegotiation by server. + - choice | allow | Allow a SSL client to renegotiate. + - choice | deny | Abort any SSL connection that attempts to renegotiate. + - choice | secure | Reject any SSL connection that does not offer a RFC 5746 Secure Renegotiation Indication. + required: false + choices: ["allow", "deny", "secure"] + + sip_ssl_max_version: + description: + - Highest SSL/TLS version to negotiate. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + sip_ssl_min_version: + description: + - Lowest SSL/TLS version to negotiate. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + sip_ssl_mode: + description: + - SSL/TLS mode for encryption & decryption of traffic. + - choice | off | No SSL. + - choice | full | Client to FortiGate and FortiGate to Server SSL. + required: false + choices: ["off", "full"] + + sip_ssl_pfs: + description: + - SSL Perfect Forward Secrecy. + - choice | require | PFS mandatory. + - choice | deny | PFS rejected. + - choice | allow | PFS allowed. + required: false + choices: ["require", "deny", "allow"] + + sip_ssl_send_empty_frags: + description: + - Send empty fragments to avoid attack on CBC IV (SSL 3.0 & TLS 1.0 only). + - choice | disable | Do not send empty fragments. + - choice | enable | Send empty fragments. + required: false + choices: ["disable", "enable"] + + sip_ssl_server_certificate: + description: + - Name of Certificate return to the client in every SSL connection. + required: false + + sip_status: + description: + - Enable/disable SIP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_strict_register: + description: + - Enable/disable only allow the registrar to connect. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_subscribe_rate: + description: + - SUBSCRIBE request rate limit (per second, per policy). + required: false + + sip_unknown_header: + description: + - Action for unknown SIP header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_update_rate: + description: + - UPDATE request rate limit (per second, per policy). + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_voip: + name: "Ansible_VOIP_Profile" + mode: "delete" + + - name: Create FMGR_VOIP_PROFILE + community.network.fmgr_secprof_voip: + mode: "set" + adom: "root" + name: "Ansible_VOIP_Profile" + comment: "Created by Ansible" + sccp: {block-mcast: "enable", log-call-summary: "enable", log-violations: "enable", status: "enable"} +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_voip_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/voip/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/voip/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + name=dict(required=False, type="str"), + comment=dict(required=False, type="str"), + sccp=dict(required=False, type="dict"), + sccp_block_mcast=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_log_call_summary=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_log_violations=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_max_calls=dict(required=False, type="int"), + sccp_status=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_verify_header=dict(required=False, type="str", choices=["disable", "enable"]), + sip=dict(required=False, type="dict"), + sip_ack_rate=dict(required=False, type="int"), + sip_block_ack=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_bye=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_cancel=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_geo_red_options=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_info=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_invite=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_long_lines=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_message=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_notify=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_options=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_prack=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_publish=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_refer=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_register=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_subscribe=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_unknown=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_update=dict(required=False, type="str", choices=["disable", "enable"]), + sip_bye_rate=dict(required=False, type="int"), + sip_call_keepalive=dict(required=False, type="int"), + sip_cancel_rate=dict(required=False, type="int"), + sip_contact_fixup=dict(required=False, type="str", choices=["disable", "enable"]), + sip_hnt_restrict_source_ip=dict(required=False, type="str", choices=["disable", "enable"]), + sip_hosted_nat_traversal=dict(required=False, type="str", choices=["disable", "enable"]), + sip_info_rate=dict(required=False, type="int"), + sip_invite_rate=dict(required=False, type="int"), + sip_ips_rtp=dict(required=False, type="str", choices=["disable", "enable"]), + sip_log_call_summary=dict(required=False, type="str", choices=["disable", "enable"]), + sip_log_violations=dict(required=False, type="str", choices=["disable", "enable"]), + sip_malformed_header_allow=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_call_id=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_contact=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_content_length=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_content_type=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_cseq=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_expires=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_from=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_max_forwards=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_p_asserted_identity=dict(required=False, type="str", choices=["pass", + "discard", + "respond"]), + sip_malformed_header_rack=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_record_route=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_route=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_rseq=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_a=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_b=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_c=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_i=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_k=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_m=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_o=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_r=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_s=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_t=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_v=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_z=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_to=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_via=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_request_line=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_max_body_length=dict(required=False, type="int"), + sip_max_dialogs=dict(required=False, type="int"), + sip_max_idle_dialogs=dict(required=False, type="int"), + sip_max_line_length=dict(required=False, type="int"), + sip_message_rate=dict(required=False, type="int"), + sip_nat_trace=dict(required=False, type="str", choices=["disable", "enable"]), + sip_no_sdp_fixup=dict(required=False, type="str", choices=["disable", "enable"]), + sip_notify_rate=dict(required=False, type="int"), + sip_open_contact_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_record_route_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_register_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_via_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_options_rate=dict(required=False, type="int"), + sip_prack_rate=dict(required=False, type="int"), + sip_preserve_override=dict(required=False, type="str", choices=["disable", "enable"]), + sip_provisional_invite_expiry_time=dict(required=False, type="int"), + sip_publish_rate=dict(required=False, type="int"), + sip_refer_rate=dict(required=False, type="int"), + sip_register_contact_trace=dict(required=False, type="str", choices=["disable", "enable"]), + sip_register_rate=dict(required=False, type="int"), + sip_rfc2543_branch=dict(required=False, type="str", choices=["disable", "enable"]), + sip_rtp=dict(required=False, type="str", choices=["disable", "enable"]), + sip_ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low"]), + sip_ssl_auth_client=dict(required=False, type="str"), + sip_ssl_auth_server=dict(required=False, type="str"), + sip_ssl_client_certificate=dict(required=False, type="str"), + sip_ssl_client_renegotiation=dict(required=False, type="str", choices=["allow", "deny", "secure"]), + sip_ssl_max_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + sip_ssl_min_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + sip_ssl_mode=dict(required=False, type="str", choices=["off", "full"]), + sip_ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + sip_ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + sip_ssl_server_certificate=dict(required=False, type="str"), + sip_status=dict(required=False, type="str", choices=["disable", "enable"]), + sip_strict_register=dict(required=False, type="str", choices=["disable", "enable"]), + sip_subscribe_rate=dict(required=False, type="int"), + sip_unknown_header=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_update_rate=dict(required=False, type="int"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "name": module.params["name"], + "comment": module.params["comment"], + "sccp": { + "block-mcast": module.params["sccp_block_mcast"], + "log-call-summary": module.params["sccp_log_call_summary"], + "log-violations": module.params["sccp_log_violations"], + "max-calls": module.params["sccp_max_calls"], + "status": module.params["sccp_status"], + "verify-header": module.params["sccp_verify_header"], + }, + "sip": { + "ack-rate": module.params["sip_ack_rate"], + "block-ack": module.params["sip_block_ack"], + "block-bye": module.params["sip_block_bye"], + "block-cancel": module.params["sip_block_cancel"], + "block-geo-red-options": module.params["sip_block_geo_red_options"], + "block-info": module.params["sip_block_info"], + "block-invite": module.params["sip_block_invite"], + "block-long-lines": module.params["sip_block_long_lines"], + "block-message": module.params["sip_block_message"], + "block-notify": module.params["sip_block_notify"], + "block-options": module.params["sip_block_options"], + "block-prack": module.params["sip_block_prack"], + "block-publish": module.params["sip_block_publish"], + "block-refer": module.params["sip_block_refer"], + "block-register": module.params["sip_block_register"], + "block-subscribe": module.params["sip_block_subscribe"], + "block-unknown": module.params["sip_block_unknown"], + "block-update": module.params["sip_block_update"], + "bye-rate": module.params["sip_bye_rate"], + "call-keepalive": module.params["sip_call_keepalive"], + "cancel-rate": module.params["sip_cancel_rate"], + "contact-fixup": module.params["sip_contact_fixup"], + "hnt-restrict-source-ip": module.params["sip_hnt_restrict_source_ip"], + "hosted-nat-traversal": module.params["sip_hosted_nat_traversal"], + "info-rate": module.params["sip_info_rate"], + "invite-rate": module.params["sip_invite_rate"], + "ips-rtp": module.params["sip_ips_rtp"], + "log-call-summary": module.params["sip_log_call_summary"], + "log-violations": module.params["sip_log_violations"], + "malformed-header-allow": module.params["sip_malformed_header_allow"], + "malformed-header-call-id": module.params["sip_malformed_header_call_id"], + "malformed-header-contact": module.params["sip_malformed_header_contact"], + "malformed-header-content-length": module.params["sip_malformed_header_content_length"], + "malformed-header-content-type": module.params["sip_malformed_header_content_type"], + "malformed-header-cseq": module.params["sip_malformed_header_cseq"], + "malformed-header-expires": module.params["sip_malformed_header_expires"], + "malformed-header-from": module.params["sip_malformed_header_from"], + "malformed-header-max-forwards": module.params["sip_malformed_header_max_forwards"], + "malformed-header-p-asserted-identity": module.params["sip_malformed_header_p_asserted_identity"], + "malformed-header-rack": module.params["sip_malformed_header_rack"], + "malformed-header-record-route": module.params["sip_malformed_header_record_route"], + "malformed-header-route": module.params["sip_malformed_header_route"], + "malformed-header-rseq": module.params["sip_malformed_header_rseq"], + "malformed-header-sdp-a": module.params["sip_malformed_header_sdp_a"], + "malformed-header-sdp-b": module.params["sip_malformed_header_sdp_b"], + "malformed-header-sdp-c": module.params["sip_malformed_header_sdp_c"], + "malformed-header-sdp-i": module.params["sip_malformed_header_sdp_i"], + "malformed-header-sdp-k": module.params["sip_malformed_header_sdp_k"], + "malformed-header-sdp-m": module.params["sip_malformed_header_sdp_m"], + "malformed-header-sdp-o": module.params["sip_malformed_header_sdp_o"], + "malformed-header-sdp-r": module.params["sip_malformed_header_sdp_r"], + "malformed-header-sdp-s": module.params["sip_malformed_header_sdp_s"], + "malformed-header-sdp-t": module.params["sip_malformed_header_sdp_t"], + "malformed-header-sdp-v": module.params["sip_malformed_header_sdp_v"], + "malformed-header-sdp-z": module.params["sip_malformed_header_sdp_z"], + "malformed-header-to": module.params["sip_malformed_header_to"], + "malformed-header-via": module.params["sip_malformed_header_via"], + "malformed-request-line": module.params["sip_malformed_request_line"], + "max-body-length": module.params["sip_max_body_length"], + "max-dialogs": module.params["sip_max_dialogs"], + "max-idle-dialogs": module.params["sip_max_idle_dialogs"], + "max-line-length": module.params["sip_max_line_length"], + "message-rate": module.params["sip_message_rate"], + "nat-trace": module.params["sip_nat_trace"], + "no-sdp-fixup": module.params["sip_no_sdp_fixup"], + "notify-rate": module.params["sip_notify_rate"], + "open-contact-pinhole": module.params["sip_open_contact_pinhole"], + "open-record-route-pinhole": module.params["sip_open_record_route_pinhole"], + "open-register-pinhole": module.params["sip_open_register_pinhole"], + "open-via-pinhole": module.params["sip_open_via_pinhole"], + "options-rate": module.params["sip_options_rate"], + "prack-rate": module.params["sip_prack_rate"], + "preserve-override": module.params["sip_preserve_override"], + "provisional-invite-expiry-time": module.params["sip_provisional_invite_expiry_time"], + "publish-rate": module.params["sip_publish_rate"], + "refer-rate": module.params["sip_refer_rate"], + "register-contact-trace": module.params["sip_register_contact_trace"], + "register-rate": module.params["sip_register_rate"], + "rfc2543-branch": module.params["sip_rfc2543_branch"], + "rtp": module.params["sip_rtp"], + "ssl-algorithm": module.params["sip_ssl_algorithm"], + "ssl-auth-client": module.params["sip_ssl_auth_client"], + "ssl-auth-server": module.params["sip_ssl_auth_server"], + "ssl-client-certificate": module.params["sip_ssl_client_certificate"], + "ssl-client-renegotiation": module.params["sip_ssl_client_renegotiation"], + "ssl-max-version": module.params["sip_ssl_max_version"], + "ssl-min-version": module.params["sip_ssl_min_version"], + "ssl-mode": module.params["sip_ssl_mode"], + "ssl-pfs": module.params["sip_ssl_pfs"], + "ssl-send-empty-frags": module.params["sip_ssl_send_empty_frags"], + "ssl-server-certificate": module.params["sip_ssl_server_certificate"], + "status": module.params["sip_status"], + "strict-register": module.params["sip_strict_register"], + "subscribe-rate": module.params["sip_subscribe_rate"], + "unknown-header": module.params["sip_unknown_header"], + "update-rate": module.params["sip_update_rate"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['sccp', 'sip'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + + results = fmgr_voip_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_waf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_waf.py new file mode 100644 index 00000000..5eaa61f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_waf.py @@ -0,0 +1,1477 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of` +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_waf +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: FortiManager web application firewall security profile +description: + - Manage web application firewall security profiles for FGTs via FMG + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + name: + description: + - WAF Profile name. + required: false + + external: + description: + - Disable/Enable external HTTP Inspection. + - choice | disable | Disable external inspection. + - choice | enable | Enable external inspection. + required: false + choices: ["disable", "enable"] + + extended_log: + description: + - Enable/disable extended logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Comment. + required: false + + address_list: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + address_list_blocked_address: + description: + - Blocked address. + required: false + + address_list_blocked_log: + description: + - Enable/disable logging on blocked addresses. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + address_list_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_trusted_address: + description: + - Trusted address. + required: false + + constraint: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + constraint_content_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_content_length_length: + description: + - Length of HTTP content in bytes (0 to 2147483647). + required: false + + constraint_content_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_content_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_content_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_address: + description: + - Host address. + required: false + + constraint_exception_content_length: + description: + - HTTP content length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_header_length: + description: + - HTTP header length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_hostname: + description: + - Enable/disable hostname check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_line_length: + description: + - HTTP line length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_malformed: + description: + - Enable/disable malformed HTTP request check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_cookie: + description: + - Maximum number of cookies in HTTP request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_header_line: + description: + - Maximum number of HTTP header line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_range_segment: + description: + - Maximum number of range segments in HTTP range line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_url_param: + description: + - Maximum number of parameters in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_method: + description: + - Enable/disable HTTP method check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_param_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_pattern: + description: + - URL pattern. + required: false + + constraint_exception_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_url_param_length: + description: + - Maximum length of parameter in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_version: + description: + - Enable/disable HTTP version check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_header_length_length: + description: + - Length of HTTP header in bytes (0 to 2147483647). + required: false + + constraint_header_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_header_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_action: + description: + - Action for a hostname constraint. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_hostname_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_hostname_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_line_length_length: + description: + - Length of HTTP line in bytes (0 to 2147483647). + required: false + + constraint_line_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_line_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_malformed_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_malformed_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_cookie_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_max_cookie: + description: + - Maximum number of cookies in HTTP request (0 to 2147483647). + required: false + + constraint_max_cookie_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_cookie_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_header_line_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_max_header_line: + description: + - Maximum number HTTP header lines (0 to 2147483647). + required: false + + constraint_max_header_line_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_header_line_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_range_segment_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_max_range_segment: + description: + - Maximum number of range segments in HTTP range line (0 to 2147483647). + required: false + + constraint_max_range_segment_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_range_segment_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_url_param_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_max_url_param: + description: + - Maximum number of parameters in URL (0 to 2147483647). + required: false + + constraint_max_url_param_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_url_param_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_method_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_param_length_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body in bytes (0 to 2147483647). + required: false + + constraint_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_url_param_length_length: + description: + - Maximum length of URL parameter in bytes (0 to 2147483647). + required: false + + constraint_url_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_url_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_version_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_version_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + method_default_allowed_methods: + description: + - Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_severity: + description: + - Severity. + - choice | low | low severity + - choice | medium | medium severity + - choice | high | High severity + required: false + choices: ["low", "medium", "high"] + + method_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_method_policy_address: + description: + - Host address. + required: false + + method_method_policy_allowed_methods: + description: + - Allowed Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_method_policy_pattern: + description: + - URL pattern. + required: false + + method_method_policy_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + signature_credit_card_detection_threshold: + description: + - The minimum number of Credit cards to detect violation. + required: false + + signature_disabled_signature: + description: + - Disabled signatures + required: false + + signature_disabled_sub_class: + description: + - Disabled signature subclasses. + required: false + + signature_custom_signature_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_custom_signature_case_sensitivity: + description: + - Case sensitivity in pattern. + - choice | disable | Case insensitive in pattern. + - choice | enable | Case sensitive in pattern. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_direction: + description: + - Traffic direction. + - choice | request | Match HTTP request. + - choice | response | Match HTTP response. + required: false + choices: ["request", "response"] + + signature_custom_signature_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_name: + description: + - Signature name. + required: false + + signature_custom_signature_pattern: + description: + - Match pattern. + required: false + + signature_custom_signature_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_custom_signature_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_target: + description: + - Match HTTP target. + - FLAG Based Options. Specify multiple in list form. + - flag | arg | HTTP arguments. + - flag | arg-name | Names of HTTP arguments. + - flag | req-body | HTTP request body. + - flag | req-cookie | HTTP request cookies. + - flag | req-cookie-name | HTTP request cookie names. + - flag | req-filename | HTTP request file name. + - flag | req-header | HTTP request headers. + - flag | req-header-name | HTTP request header names. + - flag | req-raw-uri | Raw URI of HTTP request. + - flag | req-uri | URI of HTTP request. + - flag | resp-body | HTTP response body. + - flag | resp-hdr | HTTP response headers. + - flag | resp-status | HTTP response status. + required: false + choices: ["arg","arg-name","req-body","req-cookie","req-cookie-name","req-filename","req-header","req-header-name", + "req-raw-uri","req-uri","resp-body","resp-hdr","resp-status"] + + signature_main_class_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_main_class_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_main_class_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_main_class_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + url_access_action: + description: + - Action. + - choice | bypass | Allow the HTTP request, also bypass further WAF scanning. + - choice | permit | Allow the HTTP request, and continue further WAF scanning. + - choice | block | Block HTTP request. + required: false + choices: ["bypass", "permit", "block"] + + url_access_address: + description: + - Host address. + required: false + + url_access_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + url_access_access_pattern_negate: + description: + - Enable/disable match negation. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_pattern: + description: + - URL pattern. + required: false + + url_access_access_pattern_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_srcaddr: + description: + - Source address. + required: false + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_waf: + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_waf: + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_waf_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/waf/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/waf/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + name=dict(required=False, type="str"), + external=dict(required=False, type="str", choices=["disable", "enable"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + address_list=dict(required=False, type="list"), + address_list_blocked_address=dict(required=False, type="str"), + address_list_blocked_log=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + address_list_status=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_trusted_address=dict(required=False, type="str"), + constraint=dict(required=False, type="list"), + + constraint_content_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_content_length_length=dict(required=False, type="int"), + constraint_content_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_content_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_content_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_exception_address=dict(required=False, type="str"), + constraint_exception_content_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_header_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_hostname=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_line_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_malformed=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_cookie=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_header_line=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_range_segment=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_url_param=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_method=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_pattern=dict(required=False, type="str"), + constraint_exception_regex=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_url_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_version=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_header_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_header_length_length=dict(required=False, type="int"), + constraint_header_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_header_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_header_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_hostname_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_hostname_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_hostname_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_hostname_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_line_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_line_length_length=dict(required=False, type="int"), + constraint_line_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_line_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_line_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_malformed_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_malformed_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_malformed_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_malformed_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_cookie_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_cookie_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_cookie_max_cookie=dict(required=False, type="int"), + constraint_max_cookie_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_cookie_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_header_line_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_header_line_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_header_line_max_header_line=dict(required=False, type="int"), + constraint_max_header_line_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_header_line_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_range_segment_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_range_segment_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_range_segment_max_range_segment=dict(required=False, type="int"), + constraint_max_range_segment_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_range_segment_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_url_param_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_url_param_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_url_param_max_url_param=dict(required=False, type="int"), + constraint_max_url_param_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_url_param_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_method_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_method_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_param_length_length=dict(required=False, type="int"), + constraint_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_url_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_url_param_length_length=dict(required=False, type="int"), + constraint_url_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_url_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_url_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_version_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_version_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_version_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_version_status=dict(required=False, type="str", choices=["disable", "enable"]), + method=dict(required=False, type="list"), + method_default_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_log=dict(required=False, type="str", choices=["disable", "enable"]), + method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + method_method_policy_address=dict(required=False, type="str"), + method_method_policy_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_method_policy_pattern=dict(required=False, type="str"), + method_method_policy_regex=dict(required=False, type="str", choices=["disable", "enable"]), + signature=dict(required=False, type="list"), + signature_credit_card_detection_threshold=dict(required=False, type="int"), + signature_disabled_signature=dict(required=False, type="str"), + signature_disabled_sub_class=dict(required=False, type="str"), + + signature_custom_signature_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_custom_signature_case_sensitivity=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_direction=dict(required=False, type="str", choices=["request", "response"]), + signature_custom_signature_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_name=dict(required=False, type="str"), + signature_custom_signature_pattern=dict(required=False, type="str"), + signature_custom_signature_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_custom_signature_status=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_target=dict(required=False, type="str", choices=["arg", + "arg-name", + "req-body", + "req-cookie", + "req-cookie-name", + "req-filename", + "req-header", + "req-header-name", + "req-raw-uri", + "req-uri", + "resp-body", + "resp-hdr", + "resp-status"]), + + signature_main_class_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_main_class_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_main_class_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_main_class_status=dict(required=False, type="str", choices=["disable", "enable"]), + url_access=dict(required=False, type="list"), + url_access_action=dict(required=False, type="str", choices=["bypass", "permit", "block"]), + url_access_address=dict(required=False, type="str"), + url_access_log=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + + url_access_access_pattern_negate=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_pattern=dict(required=False, type="str"), + url_access_access_pattern_regex=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_srcaddr=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "name": module.params["name"], + "external": module.params["external"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "address-list": { + "blocked-address": module.params["address_list_blocked_address"], + "blocked-log": module.params["address_list_blocked_log"], + "severity": module.params["address_list_severity"], + "status": module.params["address_list_status"], + "trusted-address": module.params["address_list_trusted_address"], + }, + "constraint": { + "content-length": { + "action": module.params["constraint_content_length_action"], + "length": module.params["constraint_content_length_length"], + "log": module.params["constraint_content_length_log"], + "severity": module.params["constraint_content_length_severity"], + "status": module.params["constraint_content_length_status"], + }, + "exception": { + "address": module.params["constraint_exception_address"], + "content-length": module.params["constraint_exception_content_length"], + "header-length": module.params["constraint_exception_header_length"], + "hostname": module.params["constraint_exception_hostname"], + "line-length": module.params["constraint_exception_line_length"], + "malformed": module.params["constraint_exception_malformed"], + "max-cookie": module.params["constraint_exception_max_cookie"], + "max-header-line": module.params["constraint_exception_max_header_line"], + "max-range-segment": module.params["constraint_exception_max_range_segment"], + "max-url-param": module.params["constraint_exception_max_url_param"], + "method": module.params["constraint_exception_method"], + "param-length": module.params["constraint_exception_param_length"], + "pattern": module.params["constraint_exception_pattern"], + "regex": module.params["constraint_exception_regex"], + "url-param-length": module.params["constraint_exception_url_param_length"], + "version": module.params["constraint_exception_version"], + }, + "header-length": { + "action": module.params["constraint_header_length_action"], + "length": module.params["constraint_header_length_length"], + "log": module.params["constraint_header_length_log"], + "severity": module.params["constraint_header_length_severity"], + "status": module.params["constraint_header_length_status"], + }, + "hostname": { + "action": module.params["constraint_hostname_action"], + "log": module.params["constraint_hostname_log"], + "severity": module.params["constraint_hostname_severity"], + "status": module.params["constraint_hostname_status"], + }, + "line-length": { + "action": module.params["constraint_line_length_action"], + "length": module.params["constraint_line_length_length"], + "log": module.params["constraint_line_length_log"], + "severity": module.params["constraint_line_length_severity"], + "status": module.params["constraint_line_length_status"], + }, + "malformed": { + "action": module.params["constraint_malformed_action"], + "log": module.params["constraint_malformed_log"], + "severity": module.params["constraint_malformed_severity"], + "status": module.params["constraint_malformed_status"], + }, + "max-cookie": { + "action": module.params["constraint_max_cookie_action"], + "log": module.params["constraint_max_cookie_log"], + "max-cookie": module.params["constraint_max_cookie_max_cookie"], + "severity": module.params["constraint_max_cookie_severity"], + "status": module.params["constraint_max_cookie_status"], + }, + "max-header-line": { + "action": module.params["constraint_max_header_line_action"], + "log": module.params["constraint_max_header_line_log"], + "max-header-line": module.params["constraint_max_header_line_max_header_line"], + "severity": module.params["constraint_max_header_line_severity"], + "status": module.params["constraint_max_header_line_status"], + }, + "max-range-segment": { + "action": module.params["constraint_max_range_segment_action"], + "log": module.params["constraint_max_range_segment_log"], + "max-range-segment": module.params["constraint_max_range_segment_max_range_segment"], + "severity": module.params["constraint_max_range_segment_severity"], + "status": module.params["constraint_max_range_segment_status"], + }, + "max-url-param": { + "action": module.params["constraint_max_url_param_action"], + "log": module.params["constraint_max_url_param_log"], + "max-url-param": module.params["constraint_max_url_param_max_url_param"], + "severity": module.params["constraint_max_url_param_severity"], + "status": module.params["constraint_max_url_param_status"], + }, + "method": { + "action": module.params["constraint_method_action"], + "log": module.params["constraint_method_log"], + "severity": module.params["constraint_method_severity"], + "status": module.params["constraint_method_status"], + }, + "param-length": { + "action": module.params["constraint_param_length_action"], + "length": module.params["constraint_param_length_length"], + "log": module.params["constraint_param_length_log"], + "severity": module.params["constraint_param_length_severity"], + "status": module.params["constraint_param_length_status"], + }, + "url-param-length": { + "action": module.params["constraint_url_param_length_action"], + "length": module.params["constraint_url_param_length_length"], + "log": module.params["constraint_url_param_length_log"], + "severity": module.params["constraint_url_param_length_severity"], + "status": module.params["constraint_url_param_length_status"], + }, + "version": { + "action": module.params["constraint_version_action"], + "log": module.params["constraint_version_log"], + "severity": module.params["constraint_version_severity"], + "status": module.params["constraint_version_status"], + }, + }, + "method": { + "default-allowed-methods": module.params["method_default_allowed_methods"], + "log": module.params["method_log"], + "severity": module.params["method_severity"], + "status": module.params["method_status"], + "method-policy": { + "address": module.params["method_method_policy_address"], + "allowed-methods": module.params["method_method_policy_allowed_methods"], + "pattern": module.params["method_method_policy_pattern"], + "regex": module.params["method_method_policy_regex"], + }, + }, + "signature": { + "credit-card-detection-threshold": module.params["signature_credit_card_detection_threshold"], + "disabled-signature": module.params["signature_disabled_signature"], + "disabled-sub-class": module.params["signature_disabled_sub_class"], + "custom-signature": { + "action": module.params["signature_custom_signature_action"], + "case-sensitivity": module.params["signature_custom_signature_case_sensitivity"], + "direction": module.params["signature_custom_signature_direction"], + "log": module.params["signature_custom_signature_log"], + "name": module.params["signature_custom_signature_name"], + "pattern": module.params["signature_custom_signature_pattern"], + "severity": module.params["signature_custom_signature_severity"], + "status": module.params["signature_custom_signature_status"], + "target": module.params["signature_custom_signature_target"], + }, + "main-class": { + "action": module.params["signature_main_class_action"], + "log": module.params["signature_main_class_log"], + "severity": module.params["signature_main_class_severity"], + "status": module.params["signature_main_class_status"], + }, + }, + "url-access": { + "action": module.params["url_access_action"], + "address": module.params["url_access_address"], + "log": module.params["url_access_log"], + "severity": module.params["url_access_severity"], + "access-pattern": { + "negate": module.params["url_access_access_pattern_negate"], + "pattern": module.params["url_access_access_pattern_pattern"], + "regex": module.params["url_access_access_pattern_regex"], + "srcaddr": module.params["url_access_access_pattern_srcaddr"], + } + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['address-list', 'constraint', 'method', 'signature', 'url-access'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_waf_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_wanopt.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_wanopt.py new file mode 100644 index 00000000..c6b3ca05 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_wanopt.py @@ -0,0 +1,685 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_wanopt +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: WAN optimization +description: + - Manage WanOpt security profiles in FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + transparent: + description: + - Enable/disable transparent mode. + required: false + choices: + - disable + - enable + + name: + description: + - Profile name. + required: false + + comments: + description: + - Comment. + required: false + + auth_group: + description: + - Optionally add an authentication group to restrict access to the WAN Optimization tunnel to + peers in the authentication group. + required: false + + cifs: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + cifs_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + cifs_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + cifs_port: + description: + - Single port number or port number range for CIFS. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + cifs_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + cifs_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + cifs_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + cifs_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + ftp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftp_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + ftp_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + ftp_port: + description: + - Single port number or port number range for FTP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + ftp_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + ftp_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + ftp_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + ftp_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + http: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + http_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + http_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + http_port: + description: + - Single port number or port number range for HTTP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + http_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + http_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + http_ssl: + description: + - Enable/disable SSL/TLS offloading (hardware acceleration) for HTTPS traffic in this tunnel. + required: false + choices: + - disable + - enable + + http_ssl_port: + description: + - Port on which to expect HTTPS traffic for SSL/TLS offloading. + required: false + + http_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + http_tunnel_non_http: + description: + - Configure how to process non-HTTP traffic when a profile configured for HTTP traffic accepts + a non-HTTP session. Can occur if an application sends non-HTTP traffic using an HTTP destination port. + required: false + choices: + - disable + - enable + + http_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + http_unknown_http_version: + description: + - How to handle HTTP sessions that do not comply with HTTP 0.9, 1.0, or 1.1. + required: false + choices: + - best-effort + - reject + - tunnel + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + mapi_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + mapi_port: + description: + - Single port number or port number range for MAPI. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + mapi_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + mapi_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + mapi_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + tcp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + tcp_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + tcp_byte_caching_opt: + description: + - Select whether TCP byte-caching uses system memory only or both memory and disk space. + required: false + choices: + - mem-only + - mem-disk + + tcp_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + tcp_port: + description: + - Single port number or port number range for TCP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + tcp_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + tcp_ssl: + description: + - Enable/disable SSL/TLS offloading. + required: false + choices: + - disable + - enable + + tcp_ssl_port: + description: + - Port on which to expect HTTPS traffic for SSL/TLS offloading. + required: false + + tcp_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + tcp_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_wanopt: + name: "Ansible_WanOpt_Profile" + mode: "delete" + + - name: Create FMGR_WANOPT_PROFILE + community.network.fmgr_secprof_wanopt: + mode: "set" + adom: "root" + transparent: "enable" + name: "Ansible_WanOpt_Profile" + comments: "Created by Ansible" + cifs: {byte-caching: "enable", + log-traffic: "enable", + port: 80, + prefer-chunking: "dynamic", + status: "enable", + tunnel-sharing: "private"} + ftp: {byte-caching: "enable", + log-traffic: "enable", + port: 80, + prefer-chunking: "dynamic", + secure-tunnel: "disable", + status: "enable", + tunnel-sharing: "private"} +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_wanopt_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/wanopt/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/wanopt/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + transparent=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + auth_group=dict(required=False, type="str"), + cifs=dict(required=False, type="dict"), + cifs_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_port=dict(required=False, type="str"), + cifs_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + cifs_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_status=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + ftp=dict(required=False, type="dict"), + ftp_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_port=dict(required=False, type="str"), + ftp_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + ftp_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_status=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + http=dict(required=False, type="dict"), + http_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + http_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + http_port=dict(required=False, type="str"), + http_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + http_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + http_ssl=dict(required=False, type="str", choices=["disable", "enable"]), + http_ssl_port=dict(required=False, type="str"), + http_status=dict(required=False, type="str", choices=["disable", "enable"]), + http_tunnel_non_http=dict(required=False, type="str", choices=["disable", "enable"]), + http_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + http_unknown_http_version=dict(required=False, type="str", choices=["best-effort", "reject", "tunnel"]), + mapi=dict(required=False, type="dict"), + mapi_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_port=dict(required=False, type="str"), + mapi_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_status=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + tcp=dict(required=False, type="dict"), + tcp_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_byte_caching_opt=dict(required=False, type="str", choices=["mem-only", "mem-disk"]), + tcp_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_port=dict(required=False, type="str"), + tcp_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_ssl=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_ssl_port=dict(required=False, type="str"), + tcp_status=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "transparent": module.params["transparent"], + "name": module.params["name"], + "comments": module.params["comments"], + "auth-group": module.params["auth_group"], + "cifs": { + "byte-caching": module.params["cifs_byte_caching"], + "log-traffic": module.params["cifs_log_traffic"], + "port": module.params["cifs_port"], + "prefer-chunking": module.params["cifs_prefer_chunking"], + "secure-tunnel": module.params["cifs_secure_tunnel"], + "status": module.params["cifs_status"], + "tunnel-sharing": module.params["cifs_tunnel_sharing"], + }, + "ftp": { + "byte-caching": module.params["ftp_byte_caching"], + "log-traffic": module.params["ftp_log_traffic"], + "port": module.params["ftp_port"], + "prefer-chunking": module.params["ftp_prefer_chunking"], + "secure-tunnel": module.params["ftp_secure_tunnel"], + "status": module.params["ftp_status"], + "tunnel-sharing": module.params["ftp_tunnel_sharing"], + }, + "http": { + "byte-caching": module.params["http_byte_caching"], + "log-traffic": module.params["http_log_traffic"], + "port": module.params["http_port"], + "prefer-chunking": module.params["http_prefer_chunking"], + "secure-tunnel": module.params["http_secure_tunnel"], + "ssl": module.params["http_ssl"], + "ssl-port": module.params["http_ssl_port"], + "status": module.params["http_status"], + "tunnel-non-http": module.params["http_tunnel_non_http"], + "tunnel-sharing": module.params["http_tunnel_sharing"], + "unknown-http-version": module.params["http_unknown_http_version"], + }, + "mapi": { + "byte-caching": module.params["mapi_byte_caching"], + "log-traffic": module.params["mapi_log_traffic"], + "port": module.params["mapi_port"], + "secure-tunnel": module.params["mapi_secure_tunnel"], + "status": module.params["mapi_status"], + "tunnel-sharing": module.params["mapi_tunnel_sharing"], + }, + "tcp": { + "byte-caching": module.params["tcp_byte_caching"], + "byte-caching-opt": module.params["tcp_byte_caching_opt"], + "log-traffic": module.params["tcp_log_traffic"], + "port": module.params["tcp_port"], + "secure-tunnel": module.params["tcp_secure_tunnel"], + "ssl": module.params["tcp_ssl"], + "ssl-port": module.params["tcp_ssl_port"], + "status": module.params["tcp_status"], + "tunnel-sharing": module.params["tcp_tunnel_sharing"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['cifs', 'ftp', 'http', 'mapi', 'tcp'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_wanopt_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_web.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_web.py new file mode 100644 index 00000000..cb2432e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/fmgr_secprof_web.py @@ -0,0 +1,1081 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_web +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage web filter security profiles in FortiManager +description: + - Manage web filter security profiles in FortiManager through playbooks using the FMG API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + youtube_channel_status: + description: + - YouTube channel filter status. + - choice | disable | Disable YouTube channel filter. + - choice | blacklist | Block matches. + - choice | whitelist | Allow matches. + required: false + choices: ["disable", "blacklist", "whitelist"] + + wisp_servers: + description: + - WISP servers. + required: false + + wisp_algorithm: + description: + - WISP server selection algorithm. + - choice | auto-learning | Select the lightest loading healthy server. + - choice | primary-secondary | Select the first healthy server in order. + - choice | round-robin | Select the next healthy server. + required: false + choices: ["auto-learning", "primary-secondary", "round-robin"] + + wisp: + description: + - Enable/disable web proxy WISP. + - choice | disable | Disable web proxy WISP. + - choice | enable | Enable web proxy WISP. + required: false + choices: ["disable", "enable"] + + web_url_log: + description: + - Enable/disable logging URL filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_invalid_domain_log: + description: + - Enable/disable logging invalid domain names. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_ftgd_quota_usage: + description: + - Enable/disable logging daily quota usage. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_ftgd_err_log: + description: + - Enable/disable logging rating errors. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_vbs_log: + description: + - Enable/disable logging VBS scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_unknown_log: + description: + - Enable/disable logging unknown scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_referer_log: + description: + - Enable/disable logging referrers. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_jscript_log: + description: + - Enable/disable logging JScripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_js_log: + description: + - Enable/disable logging Java scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_cookie_removal_log: + description: + - Enable/disable logging blocked cookies. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_cookie_log: + description: + - Enable/disable logging cookie filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_command_block_log: + description: + - Enable/disable logging blocked commands. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_applet_log: + description: + - Enable/disable logging Java applets. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_activex_log: + description: + - Enable/disable logging ActiveX. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_extended_all_action_log: + description: + - Enable/disable extended any filter action logging for web filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_content_log: + description: + - Enable/disable logging logging blocked web content. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + replacemsg_group: + description: + - Replacement message group. + required: false + + post_action: + description: + - Action taken for HTTP POST traffic. + - choice | normal | Normal, POST requests are allowed. + - choice | block | POST requests are blocked. + required: false + choices: ["normal", "block"] + + ovrd_perm: + description: + - FLAG Based Options. Specify multiple in list form. + - flag | bannedword-override | Banned word override. + - flag | urlfilter-override | URL filter override. + - flag | fortiguard-wf-override | FortiGuard Web Filter override. + - flag | contenttype-check-override | Content-type header override. + required: false + choices: + - bannedword-override + - urlfilter-override + - fortiguard-wf-override + - contenttype-check-override + + options: + description: + - FLAG Based Options. Specify multiple in list form. + - flag | block-invalid-url | Block sessions contained an invalid domain name. + - flag | jscript | Javascript block. + - flag | js | JS block. + - flag | vbs | VB script block. + - flag | unknown | Unknown script block. + - flag | wf-referer | Referring block. + - flag | intrinsic | Intrinsic script block. + - flag | wf-cookie | Cookie block. + - flag | per-user-bwl | Per-user black/white list filter + - flag | activexfilter | ActiveX filter. + - flag | cookiefilter | Cookie filter. + - flag | javafilter | Java applet filter. + required: false + choices: + - block-invalid-url + - jscript + - js + - vbs + - unknown + - wf-referer + - intrinsic + - wf-cookie + - per-user-bwl + - activexfilter + - cookiefilter + - javafilter + + name: + description: + - Profile name. + required: false + + log_all_url: + description: + - Enable/disable logging all URLs visited. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + inspection_mode: + description: + - Web filtering inspection mode. + - choice | proxy | Proxy. + - choice | flow-based | Flow based. + required: false + choices: ["proxy", "flow-based"] + + https_replacemsg: + description: + - Enable replacement messages for HTTPS. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + extended_log: + description: + - Enable/disable extended logging for web filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Optional comments. + required: false + + ftgd_wf: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftgd_wf_exempt_quota: + description: + - Do not stop quota for these categories. + required: false + + ftgd_wf_max_quota_timeout: + description: + - Maximum FortiGuard quota used by single page view in seconds (excludes streams). + required: false + + ftgd_wf_options: + description: + - Options for FortiGuard Web Filter. + - FLAG Based Options. Specify multiple in list form. + - flag | error-allow | Allow web pages with a rating error to pass through. + - flag | rate-server-ip | Rate the server IP in addition to the domain name. + - flag | connect-request-bypass | Bypass connection which has CONNECT request. + - flag | ftgd-disable | Disable FortiGuard scanning. + required: false + choices: ["error-allow", "rate-server-ip", "connect-request-bypass", "ftgd-disable"] + + ftgd_wf_ovrd: + description: + - Allow web filter profile overrides. + required: false + + ftgd_wf_rate_crl_urls: + description: + - Enable/disable rating CRL by URL. + - choice | disable | Disable rating CRL by URL. + - choice | enable | Enable rating CRL by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_css_urls: + description: + - Enable/disable rating CSS by URL. + - choice | disable | Disable rating CSS by URL. + - choice | enable | Enable rating CSS by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_image_urls: + description: + - Enable/disable rating images by URL. + - choice | disable | Disable rating images by URL (blocked images are replaced with blanks). + - choice | enable | Enable rating images by URL (blocked images are replaced with blanks). + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_javascript_urls: + description: + - Enable/disable rating JavaScript by URL. + - choice | disable | Disable rating JavaScript by URL. + - choice | enable | Enable rating JavaScript by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_filters_action: + description: + - Action to take for matches. + - choice | block | Block access. + - choice | monitor | Allow access while logging the action. + - choice | warning | Allow access after warning the user. + - choice | authenticate | Authenticate user before allowing access. + required: false + choices: ["block", "monitor", "warning", "authenticate"] + + ftgd_wf_filters_auth_usr_grp: + description: + - Groups with permission to authenticate. + required: false + + ftgd_wf_filters_category: + description: + - Categories and groups the filter examines. + required: false + + ftgd_wf_filters_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ftgd_wf_filters_override_replacemsg: + description: + - Override replacement message. + required: false + + ftgd_wf_filters_warn_duration: + description: + - Duration of warnings. + required: false + + ftgd_wf_filters_warning_duration_type: + description: + - Re-display warning after closing browser or after a timeout. + - choice | session | After session ends. + - choice | timeout | After timeout occurs. + required: false + choices: ["session", "timeout"] + + ftgd_wf_filters_warning_prompt: + description: + - Warning prompts in each category or each domain. + - choice | per-domain | Per-domain warnings. + - choice | per-category | Per-category warnings. + required: false + choices: ["per-domain", "per-category"] + + ftgd_wf_quota_category: + description: + - FortiGuard categories to apply quota to (category action must be set to monitor). + required: false + + ftgd_wf_quota_duration: + description: + - Duration of quota. + required: false + + ftgd_wf_quota_override_replacemsg: + description: + - Override replacement message. + required: false + + ftgd_wf_quota_type: + description: + - Quota type. + - choice | time | Use a time-based quota. + - choice | traffic | Use a traffic-based quota. + required: false + choices: ["time", "traffic"] + + ftgd_wf_quota_unit: + description: + - Traffic quota unit of measurement. + - choice | B | Quota in bytes. + - choice | KB | Quota in kilobytes. + - choice | MB | Quota in megabytes. + - choice | GB | Quota in gigabytes. + required: false + choices: ["B", "KB", "MB", "GB"] + + ftgd_wf_quota_value: + description: + - Traffic quota value. + required: false + + override: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + override_ovrd_cookie: + description: + - Allow/deny browser-based (cookie) overrides. + - choice | deny | Deny browser-based (cookie) override. + - choice | allow | Allow browser-based (cookie) override. + required: false + choices: ["deny", "allow"] + + override_ovrd_dur: + description: + - Override duration. + required: false + + override_ovrd_dur_mode: + description: + - Override duration mode. + - choice | constant | Constant mode. + - choice | ask | Prompt for duration when initiating an override. + required: false + choices: ["constant", "ask"] + + override_ovrd_scope: + description: + - Override scope. + - choice | user | Override for the user. + - choice | user-group | Override for the user's group. + - choice | ip | Override for the initiating IP. + - choice | ask | Prompt for scope when initiating an override. + - choice | browser | Create browser-based (cookie) override. + required: false + choices: ["user", "user-group", "ip", "ask", "browser"] + + override_ovrd_user_group: + description: + - User groups with permission to use the override. + required: false + + override_profile: + description: + - Web filter profile with permission to create overrides. + required: false + + override_profile_attribute: + description: + - Profile attribute to retrieve from the RADIUS server. + - choice | User-Name | Use this attribute. + - choice | NAS-IP-Address | Use this attribute. + - choice | Framed-IP-Address | Use this attribute. + - choice | Framed-IP-Netmask | Use this attribute. + - choice | Filter-Id | Use this attribute. + - choice | Login-IP-Host | Use this attribute. + - choice | Reply-Message | Use this attribute. + - choice | Callback-Number | Use this attribute. + - choice | Callback-Id | Use this attribute. + - choice | Framed-Route | Use this attribute. + - choice | Framed-IPX-Network | Use this attribute. + - choice | Class | Use this attribute. + - choice | Called-Station-Id | Use this attribute. + - choice | Calling-Station-Id | Use this attribute. + - choice | NAS-Identifier | Use this attribute. + - choice | Proxy-State | Use this attribute. + - choice | Login-LAT-Service | Use this attribute. + - choice | Login-LAT-Node | Use this attribute. + - choice | Login-LAT-Group | Use this attribute. + - choice | Framed-AppleTalk-Zone | Use this attribute. + - choice | Acct-Session-Id | Use this attribute. + - choice | Acct-Multi-Session-Id | Use this attribute. + required: false + choices: + - User-Name + - NAS-IP-Address + - Framed-IP-Address + - Framed-IP-Netmask + - Filter-Id + - Login-IP-Host + - Reply-Message + - Callback-Number + - Callback-Id + - Framed-Route + - Framed-IPX-Network + - Class + - Called-Station-Id + - Calling-Station-Id + - NAS-Identifier + - Proxy-State + - Login-LAT-Service + - Login-LAT-Node + - Login-LAT-Group + - Framed-AppleTalk-Zone + - Acct-Session-Id + - Acct-Multi-Session-Id + + override_profile_type: + description: + - Override profile type. + - choice | list | Profile chosen from list. + - choice | radius | Profile determined by RADIUS server. + required: false + choices: ["list", "radius"] + + url_extraction: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + url_extraction_redirect_header: + description: + - HTTP header name to use for client redirect on blocked requests + required: false + + url_extraction_redirect_no_content: + description: + - Enable / Disable empty message-body entity in HTTP response + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_extraction_redirect_url: + description: + - HTTP header value to use for client redirect on blocked requests + required: false + + url_extraction_server_fqdn: + description: + - URL extraction server FQDN (fully qualified domain name) + required: false + + url_extraction_status: + description: + - Enable URL Extraction + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + web_blacklist: + description: + - Enable/disable automatic addition of URLs detected by FortiSandbox to blacklist. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_bword_table: + description: + - Banned word table ID. + required: false + + web_bword_threshold: + description: + - Banned word score threshold. + required: false + + web_content_header_list: + description: + - Content header list. + required: false + + web_keyword_match: + description: + - Search keywords to log when match is found. + required: false + + web_log_search: + description: + - Enable/disable logging all search phrases. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_safe_search: + description: + - Safe search type. + - FLAG Based Options. Specify multiple in list form. + - flag | url | Insert safe search string into URL. + - flag | header | Insert safe search header. + required: false + choices: ["url", "header"] + + web_urlfilter_table: + description: + - URL filter table ID. + required: false + + web_whitelist: + description: + - FortiGuard whitelist settings. + - FLAG Based Options. Specify multiple in list form. + - flag | exempt-av | Exempt antivirus. + - flag | exempt-webcontent | Exempt web content. + - flag | exempt-activex-java-cookie | Exempt ActiveX-JAVA-Cookie. + - flag | exempt-dlp | Exempt DLP. + - flag | exempt-rangeblock | Exempt RangeBlock. + - flag | extended-log-others | Support extended log. + required: false + choices: + - exempt-av + - exempt-webcontent + - exempt-activex-java-cookie + - exempt-dlp + - exempt-rangeblock + - extended-log-others + + web_youtube_restrict: + description: + - YouTube EDU filter level. + - choice | strict | Strict access for YouTube. + - choice | none | Full access for YouTube. + - choice | moderate | Moderate access for YouTube. + required: false + choices: ["strict", "none", "moderate"] + + youtube_channel_filter: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + youtube_channel_filter_channel_id: + description: + - YouTube channel ID to be filtered. + required: false + + youtube_channel_filter_comment: + description: + - Comment. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_web: + name: "Ansible_Web_Filter_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_web: + name: "Ansible_Web_Filter_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + extended_log: "enable" + inspection_mode: "proxy" + log_all_url: "enable" + options: "js" + ovrd_perm: "bannedword-override" + post_action: "block" + web_content_log: "enable" + web_extended_all_action_log: "enable" + web_filter_activex_log: "enable" + web_filter_applet_log: "enable" + web_filter_command_block_log: "enable" + web_filter_cookie_log: "enable" + web_filter_cookie_removal_log: "enable" + web_filter_js_log: "enable" + web_filter_jscript_log: "enable" + web_filter_referer_log: "enable" + web_filter_unknown_log: "enable" + web_filter_vbs_log: "enable" + web_ftgd_err_log: "enable" + web_ftgd_quota_usage: "enable" + web_invalid_domain_log: "enable" + web_url_log: "enable" + wisp: "enable" + wisp_algorithm: "auto-learning" + youtube_channel_status: "blacklist" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_webfilter_profile_modify(fmgr, paramgram): + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/webfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/webfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + youtube_channel_status=dict(required=False, type="str", choices=["disable", "blacklist", "whitelist"]), + wisp_servers=dict(required=False, type="str"), + wisp_algorithm=dict(required=False, type="str", choices=["auto-learning", "primary-secondary", "round-robin"]), + wisp=dict(required=False, type="str", choices=["disable", "enable"]), + web_url_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_invalid_domain_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_ftgd_quota_usage=dict(required=False, type="str", choices=["disable", "enable"]), + web_ftgd_err_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_vbs_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_unknown_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_referer_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_jscript_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_js_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_cookie_removal_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_cookie_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_command_block_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_applet_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_activex_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_extended_all_action_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_content_log=dict(required=False, type="str", choices=["disable", "enable"]), + replacemsg_group=dict(required=False, type="str"), + post_action=dict(required=False, type="str", choices=["normal", "block"]), + ovrd_perm=dict(required=False, type="list", choices=["bannedword-override", + "urlfilter-override", + "fortiguard-wf-override", + "contenttype-check-override"]), + options=dict(required=False, type="list", choices=["block-invalid-url", + "jscript", + "js", + "vbs", + "unknown", + "wf-referer", + "intrinsic", + "wf-cookie", + "per-user-bwl", + "activexfilter", + "cookiefilter", + "javafilter"]), + name=dict(required=False, type="str"), + log_all_url=dict(required=False, type="str", choices=["disable", "enable"]), + inspection_mode=dict(required=False, type="str", choices=["proxy", "flow-based"]), + https_replacemsg=dict(required=False, type="str", choices=["disable", "enable"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + ftgd_wf=dict(required=False, type="list"), + ftgd_wf_exempt_quota=dict(required=False, type="str"), + ftgd_wf_max_quota_timeout=dict(required=False, type="int"), + ftgd_wf_options=dict(required=False, type="str", choices=["error-allow", "rate-server-ip", + "connect-request-bypass", "ftgd-disable"]), + ftgd_wf_ovrd=dict(required=False, type="str"), + ftgd_wf_rate_crl_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_css_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_image_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_javascript_urls=dict(required=False, type="str", choices=["disable", "enable"]), + + ftgd_wf_filters_action=dict(required=False, type="str", choices=["block", "monitor", + "warning", "authenticate"]), + ftgd_wf_filters_auth_usr_grp=dict(required=False, type="str"), + ftgd_wf_filters_category=dict(required=False, type="str"), + ftgd_wf_filters_log=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_filters_override_replacemsg=dict(required=False, type="str"), + ftgd_wf_filters_warn_duration=dict(required=False, type="str"), + ftgd_wf_filters_warning_duration_type=dict(required=False, type="str", choices=["session", "timeout"]), + ftgd_wf_filters_warning_prompt=dict(required=False, type="str", choices=["per-domain", "per-category"]), + + ftgd_wf_quota_category=dict(required=False, type="str"), + ftgd_wf_quota_duration=dict(required=False, type="str"), + ftgd_wf_quota_override_replacemsg=dict(required=False, type="str"), + ftgd_wf_quota_type=dict(required=False, type="str", choices=["time", "traffic"]), + ftgd_wf_quota_unit=dict(required=False, type="str", choices=["B", "KB", "MB", "GB"]), + ftgd_wf_quota_value=dict(required=False, type="int"), + override=dict(required=False, type="list"), + override_ovrd_cookie=dict(required=False, type="str", choices=["deny", "allow"]), + override_ovrd_dur=dict(required=False, type="str"), + override_ovrd_dur_mode=dict(required=False, type="str", choices=["constant", "ask"]), + override_ovrd_scope=dict(required=False, type="str", choices=["user", "user-group", "ip", "ask", "browser"]), + override_ovrd_user_group=dict(required=False, type="str"), + override_profile=dict(required=False, type="str"), + override_profile_attribute=dict(required=False, type="list", choices=["User-Name", + "NAS-IP-Address", + "Framed-IP-Address", + "Framed-IP-Netmask", + "Filter-Id", + "Login-IP-Host", + "Reply-Message", + "Callback-Number", + "Callback-Id", + "Framed-Route", + "Framed-IPX-Network", + "Class", + "Called-Station-Id", + "Calling-Station-Id", + "NAS-Identifier", + "Proxy-State", + "Login-LAT-Service", + "Login-LAT-Node", + "Login-LAT-Group", + "Framed-AppleTalk-Zone", + "Acct-Session-Id", + "Acct-Multi-Session-Id"]), + override_profile_type=dict(required=False, type="str", choices=["list", "radius"]), + url_extraction=dict(required=False, type="list"), + url_extraction_redirect_header=dict(required=False, type="str"), + url_extraction_redirect_no_content=dict(required=False, type="str", choices=["disable", "enable"]), + url_extraction_redirect_url=dict(required=False, type="str"), + url_extraction_server_fqdn=dict(required=False, type="str"), + url_extraction_status=dict(required=False, type="str", choices=["disable", "enable"]), + web=dict(required=False, type="list"), + web_blacklist=dict(required=False, type="str", choices=["disable", "enable"]), + web_bword_table=dict(required=False, type="str"), + web_bword_threshold=dict(required=False, type="int"), + web_content_header_list=dict(required=False, type="str"), + web_keyword_match=dict(required=False, type="str"), + web_log_search=dict(required=False, type="str", choices=["disable", "enable"]), + web_safe_search=dict(required=False, type="str", choices=["url", "header"]), + web_urlfilter_table=dict(required=False, type="str"), + web_whitelist=dict(required=False, type="list", choices=["exempt-av", + "exempt-webcontent", + "exempt-activex-java-cookie", + "exempt-dlp", + "exempt-rangeblock", + "extended-log-others"]), + web_youtube_restrict=dict(required=False, type="str", choices=["strict", "none", "moderate"]), + youtube_channel_filter=dict(required=False, type="list"), + youtube_channel_filter_channel_id=dict(required=False, type="str"), + youtube_channel_filter_comment=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "youtube-channel-status": module.params["youtube_channel_status"], + "wisp-servers": module.params["wisp_servers"], + "wisp-algorithm": module.params["wisp_algorithm"], + "wisp": module.params["wisp"], + "web-url-log": module.params["web_url_log"], + "web-invalid-domain-log": module.params["web_invalid_domain_log"], + "web-ftgd-quota-usage": module.params["web_ftgd_quota_usage"], + "web-ftgd-err-log": module.params["web_ftgd_err_log"], + "web-filter-vbs-log": module.params["web_filter_vbs_log"], + "web-filter-unknown-log": module.params["web_filter_unknown_log"], + "web-filter-referer-log": module.params["web_filter_referer_log"], + "web-filter-jscript-log": module.params["web_filter_jscript_log"], + "web-filter-js-log": module.params["web_filter_js_log"], + "web-filter-cookie-removal-log": module.params["web_filter_cookie_removal_log"], + "web-filter-cookie-log": module.params["web_filter_cookie_log"], + "web-filter-command-block-log": module.params["web_filter_command_block_log"], + "web-filter-applet-log": module.params["web_filter_applet_log"], + "web-filter-activex-log": module.params["web_filter_activex_log"], + "web-extended-all-action-log": module.params["web_extended_all_action_log"], + "web-content-log": module.params["web_content_log"], + "replacemsg-group": module.params["replacemsg_group"], + "post-action": module.params["post_action"], + "ovrd-perm": module.params["ovrd_perm"], + "options": module.params["options"], + "name": module.params["name"], + "log-all-url": module.params["log_all_url"], + "inspection-mode": module.params["inspection_mode"], + "https-replacemsg": module.params["https_replacemsg"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "ftgd-wf": { + "exempt-quota": module.params["ftgd_wf_exempt_quota"], + "max-quota-timeout": module.params["ftgd_wf_max_quota_timeout"], + "options": module.params["ftgd_wf_options"], + "ovrd": module.params["ftgd_wf_ovrd"], + "rate-crl-urls": module.params["ftgd_wf_rate_crl_urls"], + "rate-css-urls": module.params["ftgd_wf_rate_css_urls"], + "rate-image-urls": module.params["ftgd_wf_rate_image_urls"], + "rate-javascript-urls": module.params["ftgd_wf_rate_javascript_urls"], + "filters": { + "action": module.params["ftgd_wf_filters_action"], + "auth-usr-grp": module.params["ftgd_wf_filters_auth_usr_grp"], + "category": module.params["ftgd_wf_filters_category"], + "log": module.params["ftgd_wf_filters_log"], + "override-replacemsg": module.params["ftgd_wf_filters_override_replacemsg"], + "warn-duration": module.params["ftgd_wf_filters_warn_duration"], + "warning-duration-type": module.params["ftgd_wf_filters_warning_duration_type"], + "warning-prompt": module.params["ftgd_wf_filters_warning_prompt"], + }, + "quota": { + "category": module.params["ftgd_wf_quota_category"], + "duration": module.params["ftgd_wf_quota_duration"], + "override-replacemsg": module.params["ftgd_wf_quota_override_replacemsg"], + "type": module.params["ftgd_wf_quota_type"], + "unit": module.params["ftgd_wf_quota_unit"], + "value": module.params["ftgd_wf_quota_value"], + }, + }, + "override": { + "ovrd-cookie": module.params["override_ovrd_cookie"], + "ovrd-dur": module.params["override_ovrd_dur"], + "ovrd-dur-mode": module.params["override_ovrd_dur_mode"], + "ovrd-scope": module.params["override_ovrd_scope"], + "ovrd-user-group": module.params["override_ovrd_user_group"], + "profile": module.params["override_profile"], + "profile-attribute": module.params["override_profile_attribute"], + "profile-type": module.params["override_profile_type"], + }, + "url-extraction": { + "redirect-header": module.params["url_extraction_redirect_header"], + "redirect-no-content": module.params["url_extraction_redirect_no_content"], + "redirect-url": module.params["url_extraction_redirect_url"], + "server-fqdn": module.params["url_extraction_server_fqdn"], + "status": module.params["url_extraction_status"], + }, + "web": { + "blacklist": module.params["web_blacklist"], + "bword-table": module.params["web_bword_table"], + "bword-threshold": module.params["web_bword_threshold"], + "content-header-list": module.params["web_content_header_list"], + "keyword-match": module.params["web_keyword_match"], + "log-search": module.params["web_log_search"], + "safe-search": module.params["web_safe_search"], + "urlfilter-table": module.params["web_urlfilter_table"], + "whitelist": module.params["web_whitelist"], + "youtube-restrict": module.params["web_youtube_restrict"], + }, + "youtube-channel-filter": { + "channel-id": module.params["youtube_channel_filter_channel_id"], + "comment": module.params["youtube_channel_filter_comment"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['ftgd-wf', 'override', 'url-extraction', 'web', 'youtube-channel-filter'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + + results = fmgr_webfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_configuration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_configuration.py new file mode 100644 index 00000000..322f8e3e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_configuration.py @@ -0,0 +1,135 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ftd_configuration +short_description: Manages configuration on Cisco FTD devices over REST API +description: + - Manages configuration on Cisco FTD devices including creating, updating, removing configuration objects, + scheduling and staring jobs, deploying pending changes, etc. All operations are performed over REST API. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. Commonly, the operation starts with 'add', 'edit', 'get', 'upsert' + or 'delete' verbs, but can have an arbitrary name too. + required: true + type: str + data: + description: + - Key-value pairs that should be sent as body parameters in a REST API call + type: dict + query_params: + description: + - Key-value pairs that should be sent as query parameters in a REST API call. + type: dict + path_params: + description: + - Key-value pairs that should be sent as path parameters in a REST API call. + type: dict + register_as: + description: + - Specifies Ansible fact name that is used to register received response from the FTD device. + type: str + filters: + description: + - Key-value dict that represents equality filters. Every key is a property name and value is its desired value. + If multiple filters are present, they are combined with logical operator AND. + type: dict +''' + +EXAMPLES = """ +- name: Create a network object + community.network.ftd_configuration: + operation: "addNetworkObject" + data: + name: "Ansible-network-host" + description: "From Ansible with love" + subType: "HOST" + value: "192.168.2.0" + dnsResolution: "IPV4_AND_IPV6" + type: "networkobject" + isSystemDefined: false + register_as: "hostNetwork" + +- name: Delete the network object + community.network.ftd_configuration: + operation: "deleteNetworkObject" + path_params: + objId: "{{ hostNetwork['id'] }}" +""" + +RETURN = """ +response: + description: HTTP response returned from the API call. + returned: success + type: dict +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.configuration import BaseConfigurationResource, CheckModeException, \ + FtdInvalidOperationNameError +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import ValidationError +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import construct_ansible_facts, FtdConfigurationError, \ + FtdServerError, FtdUnexpectedResponse + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + data=dict(type='dict'), + query_params=dict(type='dict'), + path_params=dict(type='dict'), + register_as=dict(type='str'), + filters=dict(type='dict') + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + + connection = Connection(module._socket_path) + resource = BaseConfigurationResource(connection, module.check_mode) + op_name = params['operation'] + try: + resp = resource.execute_operation(op_name, params) + module.exit_json(changed=resource.config_changed, response=resp, + ansible_facts=construct_ansible_facts(resp, module.params)) + except FtdInvalidOperationNameError as e: + module.fail_json(msg='Invalid operation name provided: %s' % e.operation_name) + except FtdConfigurationError as e: + module.fail_json(msg='Failed to execute %s operation because of the configuration error: %s' % (op_name, e.msg)) + except FtdServerError as e: + module.fail_json(msg='Server returned an error trying to execute %s operation. Status code: %s. ' + 'Server response: %s' % (op_name, e.code, e.response)) + except FtdUnexpectedResponse as e: + module.fail_json(msg=e.args[0]) + except ValidationError as e: + module.fail_json(msg=e.args[0]) + except CheckModeException: + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_download.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_download.py new file mode 100644 index 00000000..78fbdbc0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_download.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ftd_file_download +short_description: Downloads files from Cisco FTD devices over HTTP(S) +description: + - Downloads files from Cisco FTD devices including pending changes, disk files, certificates, + troubleshoot reports, and backups. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. + - Only operations that return a file can be used in this module. + required: true + type: str + path_params: + description: + - Key-value pairs that should be sent as path parameters in a REST API call. + type: dict + destination: + description: + - Absolute path of where to download the file to. + - If destination is a directory, the module uses a filename from 'Content-Disposition' header specified by + the server. + required: true + type: path +''' + +EXAMPLES = """ +- name: Download pending changes + community.network.ftd_file_download: + operation: 'getdownload' + path_params: + objId: 'default' + destination: /tmp/ +""" + +RETURN = """ +msg: + description: The error message describing why the module failed. + returned: error + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import FtdServerError, HTTPMethod +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import OperationField, ValidationError, FILE_MODEL_NAME + + +def is_download_operation(op_spec): + return op_spec[OperationField.METHOD] == HTTPMethod.GET and op_spec[OperationField.MODEL_NAME] == FILE_MODEL_NAME + + +def validate_params(connection, op_name, path_params): + field_name = 'Invalid path_params provided' + try: + is_valid, validation_report = connection.validate_path_params(op_name, path_params) + if not is_valid: + raise ValidationError({ + field_name: validation_report + }) + except Exception as e: + raise ValidationError({ + field_name: str(e) + }) + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + path_params=dict(type='dict'), + destination=dict(type='path', required=True) + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + connection = Connection(module._socket_path) + + op_name = params['operation'] + op_spec = connection.get_operation_spec(op_name) + if op_spec is None: + module.fail_json(msg='Operation with specified name is not found: %s' % op_name) + if not is_download_operation(op_spec): + module.fail_json( + msg='Invalid download operation: %s. The operation must make GET request and return a file.' % + op_name) + + try: + path_params = params['path_params'] + validate_params(connection, op_name, path_params) + if module.check_mode: + module.exit_json(changed=False) + connection.download_file(op_spec[OperationField.URL], params['destination'], path_params) + module.exit_json(changed=False) + except FtdServerError as e: + module.fail_json(msg='Download request for %s operation failed. Status code: %s. ' + 'Server response: %s' % (op_name, e.code, e.response)) + except ValidationError as e: + module.fail_json(msg=e.args[0]) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_upload.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_upload.py new file mode 100644 index 00000000..c9b124f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_file_upload.py @@ -0,0 +1,103 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ftd_file_upload +short_description: Uploads files to Cisco FTD devices over HTTP(S) +description: + - Uploads files to Cisco FTD devices including disk files, backups, and upgrades. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. + - Only operations that upload file can be used in this module. + required: true + type: str + file_to_upload: + description: + - Absolute path to the file that should be uploaded. + required: true + type: path + register_as: + description: + - Specifies Ansible fact name that is used to register received response from the FTD device. + type: str +''' + +EXAMPLES = """ +- name: Upload disk file + community.network.ftd_file_upload: + operation: 'postuploaddiskfile' + file_to_upload: /tmp/test1.txt +""" + +RETURN = """ +msg: + description: The error message describing why the module failed. + returned: error + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import construct_ansible_facts, FtdServerError, HTTPMethod +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import OperationField + + +def is_upload_operation(op_spec): + return op_spec[OperationField.METHOD] == HTTPMethod.POST or 'UploadStatus' in op_spec[OperationField.MODEL_NAME] + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + file_to_upload=dict(type='path', required=True), + register_as=dict(type='str'), + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + connection = Connection(module._socket_path) + + op_spec = connection.get_operation_spec(params['operation']) + if op_spec is None: + module.fail_json(msg='Operation with specified name is not found: %s' % params['operation']) + if not is_upload_operation(op_spec): + module.fail_json( + msg='Invalid upload operation: %s. The operation must make POST request and return UploadStatus model.' % + params['operation']) + + try: + if module.check_mode: + module.exit_json() + resp = connection.upload_file(params['file_to_upload'], op_spec[OperationField.URL]) + module.exit_json(changed=True, response=resp, ansible_facts=construct_ansible_facts(resp, module.params)) + except FtdServerError as e: + module.fail_json(msg='Upload request for %s operation failed. Status code: %s. ' + 'Server response: %s' % (params['operation'], e.code, e.response)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_install.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_install.py new file mode 100644 index 00000000..42b4997a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ftd_install.py @@ -0,0 +1,290 @@ +#!/usr/bin/python + +# Copyright (c) 2019 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ftd_install +short_description: Installs FTD pkg image on the firewall +description: + - Provisioning module for FTD devices that installs ROMMON image (if needed) and + FTD pkg image on the firewall. + - Can be used with `httpapi` and `local` connection types. The `httpapi` is preferred, + the `local` connection should be used only when the device cannot be accessed via + REST API. +requirements: [ "python >= 3.5", "firepower-kickstart" ] +notes: + - Requires `firepower-kickstart` library that should be installed separately and requires Python >= 3.5. + - On localhost, Ansible can be still run with Python >= 2.7, but the interpreter for this particular module must be + Python >= 3.5. + - Python interpreter for the module can overwritten in `ansible_python_interpreter` variable. +author: "Cisco Systems, Inc. (@annikulin)" +options: + device_hostname: + description: + - Hostname of the device as appears in the prompt (e.g., 'firepower-5516'). + required: true + type: str + device_username: + description: + - Username to login on the device. + - Defaulted to 'admin' if not specified. + required: false + type: str + default: admin + device_password: + description: + - Password to login on the device. + required: true + type: str + device_sudo_password: + description: + - Root password for the device. If not specified, `device_password` is used. + required: false + type: str + device_new_password: + description: + - New device password to set after image installation. + - If not specified, current password from `device_password` property is reused. + - Not applicable for ASA5500-X series devices. + required: false + type: str + device_ip: + description: + - Device IP address of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_gateway: + description: + - Device gateway of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_netmask: + description: + - Device netmask of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_model: + description: + - Platform model of the device (e.g., 'Cisco ASA5506-X Threat Defense'). + - If not specified and connection is 'httpapi`, the module tries to fetch the device model via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + choices: + - Cisco ASA5506-X Threat Defense + - Cisco ASA5508-X Threat Defense + - Cisco ASA5516-X Threat Defense + - Cisco Firepower 2110 Threat Defense + - Cisco Firepower 2120 Threat Defense + - Cisco Firepower 2130 Threat Defense + - Cisco Firepower 2140 Threat Defense + dns_server: + description: + - DNS IP address of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + console_ip: + description: + - IP address of a terminal server. + - Used to set up an SSH connection with device's console port through the terminal server. + required: true + type: str + console_port: + description: + - Device's port on a terminal server. + required: true + type: str + console_username: + description: + - Username to login on a terminal server. + required: true + type: str + console_password: + description: + - Password to login on a terminal server. + required: true + type: str + rommon_file_location: + description: + - Path to the boot (ROMMON) image on TFTP server. + - Only TFTP is supported. + required: true + type: str + image_file_location: + description: + - Path to the FTD pkg image on the server to be downloaded. + - FTP, SCP, SFTP, TFTP, or HTTP protocols are usually supported, but may depend on the device model. + required: true + type: str + image_version: + description: + - Version of FTD image to be installed. + - Helps to compare target and current FTD versions to prevent unnecessary reinstalls. + required: true + type: str + force_install: + description: + - Forces the FTD image to be installed even when the same version is already installed on the firewall. + - By default, the module stops execution when the target version is installed in the device. + required: false + type: bool + default: false + search_domains: + description: + - Search domains delimited by comma. + - Defaulted to 'cisco.com' if not specified. + required: false + type: str + default: cisco.com +''' + +EXAMPLES = """ + - name: Install image v6.3.0 on FTD 5516 + community.network.ftd_install: + device_hostname: firepower + device_password: pass + device_ip: 192.168.0.1 + device_netmask: 255.255.255.0 + device_gateway: 192.168.0.254 + dns_server: 8.8.8.8 + + console_ip: 10.89.0.0 + console_port: 2004 + console_username: console_user + console_password: console_pass + + rommon_file_location: 'tftp://10.89.0.11/installers/ftd-boot-9.10.1.3.lfbff' + image_file_location: 'https://10.89.0.11/installers/ftd-6.3.0-83.pkg' + image_version: 6.3.0-83 +""" + +RETURN = """ +msg: + description: The message saying whether the image was installed or explaining why the installation failed. + returned: always + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.six import iteritems + +from ansible_collections.community.network.plugins.module_utils.network.ftd.configuration import BaseConfigurationResource, ParamName +from ansible_collections.community.network.plugins.module_utils.network.ftd.device import assert_kick_is_installed, FtdPlatformFactory, FtdModel +from ansible_collections.community.network.plugins.module_utils.network.ftd.operation import FtdOperations, get_system_info + +REQUIRED_PARAMS_FOR_LOCAL_CONNECTION = ['device_ip', 'device_netmask', 'device_gateway', 'device_model', 'dns_server'] + + +def main(): + fields = dict( + device_hostname=dict(type='str', required=True), + device_username=dict(type='str', required=False, default='admin'), + device_password=dict(type='str', required=True, no_log=True), + device_sudo_password=dict(type='str', required=False, no_log=True), + device_new_password=dict(type='str', required=False, no_log=True), + device_ip=dict(type='str', required=False), + device_netmask=dict(type='str', required=False), + device_gateway=dict(type='str', required=False), + device_model=dict(type='str', required=False, choices=FtdModel.supported_models()), + dns_server=dict(type='str', required=False), + search_domains=dict(type='str', required=False, default='cisco.com'), + + console_ip=dict(type='str', required=True), + console_port=dict(type='str', required=True), + console_username=dict(type='str', required=True), + console_password=dict(type='str', required=True, no_log=True), + + rommon_file_location=dict(type='str', required=True), + image_file_location=dict(type='str', required=True), + image_version=dict(type='str', required=True), + force_install=dict(type='bool', required=False, default=False) + ) + module = AnsibleModule(argument_spec=fields) + assert_kick_is_installed(module) + + use_local_connection = module._socket_path is None + if use_local_connection: + check_required_params_for_local_connection(module, module.params) + platform_model = module.params['device_model'] + check_that_model_is_supported(module, platform_model) + else: + connection = Connection(module._socket_path) + resource = BaseConfigurationResource(connection, module.check_mode) + system_info = get_system_info(resource) + + platform_model = module.params['device_model'] or system_info['platformModel'] + check_that_model_is_supported(module, platform_model) + check_that_update_is_needed(module, system_info) + check_management_and_dns_params(resource, module.params) + + ftd_platform = FtdPlatformFactory.create(platform_model, module.params) + ftd_platform.install_ftd_image(module.params) + + module.exit_json(changed=True, + msg='Successfully installed FTD image %s on the firewall device.' % module.params["image_version"]) + + +def check_required_params_for_local_connection(module, params): + missing_params = [k for k, v in iteritems(params) if k in REQUIRED_PARAMS_FOR_LOCAL_CONNECTION and v is None] + if missing_params: + message = "The following parameters are mandatory when the module is used with 'local' connection: %s." % \ + ', '.join(sorted(missing_params)) + module.fail_json(msg=message) + + +def check_that_model_is_supported(module, platform_model): + if platform_model not in FtdModel.supported_models(): + module.fail_json(msg="Platform model '%s' is not supported by this module." % platform_model) + + +def check_that_update_is_needed(module, system_info): + target_ftd_version = module.params["image_version"] + if not module.params["force_install"] and target_ftd_version == system_info['softwareVersion']: + module.exit_json(changed=False, msg="FTD already has %s version of software installed." % target_ftd_version) + + +def check_management_and_dns_params(resource, params): + if not all([params['device_ip'], params['device_netmask'], params['device_gateway']]): + management_ip = resource.execute_operation(FtdOperations.GET_MANAGEMENT_IP_LIST, {})['items'][0] + params['device_ip'] = params['device_ip'] or management_ip['ipv4Address'] + params['device_netmask'] = params['device_netmask'] or management_ip['ipv4NetMask'] + params['device_gateway'] = params['device_gateway'] or management_ip['ipv4Gateway'] + if not params['dns_server']: + dns_setting = resource.execute_operation(FtdOperations.GET_DNS_SETTING_LIST, {})['items'][0] + dns_server_group_id = dns_setting['dnsServerGroup']['id'] + dns_server_group = resource.execute_operation(FtdOperations.GET_DNS_SERVER_GROUP, + {ParamName.PATH_PARAMS: {'objId': dns_server_group_id}}) + params['dns_server'] = dns_server_group['dnsServers'][0]['ipAddress'] + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_start_workflow.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_start_workflow.py new file mode 100644 index 00000000..c8c2ada8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_start_workflow.py @@ -0,0 +1,179 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Itential +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +This module provides the ability to start a workflow from Itential Automation Platform +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: iap_start_workflow +author: "Itential (@cma0) " +short_description: Start a workflow in the Itential Automation Platform +description: + - This will start a specified workflow in the Itential Automation Platform with given arguments. +options: + iap_port: + description: + - Provide the port number for the Itential Automation Platform + required: true + type: str + default: null + + iap_fqdn: + description: + - Provide the fqdn for the Itential Automation Platform + required: true + type: str + default: null + + token_key: + description: + - Token key generated by iap_token module for the Itential Automation Platform + required: true + type: str + default: null + + workflow_name: + description: + - Provide the workflow name + required: true + type: str + default: null + + description: + description: + - Provide the description for the workflow + required: true + type: str + default: null + + variables: + description: + - Provide the values to the job variables + required: true + type: dict + default: null + + https: + description: + - Use HTTPS to connect + - By default using http + type: bool + default: False + + validate_certs: + description: + - If C(no), SSL certificates for the target url will not be validated. This should only be used + on personally controlled sites using self-signed certificates. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Start a workflow in the Itential Automation Platform + community.network.iap_start_workflow: + iap_port: 3000 + iap_fqdn: localhost + token_key: "DFSFSFHFGFGF[DSFSFAADAFASD%3D" + workflow_name: "RouterUpgradeWorkflow" + description: "OS-Router-Upgrade" + variables: {"deviceName":"ASR9K"} + register: result + +- ansible.builtin.debug: var=result +''' + +RETURN = ''' +response: + description: The result contains the response from the call + type: dict + returned: always +msg: + description: The msg will contain the error code or status of the workflow + type: str + returned: always +''' + +# Ansible imports +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url + +# Standard library imports +import json + + +def start_workflow(module): + """ + :param module: + :return: response and msg + """ + # By default this will be http. + # By default when using https, self signed certificate is used + # If https needs to pass certificate then use validate_certs as true + if module.params['https']: + transport_protocol = 'https' + else: + transport_protocol = 'http' + + application_token = str(module.params['token_key']) + url = str(transport_protocol) + "://" + str(module.params['iap_fqdn']) + ":" + str(module.params[ + 'iap_port']) + "/workflow_engine/startJobWithOptions/" \ + + str(module.params['workflow_name']) + "?token=" + str(application_token) + options = { + "variables": module.params['variables'], + "description": str(module.params['description']) + } + + payload = { + "workflow": module.params['workflow_name'], + "options": options + } + + json_body = module.jsonify(payload) + headers = dict() + headers['Content-Type'] = 'application/json' + + # Using fetch url instead of requests + response, info = fetch_url(module, url, data=json_body, headers=headers) + response_code = str(info['status']) + if info['status'] not in [200, 201]: + module.fail_json(msg="Failed to connect to Itential Automation Platform. Response code is " + response_code) + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + jsonResponse = json.loads(response.read().decode('utf-8')) + module.exit_json(changed=True, msg={"workflow_name": module.params['workflow_name'], "status": "started"}, + response=jsonResponse) + + +def main(): + """ + :return: response and msg + """ + # define the available arguments/parameters that a user can pass to + # the module + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=dict( + iap_port=dict(type='str', required=True), + iap_fqdn=dict(type='str', required=True), + token_key=dict(type='str', required=True, no_log=True), + workflow_name=dict(type='str', required=True), + description=dict(type='str', required=True), + variables=dict(type='dict', required=False), + https=(dict(type='bool', default=False)), + validate_certs=dict(type='bool', default=False) + ) + ) + start_workflow(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_token.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_token.py new file mode 100644 index 00000000..72d69df6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/iap_token.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Itential +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +This module provides the token for Itential Automation Platform +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: iap_token +author: "Itential (@cma0) " +short_description: Get token for the Itential Automation Platform +description: + - Checks the connection to IAP and retrieves a login token. +options: + iap_port: + description: + - Provide the port number for the Itential Automation Platform + required: true + default: null + + iap_fqdn: + description: + - Provide the fqdn or ip-address for the Itential Automation Platform + required: true + default: null + + username: + description: + - Provide the username for the Itential Automation Platform + required: true + default: null + + password: + description: + - Provide the password for the Itential Automation Platform + required: true + default: null + + https: + description: + - Use HTTPS to connect + - By default using http + type: bool + default: False + + validate_certs: + description: + - If C(no), SSL certificates for the target url will not be validated. This should only be used + on personally controlled sites using self-signed certificates. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Get token for the Itential Automation Platform + community.network.iap_token: + iap_port: 3000 + iap_fqdn: localhost + username: myusername + password: mypass + register: result + +- ansible.builtin.debug: var=result.token +''' + +RETURN = ''' +token: + description: The token acquired from the Itential Automation Platform + type: str + returned: always +''' +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url + + +def get_token(module): + """ + :param module: + :return: token + """ + # defaulting the value for transport_protocol to be : http + transport_protocol = 'http' + if module.params['https'] or module.params['validate_certs'] is True: + transport_protocol = 'https' + + url = transport_protocol + "://" + module.params['iap_fqdn'] + ":" + module.params['iap_port'] + "/login" + username = module.params['username'] + password = module.params['password'] + + login = { + "user": { + "username": username, + "password": password + } + } + json_body = module.jsonify(login) + headers = {} + headers['Content-Type'] = 'application/json' + + # Using fetch url instead of requests + response, info = fetch_url(module, url, data=json_body, headers=headers) + response_code = str(info['status']) + if info['status'] not in [200, 201]: + module.fail_json(msg="Failed to connect to Itential Automation Platform" + response_code) + response = response.read() + module.exit_json(changed=True, token=response) + + +def main(): + """ + :return: token + """ + # define the available arguments/parameters that a user can pass to + # the module + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=dict( + iap_port=dict(type='int', required=True), + iap_fqdn=dict(type='str', required=True), + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + https=(dict(type='bool', default=False)), + validate_certs=dict(type='bool', default=False) + ) + ) + get_token(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_banner.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_banner.py new file mode 100644 index 00000000..f3ba4d0c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_banner.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_banner +author: "Ruckus Wireless (@Commscope)" +short_description: Manage multiline banners on Ruckus ICX 7000 series switches +description: + - This will configure both login and motd banners on remote + ruckus ICX 7000 series switches. It allows playbooks to add or remove + banner text from the active running configuration. +notes: + - Tested against ICX 10.1 +options: + banner: + description: + - Specifies which banner should be configured on the remote device. + type: str + required: true + choices: ['motd', 'exec', 'incoming'] + text: + description: + - The banner text that should be + present in the remote device running configuration. + This argument accepts a multiline string, with no empty lines. + type: str + state: + description: + - Specifies whether or not the configuration is + present in the current devices active running configuration. + type: str + default: present + choices: ['present', 'absent'] + enterkey: + description: + - Specifies whether or not the motd configuration should accept + the require-enter-key + - Default is false. + type: bool + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, + by specifying it as module parameter. + type: bool + default: yes + +''' + +EXAMPLES = """ +- name: Configure the motd banner + community.network.icx_banner: + banner: motd + text: | + this is my motd banner + that contains a multiline + string + state: present + +- name: Remove the motd banner + community.network.icx_banner: + banner: motd + state: absent + +- name: Configure require-enter-key for motd + community.network.icx_banner: + banner: motd + enterkey: True + +- name: Remove require-enter-key for motd + community.network.icx_banner: + banner: motd + enterkey: False +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner motd + - this is my motd banner + - that contains a multiline + - string +""" + +import re +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import exec_command +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + want, have = updates + + if module.params['banner'] != 'motd' and module.params['enterkey']: + module.fail_json(msg=module.params['banner'] + " banner can have text only, got enterkey") + + if state == 'absent': + if 'text' in have.keys() and have['text']: + commands.append('no banner %s' % module.params['banner']) + if(module.params['enterkey'] is False): + commands.append('no banner %s require-enter-key' % module.params['banner']) + + elif state == 'present': + if module.params['text'] is None and module.params['enterkey'] is None: + module.fail_json(msg=module.params['banner'] + " one of the following is required: text, enterkey:only if motd") + + if module.params["banner"] == "motd" and want['enterkey'] != have['enterkey']: + if(module.params['enterkey']): + commands.append('banner %s require-enter-key' % module.params['banner']) + + if want['text'] and (want['text'] != have.get('text')): + module.params["enterkey"] = None + banner_cmd = 'banner %s' % module.params['banner'] + banner_cmd += ' $\n' + banner_cmd += module.params['text'].strip() + banner_cmd += '\n$' + commands.append(banner_cmd) + return commands + + +def map_config_to_obj(module): + compare = module.params.get('check_running_config') + obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False} + exec_command(module, 'skip') + output_text = '' + output_re = '' + out = get_config(module, flags=['| begin banner %s' + % module.params['banner']], compare=module.params['check_running_config']) + if out: + try: + output_re = re.search(r'banner %s( require-enter-key)' % module.params['banner'], out, re.S).group(0) + obj['enterkey'] = True + except BaseException: + pass + try: + output_text = re.search(r'banner %s (\$([^\$])+\$){1}' % module.params['banner'], out, re.S).group(1).strip('$\n') + except BaseException: + pass + + else: + output_text = None + if output_text: + obj['text'] = output_text + obj['state'] = 'present' + if module.params['check_running_config'] is False: + obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False, 'text': 'JUNK'} + return obj + + +def map_params_to_obj(module): + text = module.params['text'] + if text: + text = str(text).strip() + + return { + 'banner': module.params['banner'], + 'text': text, + 'state': module.params['state'], + 'enterkey': module.params['enterkey'] + } + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + banner=dict(required=True, choices=['motd', 'exec', 'incoming']), + text=dict(), + enterkey=dict(type='bool'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + required_one_of = [['text', 'enterkey', 'state']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + supports_check_mode=True) + + warnings = list() + results = {'changed': False} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + results['commands'] = commands + + if commands: + if not module.check_mode: + response = load_config(module, commands) + + results['changed'] = True + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_command.py new file mode 100644 index 00000000..85c85fc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_command.py @@ -0,0 +1,227 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: icx_command +author: "Ruckus Wireless (@Commscope)" +short_description: Run arbitrary commands on remote Ruckus ICX 7000 series switches +description: + - Sends arbitrary commands to an ICX node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +notes: + - Tested against ICX 10.1 +options: + commands: + description: + - List of commands to send to the remote ICX device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. If a command sent to the + device requires answering a prompt, checkall and newline if + multiple prompts, it is possible to pass + a dict containing I(command), I(answer), I(prompt), I(check_all) + and I(newline).Common answers are 'y' or "\\r" (carriage return, + must be double quotes). See examples. + type: list + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + type: str + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of times a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + type: int + default: 1 +""" + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.icx_command: + commands: show version + + - name: Run show version and check to see if output contains ICX + community.network.icx_command: + commands: show version + wait_for: result[0] contains ICX + + - name: Run multiple commands on remote nodes + community.network.icx_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.icx_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains ICX + - result[1] contains GigabitEthernet1/1/1 + - name: Run commands that require answering a prompt + community.network.icx_command: + commands: + - command: 'service password-encryption sha1' + prompt: 'Warning: Moving to higher password-encryption type,.*' + answer: 'y' + - name: Run commands that require answering multiple prompt + community.network.icx_command: + commands: + - command: 'username qqq password qqq' + prompt: + - 'User already exists. Do you want to modify:.*' + - 'To modify or remove user, enter current password:' + answer: + - 'y' + - 'qqq\\\r' + check_all: True + newline: False +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" + + +import re +import time +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, to_lines +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict(), + check_all=dict(type='bool', default='False'), + newline=dict(type='bool', default='True') + ), module) + commands = command(module.params['commands']) + for item in list(commands): + if module.check_mode: + if not item['command'].startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not executing configure terminal') + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + run_commands(module, ['skip']) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + + responses = run_commands(module, commands) + + for item in list(conditionals): + + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_config.py new file mode 100644 index 00000000..9486900f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_config.py @@ -0,0 +1,479 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_config +author: "Ruckus Wireless (@Commscope)" +short_description: Manage configuration sections of Ruckus ICX 7000 series switches +description: + - Ruckus ICX configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with ICX configuration sections in + a deterministic way. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + type: list + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + type: list + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + type: str + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + type: list + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + type: list + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + type: str + choices: ['line', 'strict', 'exact', 'none'] + default: line + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + type: str + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the ICX device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + type: str + default: "@" + backup: + description: + - 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 or role root directory, if + playbook is part of an ansible role. If the directory does not exist, + it is created. + type: bool + default: 'no' + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + type: str + aliases: ['config'] + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + start-up configuration and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the start-up configuration if it has changed since + the last save to configuration. If the argument is set to + I(never), the running-config will never be copied to the + configuration. If the argument is set to I(changed), then the running-config + will only be copied to the configuration if the task has made a change. + type: str + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the configuration. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + type: str + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + type: list + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + type: str +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.icx_config: + lines: hostname {{ inventory_hostname }} + +- name: Configure interface settings + community.network.icx_config: + lines: + - port-name test string + - ip address 172.31.1.1 255.255.255.0 + parents: interface ethernet 1/1/2 + +- name: Configure ip helpers on multiple interfaces + community.network.icx_config: + lines: + - ip helper-address 172.26.1.10 + - ip helper-address 172.26.3.8 + parents: "{{ item }}" + with_items: + - interface ethernet 1/1/2 + - interface ethernet 1/1/3 + +- name: Load new acl into device + community.network.icx_config: + lines: + - permit ip host 192.0.2.1 any log + - permit ip host 192.0.2.2 any log + - permit ip host 192.0.2.3 any log + - permit ip host 192.0.2.4 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.icx_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the configuration against the running-config + community.network.icx_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: For idempotency, use full-form commands + community.network.icx_config: + lines: + # - en + - enable + # parents: int eth1/0/11 + parents: interface ethernet 1/1/2 + +# Set boot image based on comparison to a group_var (version) and the version +# that is returned from the `icx_facts` module +- name: SETTING BOOT IMAGE + community.network.icx_config: + lines: + - no boot system + - boot system flash bootflash:{{new_image}} + host: "{{ inventory_hostname }}" + when: ansible_net_version != version + +- name: Render template onto an ICX device + community.network.icx_config: + backup: yes + src: "{{ lookup('file', 'config.j2') }}" +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/icx_config.2016-07-16@22:28:34 +""" + +import json +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands, get_config +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_defaults_flag, get_connection +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import check_args as icx_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def check_args(module, warnings): + icx_check_args(module, warnings) + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def edit_config_or_macro(connection, commands): + if "macro name" in commands[0]: + connection.edit_macro(candidate=commands) + else: + if commands[0] != '': + connection.edit_config(candidate=commands) + + +def get_candidate_config(module): + candidate = '' + if module.params['src']: + candidate = module.params['src'] + + elif module.params['lines']: + candidate_obj = NetworkConfig(indent=1) + parents = module.params['parents'] or list() + candidate_obj.add(module.params['lines'], parents=parents) + candidate = dumps(candidate_obj, 'raw') + + return candidate + + +def get_running_config(module, current_config=None, flags=None): + running = module.params['running_config'] + if not running: + if not module.params['defaults'] and current_config: + running = current_config + else: + running = get_config(module, flags=flags) + + return running + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'write memory') + else: + module.warn('Skipping command `copy running-config start-up configuration` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + src=dict(), + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + run_commands(module, 'skip') + diff_ignore_lines = module.params['diff_ignore_lines'] + config = None + contents = None + flags = None if module.params['defaults'] else [] + connection = get_connection(module) + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate_config(module) + running = get_running_config(module, contents, flags=flags) + try: + response = connection.get_diff(candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, path=path, + diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response['config_diff'] + banner_diff = response['banner_diff'] + + if config_diff or banner_diff: + commands = config_diff.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + result['banners'] = banner_diff + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + edit_config_or_macro(connection, commands) + if banner_diff: + connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter']) + + result['changed'] = True + + running_config = module.params['running_config'] + startup_config = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show configuration']) + + running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show configuration') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_copy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_copy.py new file mode 100644 index 00000000..ce67dbb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_copy.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_copy +author: "Ruckus Wireless (@Commscope)" +short_description: Transfer files from or to remote Ruckus ICX 7000 series switches +description: + - This module transfers files from or to remote devices running ICX. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + upload: + description: + - Name of the resource to be uploaded. Mutually exclusive with download. + type: str + choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary'] + download: + description: + - Name of the resource to be downloaded. Mutually exclusive with upload. + type: str + choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary', 'bootrom', 'fips-primary-sig', 'fips-secondary-sig', 'fips-bootrom-sig'] + protocol: + description: + - Data transfer protocol to be used + type: str + choices: ['scp', 'https'] + required: true + remote_server: + description: + - IP address of the remote server + type: str + required: true + remote_port: + description: + - The port number of the remote host. Default values will be selected based on protocol type. + Default scp:22, http:443 + type: str + remote_filename: + description: + - The name or path of the remote file/resource to be uploaded or downloaded. + type: str + required: true + remote_user: + description: + - remote username to be used for scp login. + type: str + remote_pass: + description: + - remote password to be used for scp login. + type: str + public_key: + description: + - public key type to be used to login to scp server + type: str + choices: ['rsa', 'dsa'] + +''' + +EXAMPLES = """ +- name: Upload running-config to the remote scp server + community.network.icx_copy: + upload: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download running-config from the remote scp server + community.network.icx_copy: + download: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download running-config from the remote scp server using rsa public key + community.network.icx_copy: + download: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + public_key: rsa + +- name: Upload startup-config to the remote https server + community.network.icx_copy: + upload: startup-config + protocol: https + remote_server: 172.16.10.49 + remote_filename: config/running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Upload startup-config to the remote https server + community.network.icx_copy: + upload: startup-config + protocol: https + remote_server: 172.16.10.49 + remote_filename: config/running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the flash from remote scp ipv6 server + community.network.icx_copy: + download: startup-config + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the secondary flash from remote scp ipv6 server + community.network.icx_copy: + Download: flash_secondary + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the secondary flash from remote scp ipv6 server on port 5000 + community.network.icx_copy: + Download: flash_secondary + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_port: 5000 + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the primary flash from remote https ipv6 server + community.network.icx_copy: + Download: flash_primary + protocol: https + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: images/img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the primary flash from remote https ipv6 server on port 8080 + community.network.icx_copy: + Download: flash_primary + protocol: https + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_port: 8080 + remote_filename: images/img.bin + remote_user: user1 + remote_pass: pass123 +""" + +RETURN = """ +changed: + description: true when downloaded any configuration or flash. false otherwise. + returned: always + type: bool +""" + + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError, exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import exec_scp, run_commands + + +def map_params_to_obj(module): + command = dict() + + if(module.params['protocol'] == 'scp'): + if(module.params['upload'] is not None): + module.params["upload"] = module.params["upload"].replace("flash_primary", "primary") + module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary") + if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'): + command["command"] = "copy %s scp %s%s %s%s" % (module.params['upload'], + module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "") + else: + command["command"] = "copy flash scp %s%s %s%s %s" % (module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "", + module.params["upload"]) + command["scp_user"] = module.params["remote_user"] + command["scp_pass"] = module.params["remote_pass"] + if(module.params['download'] is not None): + module.params["download"] = module.params["download"].replace("flash_primary", "primary") + module.params["download"] = module.params["download"].replace("flash_secondary", "secondary") + if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'): + command["command"] = "copy scp %s %s%s %s%s" % (module.params['download'], + module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "") + else: + command["command"] = "copy scp flash %s%s %s%s %s" % (module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "", + module.params["download"]) + command["scp_user"] = module.params["remote_user"] + command["scp_pass"] = module.params["remote_pass"] + if(module.params['protocol'] == 'https'): + if(module.params['upload'] is not None): + module.params["upload"] = module.params["upload"].replace("flash_primary", "primary") + module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary") + if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'): + command["command"] = "copy %s https %s %s%s" % (module.params['upload'], + module.params["remote_server"], + module.params["remote_filename"], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + else: + command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"], + module.params["remote_filename"], + module.params['upload'], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + if(module.params['download'] is not None): + module.params["download"] = module.params["download"].replace("flash_primary", "primary") + module.params["download"] = module.params["download"].replace("flash_secondary", "secondary") + if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'): + command["command"] = "copy https %s %s %s%s" % (module.params['download'], + module.params["remote_server"], + module.params["remote_filename"], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + else: + command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"], + module.params["remote_filename"], + module.params['download'], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + return command + + +def checkValidations(module): + validation = dict( + scp=dict( + upload=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary'], + download=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary', + 'bootrom', + 'fips-primary-sig', + 'fips-secondary-sig', + 'fips-bootrom-sig']), + https=dict( + upload=[ + 'running-config', + 'startup-config'], + download=[ + 'flash_primary', + 'flash_secondary', + 'startup-config'])) + protocol = module.params['protocol'] + upload = module.params['upload'] + download = module.params['download'] + + if(protocol == 'scp' and module.params['remote_user'] is None): + module.fail_json(msg="While using scp remote_user argument is required") + if(upload is None and download is None): + module.fail_json(msg="Upload or download params are required.") + if(upload is not None and download is not None): + module.fail_json(msg="Only upload or download can be used at a time.") + if(upload): + if(not (upload in validation.get(protocol).get("upload"))): + module.fail_json(msg="Specified resource '" + upload + "' can't be uploaded to '" + protocol + "'") + if(download): + if(not (download in validation.get(protocol).get("download"))): + module.fail_json(msg="Specified resource '" + download + "' can't be downloaded from '" + protocol + "'") + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + upload=dict( + type='str', + required=False, + choices=[ + 'running-config', + 'flash_primary', + 'flash_secondary', + 'startup-config']), + download=dict( + type='str', + required=False, + choices=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary', + 'bootrom', + 'fips-primary-sig', + 'fips-secondary-sig', + 'fips-bootrom-sig']), + protocol=dict( + type='str', + required=True, + choices=[ + 'https', + 'scp']), + remote_server=dict( + type='str', + required=True), + remote_port=dict( + type='str', + required=False), + remote_filename=dict( + type='str', + required=True), + remote_user=dict( + type='str', + required=False), + remote_pass=dict( + type='str', + required=False, + no_log=True), + public_key=dict( + type='str', + required=False, + choices=[ + 'rsa', + 'dsa'])) + mutually_exclusive = [['upload', 'download']] + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, mutually_exclusive=mutually_exclusive) + + checkValidations(module) + warnings = list() + result = {'changed': False, 'warnings': warnings} + exec_command(module, 'skip') + + response = '' + try: + command = map_params_to_obj(module) + result['commands'] = [command["command"]] + + if(module.params['protocol'] == 'scp'): + response = exec_scp(module, command) + else: + response = run_commands(module, command) + if('Response Code: 404' in response): + module.fail_json(msg=response) + else: + result['response'] = "in progress..." + if(module.params["download"] is not None): + result['changed'] = True + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_facts.py new file mode 100644 index 00000000..0831f117 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_facts.py @@ -0,0 +1,544 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_facts +author: "Ruckus Wireless (@Commscope)" +short_description: Collect facts from remote Ruckus ICX 7000 series switches +description: + - Collects a base set of device facts from a remote device that + is running ICX. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + type: list + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.icx_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.icx_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.icx_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str +ansible_net_stacked_models: + description: The model names of each device in the stack + returned: when multiple devices are configured in a stack + type: list +ansible_net_stacked_serialnums: + description: The serial numbers of each device in the stack + returned: when multiple devices are configured in a stack + type: list + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_filesystems_info: + description: A hash of all file systems containing info about each file system (e.g. free and total space) + returned: when hardware is configured + type: dict +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + + +import re +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show running-config | include hostname'] + + def populate(self): + super(Default, self).run(['skip']) + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + self.facts['hostname'] = self.parse_hostname(self.responses[1]) + self.parse_stacks(data) + + def parse_version(self, data): + match = re.search(r'SW: Version ([0-9]+.[0-9]+.[0-9a-zA-Z]+)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'^hostname (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'HW: (\S+ \S+)', data, re.M) + if match: + return match.group(1) + + def parse_image(self, data): + match = re.search(r'\([0-9]+ bytes\) from \S+ (\S+)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial #:(\S+)', data) + if match: + return match.group(1) + + def parse_stacks(self, data): + match = re.findall(r'UNIT [1-9]+: SL [1-9]+: (\S+)', data, re.M) + if match: + self.facts['stacked_models'] = match + + match = re.findall(r'^System [Ss]erial [Nn]umber\s+: (\S+)', data, re.M) + if match: + self.facts['stacked_serialnums'] = match + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show memory', + 'show flash' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + self.facts['filesystems_info'] = self.parse_filesystems_info(self.responses[1]) + + if data: + if 'Invalid input detected' in data: + warnings.append('Unable to gather memory statistics') + else: + match = re.search(r'Dynamic memory: ([0-9]+) bytes total, ([0-9]+) bytes free, ([0-9]+%) used', data) + if match: + self.facts['memtotal_mb'] = int(match.group(1)) / 1024 + self.facts['memfree_mb'] = int(match.group(2)) / 1024 + + def parse_filesystems(self, data): + return "flash" + + def parse_filesystems_info(self, data): + facts = dict() + fs = '' + for line in data.split('\n'): + match = re.match(r'^(Stack unit \S+):', line) + if match: + fs = match.group(1) + facts[fs] = dict() + continue + match = re.match(r'\W+NAND Type: Micron NAND (\S+)', line) + if match: + facts[fs]['spacetotal'] = match.group(1) + match = re.match(r'\W+Code Flash Free Space = (\S+)', line) + if match: + facts[fs]['spacefree'] = int(int(match.group(1)) / 1024) + facts[fs]['spacefree'] = str(facts[fs]['spacefree']) + "Kb" + return {"flash": facts} + + +class Config(FactsBase): + + COMMANDS = ['skip', 'show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[1] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'skip', + 'show interfaces', + 'show running-config', + 'show lldp', + 'show media' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + data = self.responses[1] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_ipv4_interfaces(data) + + data = self.responses[2] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[3] + lldp_errs = ['Invalid input', 'LLDP is not enabled'] + + if data and not any(err in data for err in lldp_errs): + neighbors = self.run(['show lldp neighbors detail']) + if neighbors: + self.facts['neighbors'] = self.parse_neighbors(neighbors[0]) + + data = self.responses[4] + self.populate_mediatype(data) + + interfaceList = {} + for iface in self.facts['interfaces']: + if 'type' in self.facts['interfaces'][iface]: + newName = self.facts['interfaces'][iface]['type'] + iface + else: + newName = iface + interfaceList[newName] = self.facts['interfaces'][iface] + self.facts['interfaces'] = interfaceList + + def populate_mediatype(self, data): + lines = data.split("\n") + for line in lines: + match = re.match(r'Port (\S+):\W+Type\W+:\W+(.*)', line) + if match: + self.facts['interfaces'][match.group(1)]["mediatype"] = match.group(2) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = dict() + primary_address = addresses = [] + primary_address = re.findall(r'Internet address is (\S+/\S+), .*$', value, re.M) + addresses = re.findall(r'Secondary address (.+)$', value, re.M) + if len(primary_address) == 0: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'] = ipv4 + + def populate_ipv6_interfaces(self, data): + parts = data.split("\n") + for line in parts: + match = re.match(r'\W*interface \S+ (\S+)', line) + if match: + key = match.group(1) + try: + self.facts['interfaces'][key]['ipv6'] = list() + except KeyError: + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['ipv6'] = list() + self.facts['interfaces'][key]['ipv6'] = {} + continue + match = re.match(r'\W+ipv6 address (\S+)/(\S+)', line) + if match: + self.add_ip_address(match.group(1), "ipv6") + self.facts['interfaces'][key]['ipv6']["address"] = match.group(1) + self.facts['interfaces'][key]['ipv6']["subnet"] = match.group(2) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + for entry in neighbors.split('------------------------------------------------'): + if entry == '': + continue + intf = self.parse_lldp_intf(entry) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = self.parse_lldp_host(entry) + fact['port'] = self.parse_lldp_port(entry) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'\S+Ethernet(\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Port name is ([ \S]+)', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is \S+, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'Configured speed (\S+), actual (\S+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'configured duplex (\S+), actual (\S+)', data, re.M) + if match: + return match.group(2) + + def parse_mediatype(self, data): + match = re.search(r'media type is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lldp_intf(self, data): + match = re.search(r'^Local Intf: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_host(self, data): + match = re.search(r'System Name: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_port(self, data): + match = re.search(r'Port id: (.+)$', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +warnings = list() + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_interface.py new file mode 100644 index 00000000..df18a5ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_interface.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_interface +author: "Ruckus Wireless (@Commscope)" +short_description: Manage Interface on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of Interfaces + on ruckus icx devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the Interface. + type: str + description: + description: + - Name of the description. + type: str + enabled: + description: + - Interface link status + default: yes + type: bool + speed: + description: + - Interface link speed/duplex + choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] + type: str + stp: + description: + - enable/disable stp for the interface + type: bool + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + neighbors: + description: + - Check the operational state of given interface C(name) for CDP/LLDP neighbor. + - The following suboptions are available. + type: list + suboptions: + host: + description: + - "CDP/LLDP neighbor host for given interface C(name)." + type: str + port: + description: + - "CDP/LLDP neighbor port to which given interface C(name) is connected." + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + type: int + default: 10 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + type: str + choices: ['present', 'absent', 'up', 'down'] + power: + description: + - Inline power on Power over Ethernet (PoE) ports. + type: dict + suboptions: + by_class: + description: + - "The range is 0-4" + - "The power limit based on class value for given interface C(name)" + choices: ['0', '1', '2', '3', '4'] + type: str + limit: + description: + - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" + - "The power limit based on actual power value for given interface C(name)" + type: str + priority: + description: + - "The range is 1 (highest) to 3 (lowest)" + - "The priority for power management or given interface C(name)" + choices: ['1', '2', '3'] + type: str + enabled: + description: + - "enable/disable the poe of the given interface C(name)" + - "Default is false." + type: bool + aggregate: + description: + - List of Interfaces definitions. + type: list + suboptions: + name: + description: + - Name of the Interface. + type: str + description: + description: + - Name of the description. + type: str + enabled: + description: + - Interface link status + type: bool + speed: + description: + - Interface link speed/duplex + choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] + type: str + stp: + description: + - enable/disable stp for the interface + type: bool + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + neighbors: + description: + - Check the operational state of given interface C(name) for CDP/LLDP neighbor. + - The following suboptions are available. + type: list + suboptions: + host: + description: + - "CDP/LLDP neighbor host for given interface C(name)." + type: str + port: + description: + - "CDP/LLDP neighbor port to which given interface C(name) is connected." + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + type: int + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + type: str + choices: ['present', 'absent', 'up', 'down'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + - Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + power: + description: + - Inline power on Power over Ethernet (PoE) ports. + type: dict + suboptions: + by_class: + description: + - "The range is 0-4" + - "The power limit based on class value for given interface C(name)" + choices: ['0', '1', '2', '3', '4'] + type: str + limit: + description: + - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" + - "The power limit based on actual power value for given interface C(name)" + type: str + priority: + description: + - "The range is 1 (highest) to 3 (lowest)" + - "The priority for power management or given interface C(name)" + choices: ['1', '2', '3'] + type: str + enabled: + description: + - "enable/disable the poe of the given interface C(name)" + type: bool + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + - Module will use environment variable value(default:True), unless it is overridden, + by specifying it as module parameter. + default: yes + type: bool +''' + +EXAMPLES = """ +- name: Enable ethernet port and set name + community.network.icx_interface: + name: ethernet 1/1/1 + description: interface-1 + stp: true + enabled: true + +- name: Disable ethernet port 1/1/1 + community.network.icx_interface: + name: ethernet 1/1/1 + enabled: false + +- name: Enable ethernet port range, set name and speed + community.network.icx_interface: + name: ethernet 1/1/1 to 1/1/10 + description: interface-1 + speed: 100-full + enabled: true + +- name: Enable poe. Set class + community.network.icx_interface: + name: ethernet 1/1/1 + power: + by_class: 2 + +- name: Configure poe limit of interface + community.network.icx_interface: + name: ethernet 1/1/1 + power: + limit: 10000 + +- name: Disable poe of interface + community.network.icx_interface: + name: ethernet 1/1/1 + power: + enabled: false + +- name: Set lag name for a range of lags + community.network.icx_interface: + name: lag 1 to 10 + description: test lags + +- name: Disable lag + community.network.icx_interface: + name: lag 1 + enabled: false + +- name: Enable management interface + community.network.icx_interface: + name: management 1 + enabled: true + +- name: Enable loopback interface + community.network.icx_interface: + name: loopback 10 + enabled: true + +- name: Add interface using aggregate + community.network.icx_interface: + aggregate: + - { name: ethernet 1/1/1, description: test-interface-1, power: { by_class: 2 } } + - { name: ethernet 1/1/3, description: test-interface-3} + speed: 10-full + enabled: true + +- name: Check tx_rate, rx_rate intent arguments + community.network.icx_interface: + name: ethernet 1/1/10 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.icx_interface: + name: ethernet 1/1/10 + neighbors: + - port: 1/1/5 + host: netdev +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always + type: list + sample: + - interface ethernet 1/1/1 + - port-name interface-1 + - state present + - speed-duplex 100-full + - inline power priority 1 +""" + +import re +from copy import deepcopy +from time import sleep +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def parse_enable(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^disable', cfg, re.M) + if match: + return True + else: + return False + + +def parse_power_argument(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'(^inline power|^inline power(.*))+$', cfg, re.M) + if match: + return match.group(1) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def parse_stp_arguments(module, item): + rc, out, err = exec_command(module, 'show interfaces ' + item) + match = re.search(r'STP configured to (\S+),', out, re.S) + if match: + return True if match.group(1) == "ON" else False + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def validate_power(module, power): + count = 0 + for item in power: + if power.get(item) is not None: + count += 1 + if count > 1: + module.fail_json(msg='power parameters are mutually exclusive: class,limit,priority,enabled') + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, None, compare) + configobj = NetworkConfig(indent=1, contents=config) + match = re.findall(r'^interface (.+)$', config, re.M) + + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'port-name': parse_config_argument(configobj, item, 'port-name'), + 'speed-duplex': parse_config_argument(configobj, item, 'speed-duplex'), + 'stp': parse_stp_arguments(module, item), + 'disable': True if parse_enable(configobj, item) else False, + 'power': parse_power_argument(configobj, item), + 'state': 'present' + } + instances.append(obj) + return instances + + +def parse_poe_config(poe, power): + if poe.get('by_class') is not None: + power += 'power-by-class %s' % poe.get('by_class') + elif poe.get('limit') is not None: + power += 'power-limit %s' % poe.get('limit') + elif poe.get('priority') is not None: + power += 'priority %s' % poe.get('priority') + elif poe.get('enabled'): + power = 'inline power' + elif poe.get('enabled') is False: + power = 'no inline power' + return power + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + item['port-name'] = item.pop('description') + item['speed-duplex'] = item.pop('speed') + poe = item.get('power') + if poe: + + validate_power(module, poe) + power = 'inline power' + ' ' + power_arg = parse_poe_config(poe, power) + item.update({'power': power_arg}) + + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'port-name': module.params['description'], + 'speed-duplex': module.params['speed'], + 'stp': module.params['stp'], + 'delay': module.params['delay'], + 'state': module.params['state'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + poe = module.params.get('power') + if poe: + validate_power(module, poe) + power = 'inline power' + ' ' + power_arg = parse_poe_config(poe, power) + params.update({'power': power_arg}) + + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + + args = ('speed-duplex', 'port-name', 'power', 'stp') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + + if state == 'absent' and have == []: + commands.append('no ' + interface) + + elif state == 'absent' and obj_in_have: + commands.append('no ' + interface) + + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate == 'no inline power' and running is None: + candidate = None + if candidate != running: + if candidate: + if item == 'power': + cmd = str(candidate) + elif item == 'stp': + cmd = 'spanning-tree' if candidate else 'no spanning-tree' + else: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'disable', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'enable', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + if item == 'power': + commands.append(str(value)) + elif item == 'stp': + cmd = 'spanning-tree' if item else 'no spanning-tree' + else: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('disable') + if disable is False: + commands.append('enable') + + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors_lldp = None + have_neighbors_cdp = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interfaces %s' % w['name'] + rc, out, err = exec_command(module, command) + + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + if want_state in ('up', 'down'): + match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) + have_state = None + if match: + have_state = match.group(1) + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + if want_tx_rate: + match = re.search(r'%s (\d+)' % 'output rate:', out, re.M) + have_tx_rate = None + if match: + have_tx_rate = match.group(1) + + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + + if want_rx_rate: + match = re.search(r'%s (\d+)' % 'input rate:', out, re.M) + have_rx_rate = None + if match: + have_rx_rate = match.group(1) + + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + + if want_neighbors: + have_host = [] + have_port = [] + + 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_lldp: + lines = have_neighbors_lldp.strip().split('Local port: ') + + for line in lines: + field = line.split('\n') + if field[0].strip() == w['name'].split(' ')[1]: + for item in field: + match = re.search(r'\s*\+\s+System name\s+:\s+"(.*)"', item, re.M) + if match: + have_host.append(match.group(1)) + + match = re.search(r'\s*\+\s+Port description\s+:\s+"(.*)"', item, re.M) + if match: + have_port.append(match.group(1)) + + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + power_spec = dict( + by_class=dict(choices=['0', '1', '2', '3', '4']), + limit=dict(type='str'), + priority=dict(choices=['1', '2', '3']), + enabled=dict(type='bool') + ) + neighbors_spec = dict( + host=dict(), + port=dict() + ) + element_spec = dict( + name=dict(), + description=dict(), + enabled=dict(default=True, type='bool'), + speed=dict(type='str', choices=['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']), + stp=dict(type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']), + power=dict(type='dict', options=power_spec), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {} + result['changed'] = False + if warnings: + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_l3_interface.py new file mode 100644 index 00000000..8a2dc794 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_l3_interface.py @@ -0,0 +1,434 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_l3_interface +author: "Ruckus Wireless (@Commscope)" +short_description: Manage Layer-3 interfaces on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of Layer-3 interfaces + on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1 + type: str + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + type: str + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64. + type: str + mode: + description: + - Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf. + This should be specified only if ipv4 address is configured and if it is not secondary IP address. + choices: ['dynamic', 'ospf-ignore', 'ospf-passive'] + type: str + replace: + description: + - Replaces the configured primary IP address on the interface. + choices: ['yes', 'no'] + type: str + secondary: + description: + - Specifies that the configured address is a secondary IP address. + If this keyword is omitted, the configured address is the primary IP address. + choices: ['yes', 'no'] + type: str + aggregate: + description: + - List of Layer-3 interfaces definitions. Each of the entry in aggregate list should + define name of interface C(name) and a optional C(ipv4) or C(ipv6) address. + type: list + suboptions: + name: + description: + - Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1 + type: str + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + type: str + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64. + type: str + mode: + description: + - Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf. + This should be specified only if ipv4 address is configured and if it is not secondary IP address. + choices: ['dynamic', 'ospf-ignore', 'ospf-passive'] + type: str + replace: + description: + - Replaces the configured primary IP address on the interface. + choices: ['yes', 'no'] + type: str + secondary: + description: + - Specifies that the configured address is a secondary IP address. + If this keyword is omitted, the configured address is the primary IP address. + choices: ['yes', 'no'] + type: str + state: + description: + - State of the Layer-3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + state: + description: + - State of the Layer-3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + default: present + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Remove ethernet 1/1/1 IPv4 and IPv6 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + ipv6: "fd5d:12c9:2201:1::1/64" + state: absent + +- name: Replace ethernet 1/1/1 primary IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + replace: yes + state: absent + +- name: Replace ethernet 1/1/1 dynamic IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + mode: dynamic + state: absent + +- name: Set ethernet 1/1/1 secondary IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + secondary: yes + state: absent + +- name: Set ethernet 1/1/1 IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + +- name: Set ethernet 1/1/1 IPv6 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set IP addresses on aggregate + community.network.icx_l3_interface: + aggregate: + - { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 } + - { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.icx_l3_interface: + aggregate: + - { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 } + - { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent + + +- name: Set the ipv4 and ipv6 of a virtual ethernet(ve) + community.network.icx_l3_interface: + name: ve 100 + ipv4: 192.168.0.1 + ipv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface ethernet 1/1/1 + - ip address 192.168.0.1 255.255.255.0 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" + + +import re +from copy import deepcopy +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'], + 'replace': module.params['replace'], + 'mode': module.params['mode'], + 'secondary': module.params['secondary'], + }) + + validate_param_values(module, obj) + + return obj + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + + values = [] + matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M) + for match in matches: + match_str = match.group(1).strip() + if arg == 'ipv6 address': + values.append(match_str) + else: + values = match_str + break + + return values or None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, flags=['| begin interface'], compare=compare) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+ \S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1]))) + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + if 'replace' in w: + replace = w['replace'] == 'yes' + else: + replace = False + if w['mode'] is not None: + mode = ' ' + w['mode'] + else: + mode = '' + if w['secondary'] is not None: + secondary = w['secondary'] == 'yes' + else: + secondary = False + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and have == []: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1])) + commands.append('no ip address {ip}'.format(ip=ipv4)) + if ipv6: + commands.append('no ipv6 address {ip}'.format(ip=ipv6)) + + elif state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1])) + commands.append('no ip address {ip}'.format(ip=ipv4)) + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address {ip}'.format(ip=ipv6)) + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format(address[0], to_netmask(address[1])) + commands.append('ip address %s%s%s%s' % (format(ipv4), mode, ' replace' if (replace) else '', ' secondary' if (secondary) else '')) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]: + commands.append('ipv6 address {ip}'.format(ip=ipv6)) + + if commands[-1] == interface: + commands.pop(-1) + else: + commands.append("exit") + + return commands + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + replace=dict(choices=['yes', 'no']), + mode=dict(choices=['dynamic', 'ospf-ignore', 'ospf-passive']), + secondary=dict(choices=['yes', 'no']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])), + state=dict(default='present', + choices=['present', 'absent']), + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec) + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate'], ['secondary', 'replace'], ['secondary', 'mode']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + + if commands: + if not module.check_mode: + resp = load_config(module, commands) + warnings.extend((out for out in resp if out)) + + result['changed'] = True + + if warnings: + result['warnings'] = warnings + + result['commands'] = commands + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_linkagg.py new file mode 100644 index 00000000..7400d56d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_linkagg.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_linkagg +author: "Ruckus Wireless (@Commscope)" +short_description: Manage link aggregation groups on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of link aggregation groups + on Ruckus ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID + type: int + name: + description: + - Name of the LAG + type: str + mode: + description: + - Mode of the link aggregation group. + type: str + choices: ['dynamic', 'static'] + members: + description: + - List of port members or ranges of the link aggregation group. + type: list + state: + description: + - State of the link aggregation group. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes + aggregate: + description: + - List of link aggregation definitions. + type: list + suboptions: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID + type: int + name: + description: + - Name of the LAG + type: str + mode: + description: + - Mode of the link aggregation group. + type: str + choices: ['dynamic', 'static'] + members: + description: + - List of port members or ranges of the link aggregation group. + type: list + state: + description: + - State of the link aggregation group. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: no + +''' + +EXAMPLES = """ +- name: Create static link aggregation group + community.network.icx_linkagg: + group: 10 + mode: static + name: LAG1 + +- name: Create link aggregation group with auto id + community.network.icx_linkagg: + group: auto + mode: dynamic + name: LAG2 + +- name: Delete link aggregation group + community.network.icx_linkagg: + group: 10 + state: absent + +- name: Set members to LAG + community.network.icx_linkagg: + group: 200 + mode: static + members: + - ethernet 1/1/1 to 1/1/6 + - ethernet 1/1/10 + +- name: Remove links other then LAG id 100 and 3 using purge + community.network.icx_linkagg: + aggregate: + - { group: 3} + - { group: 100} + purge: true +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lag LAG1 dynamic id 11 + - ports ethernet 1/1/1 to 1/1/6 + - no ports ethernet 1/1/10 + - no lag LAG1 dynamic id 12 +""" + + +import re +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import ConnectionError, exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands, get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec + + +def range_to_members(ranges, prefix=""): + match = re.findall(r'(ethe[a-z]* [0-9]/[0-9]/[0-9]+)( to [0-9]/[0-9]/[0-9]+)?', ranges) + members = list() + for m in match: + start, end = m + if(end == ''): + start = start.replace("ethe ", "ethernet ") + members.append("%s%s" % (prefix, start)) + else: + start_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', start) + end_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', end) + start = int(start_tmp.group(1)) + end = int(end_tmp.group(1)) + 1 + for num in range(start, end): + members.append("%sethernet 1/1/%s" % (prefix, num)) + return members + + +def map_config_to_obj(module): + objs = dict() + compare = module.params['check_running_config'] + config = get_config(module, None, compare=compare) + obj = None + for line in config.split('\n'): + l = line.strip() + match1 = re.search(r'lag (\S+) (\S+) id (\S+)', l, re.M) + if match1: + obj = dict() + obj['name'] = match1.group(1) + obj['mode'] = match1.group(2) + obj['group'] = match1.group(3) + obj['state'] = 'present' + obj['members'] = list() + else: + match2 = re.search(r'ports .*', l, re.M) + if match2 and obj is not None: + obj['members'].extend(range_to_members(match2.group(0))) + elif obj is not None: + objs[obj['group']] = obj + obj = None + return objs + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + d = item.copy() + d['group'] = str(d['group']) + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'], + 'name': module.params['name'] + }) + + return obj + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + return None + + +def is_member(member, lst): + for li in lst: + ml = range_to_members(li) + if member in ml: + return True + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + if have == {} and w['state'] == 'absent': + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + elif have.get(w['group']) is None: + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + if(w.get('members') is not None and w['state'] == 'present'): + for m in w['members']: + commands.append("ports %s" % (m)) + if w['state'] == 'present': + commands.append("exit") + else: + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + if(w.get('members') is not None and w['state'] == 'present'): + for m in have[w['group']]['members']: + if not is_member(m, w['members']): + commands.append("no ports %s" % (m)) + for m in w['members']: + sm = range_to_members(ranges=m) + for smm in sm: + if smm not in have[w['group']]['members']: + commands.append("ports %s" % (smm)) + + if w['state'] == 'present': + commands.append("exit") + if purge: + for h in have: + if search_obj_in_list(have[h]['group'], want) is None: + commands.append("no lag %s %s id %s" % (have[h]['name'], have[h]['mode'], have[h]['group'])) + return commands + + +def main(): + element_spec = dict( + group=dict(type='int'), + name=dict(type='str'), + mode=dict(choices=['dynamic', 'static']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True, type='int') + + required_one_of = [['group', 'aggregate']] + required_together = [['name', 'group']] + mutually_exclusive = [['group', 'aggregate']] + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + exec_command(module, 'skip') + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_lldp.py new file mode 100644 index 00000000..43d10765 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_lldp.py @@ -0,0 +1,178 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_lldp +author: "Ruckus Wireless (@Commscope)" +short_description: Manage LLDP configuration on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of LLDP service on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + interfaces: + description: + - specify interfaces + suboptions: + name: + description: + - List of ethernet ports to enable lldp. To add a range of ports use 'to' keyword. See the example. + type: list + state: + description: + - State of lldp configuration for interfaces + type: str + choices: ['present', 'absent', 'enabled', 'disabled'] + type: list + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes + state: + description: + - Enables the receipt and transmission of Link Layer Discovery Protocol (LLDP) globally. + type: str + choices: ['present', 'absent', 'enabled', 'disabled'] +''' + +EXAMPLES = """ +- name: Disable LLDP + community.network.icx_lldp: + state: absent + +- name: Enable LLDP + community.network.icx_lldp: + state: present + +- name: Disable LLDP on ports 1/1/1 - 1/1/10, 1/1/20 + community.network.icx_lldp: + interfaces: + - name: + - ethernet 1/1/1 to 1/1/10 + - ethernet 1/1/20 + state: absent + state: present + +- name: Enable LLDP on ports 1/1/5 - 1/1/10 + community.network.icx_lldp: + interfaces: + - name: + - ethernet 1/1/1 to 1/1/10 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lldp run + - no lldp run +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, run_commands + + +def has_lldp(module): + run_commands(module, ['skip']) + output = run_commands(module, ['show lldp']) + is_lldp_enable = False + if len(output) > 0 and "LLDP is not running" not in output[0]: + is_lldp_enable = True + + return is_lldp_enable + + +def map_obj_to_commands(module, commands): + interfaces = module.params.get('interfaces') + for item in interfaces: + state = item.get('state') + if state == 'present': + for port in item.get('name'): + if 'all' in port: + module.fail_json(msg='cannot enable on all the ports') + else: + commands.append('lldp enable ports {0}'.format(str(port))) + elif state == 'absent': + for port in item.get('name'): + if 'all' in port: + module.fail_json(msg='cannot enable on all the ports') + else: + commands.append('no lldp enable ports {0}'.format(str(port))) + + +def main(): + """ main entry point for module execution + """ + interfaces_spec = dict( + name=dict(type='list'), + state=dict(choices=['present', 'absent', + 'enabled', 'disabled']) + ) + + argument_spec = dict( + interfaces=dict(type='list', elements='dict', options=interfaces_spec), + state=dict(choices=['present', 'absent', + 'enabled', 'disabled']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + if module.params['check_running_config'] is False: + HAS_LLDP = None + else: + HAS_LLDP = has_lldp(module) + + commands = [] + state = module.params['state'] + + if state is None: + if HAS_LLDP: + map_obj_to_commands(module, commands) + else: + module.fail_json(msg='LLDP is not running') + else: + if state == 'absent' and HAS_LLDP is None: + commands.append('no lldp run') + + if state == 'absent' and HAS_LLDP: + commands.append('no lldp run') + + elif state == 'present': + if not HAS_LLDP: + commands.append('lldp run') + if module.params.get('interfaces'): + map_obj_to_commands(module, commands) + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_logging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_logging.py new file mode 100644 index 00000000..fbe3d73d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_logging.py @@ -0,0 +1,576 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_logging +author: "Ruckus Wireless (@Commscope)" +short_description: Manage logging on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of logging + on Ruckus ICX 7000 series switches. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + aggregate: + description: + - List of logging definitions. + type: list + suboptions: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + state: + description: + - State of the logging configuration. + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + state: + description: + - State of the logging configuration. + default: present + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure host logging. + community.network.icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 +- name: Remove host logging configuration. + community.network.icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 + state: absent +- name: Disables the real-time display of syslog messages. + community.network.icx_logging: + dest: console + state: absent +- name: Enables local syslog logging. + community.network.icx_logging: + dest : on + state: present +- name: Configure buffer level + community.network.icx_logging: + dest: buffered + level: critical +- name: Configure logging using aggregate + community.network.icx_logging: + aggregate: + - { dest: buffered, level: ['notifications','errors'] } +- name: Remove logging using aggregate + community.network.icx_logging: + aggregate: + - { dest: console } + - { dest: host, name: 172.16.0.1, udp_port: 5555 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging host 172.16.0.1 + - logging console +""" + +import re +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec, validate_ip_v6_address +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def diff_in_list(want, have): + adds = set() + removes = set() + for w in want: + if w['dest'] == 'buffered': + for h in have: + if h['dest'] == 'buffered': + adds = w['level'] - h['level'] + removes = h['level'] - w['level'] + return adds, removes + return adds, removes + + +def map_obj_to_commands(updates): + dest_group = ('host', 'console', 'persistence', 'enable') + commands = list() + want, have = updates + + for w in want: + dest = w['dest'] + name = w['name'] + level = w['level'] + state = w['state'] + udp_port = w['udp_port'] + facility = w['facility'] + del w['state'] + del w['facility'] + + facility_name = '' + facility_level = '' + if name is not None and validate_ip_v6_address(name): + name = 'ipv6 ' + name + + if facility: + for item in have: + if item['dest'] == 'facility': + facility_name = item['dest'] + facility_level = item['facility'] + + if state == 'absent': + if have == []: + if facility: + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + commands.append('no logging buffered {0}'.format(item)) + + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if facility: + if facility_name == 'facility' and facility_level != 'user': + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + if item in have_level: + commands.append('no logging buffered {0}'.format(item)) + + if w in have: + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if state == 'present': + if facility: + if facility != facility_level: + commands.append('logging facility {0}'.format(facility)) + if w not in have: + if dest == 'host': + if name and udp_port: + commands.append('logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('logging host {0}'.format(name)) + elif dest == 'buffered': + adds, removes = diff_in_list(want, have) + for item in adds: + commands.append('logging buffered {0}'.format(item)) + for item in removes: + commands.append('no logging buffered {0}'.format(item)) + elif dest == 'rfc5424': + commands.append('logging enable {0}'.format(dest)) + else: + commands.append('logging {0}'.format(dest)) + + return commands + + +def parse_port(line, dest): + port = None + if dest == 'host': + match = re.search(r'logging host \S+\s+udp-port\s+(\d+)', line, re.M) + if match: + port = match.group(1) + else: + match_port = re.search(r'logging host ipv6 \S+\s+udp-port\s+(\d+)', line, re.M) + if match_port: + port = match_port.group(1) + return port + + +def parse_name(line, dest): + name = None + if dest == 'host': + match = re.search(r'logging host (\S+)', line, re.M) + if match: + if match.group(1) == 'ipv6': + ipv6_add = re.search(r'logging host ipv6 (\S+)', line, re.M) + name = ipv6_add.group(1) + else: + name = match.group(1) + + return name + + +def parse_address(line, dest): + if dest == 'host': + match = re.search(r'^logging host ipv6 (\S+)', line.strip(), re.M) + if match: + return True + return False + + +def map_config_to_obj(module): + obj = [] + facility = '' + addr6 = False + dest_group = ('host', 'console', 'buffered', 'persistence', 'enable') + dest_level = ('alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', 'notifications', 'warnings') + buff_level = list() + if module.params['check_running_config'] is False: + return [] + data = get_config(module, flags=['| include logging']) + facility_match = re.search(r'^logging facility (\S+)', data, re.M) + if facility_match: + facility = facility_match.group(1) + obj.append({ + 'dest': 'facility', + 'facility': facility + }) + else: + obj.append({ + 'dest': 'facility', + 'facility': 'user' + }) + for line in data.split('\n'): + match = re.search(r'^logging (\S+)', line.strip(), re.M) + if match: + + if match.group(1) in dest_group: + dest = match.group(1) + if dest == 'host': + obj.append({ + 'dest': dest, + 'name': parse_name(line.strip(), dest), + 'udp_port': parse_port(line, dest), + 'level': None, + 'addr6': parse_address(line, dest) + + }) + elif dest == 'buffered': + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + if dest == 'enable': + dest = 'rfc5424' + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + + ip_match = re.search(r'^no logging buffered (\S+)', line, re.M) + if ip_match: + dest = 'buffered' + buff_level.append(ip_match.group(1)) + if 'no logging on' not in data: + obj.append({ + 'dest': 'on', + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + levels = set() + for items in dest_level: + if items not in buff_level: + levels.add(items) + obj.append({ + 'dest': 'buffered', + 'level': levels, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + return obj + + +def count_terms(check, param=None): + count = 0 + for term in check: + if param[term] is not None: + count += 1 + return count + + +def check_required_if(module, spec, param): + for sp in spec: + missing = [] + max_missing_count = 0 + is_one_of = False + if len(sp) == 4: + key, val, requirements, is_one_of = sp + else: + key, val, requirements = sp + + if is_one_of: + max_missing_count = len(requirements) + term = 'any' + else: + term = 'all' + + if key in param and param[key] == val: + for check in requirements: + count = count_terms((check,), param) + if count == 0: + missing.append(check) + if len(missing) and len(missing) >= max_missing_count: + msg = "%s is %s but %s of the following are missing: %s" % (key, val, term, ', '.join(missing)) + module.fail_json(msg=msg) + + +def map_params_to_obj(module, required_if=None): + obj = [] + addr6 = False + aggregate = module.params.get('aggregate') + + if aggregate: + for item in aggregate: + if item['name'] is not None and validate_ip_v6_address(item['name']): + addr6 = True + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + check_required_if(module, required_if, item) + item.update({'addr6': addr6}) + + d = item.copy() + d['level'] = set(d['level']) if d['level'] is not None else None + if d['dest'] != 'host': + d['name'] = None + d['udp_port'] = None + + if d['dest'] != 'buffered': + d['level'] = None + del d['check_running_config'] + obj.append(d) + + else: + if module.params['name'] is not None and validate_ip_v6_address(module.params['name']): + addr6 = True + if module.params['dest'] != 'host': + module.params['name'] = None + module.params['udp_port'] = None + + if module.params['dest'] != 'buffered': + module.params['level'] = None + + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'udp_port': module.params['udp_port'], + 'level': set(module.params['level']) if module.params['level'] else None, + 'facility': module.params['facility'], + 'state': module.params['state'], + 'addr6': addr6 + }) + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + dest=dict( + type='str', + choices=[ + 'on', + 'host', + 'console', + 'buffered', + 'persistence', + 'rfc5424']), + name=dict( + type='str'), + udp_port=dict(), + level=dict( + type='list', + choices=[ + 'alerts', + 'critical', + 'debugging', + 'emergencies', + 'errors', + 'informational', + 'notifications', + 'warnings']), + facility=dict( + type='str', + choices=[ + 'auth', + 'cron', + 'daemon', + 'kern', + 'local0', + 'local1', + 'local2', + 'local3', + 'local4', + 'local5', + 'local6', + 'local7', + 'user', + 'lpr', + 'mail', + 'news', + 'syslog', + 'sys9', + 'sys10', + 'sys11', + 'sys12', + 'sys13', + 'sys14', + 'user', + 'uucp']), + state=dict( + default='present', + choices=[ + 'present', + 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))) + + aggregate_spec = deepcopy(element_spec) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + required_if = [('dest', 'host', ['name']), + ('dest', 'buffered', ['level'])] + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + warnings = list() + + exec_command(module, 'skip') + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_if=required_if) + have = map_config_to_obj(module) + result['want'] = want + result['have'] = have + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_ping.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_ping.py new file mode 100644 index 00000000..8ea2544e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_ping.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_ping +author: "Ruckus Wireless (@Commscope)" +short_description: Tests reachability using ping from Ruckus ICX 7000 series switches +description: + - Tests reachability using ping from switch to a remote destination. +notes: + - Tested against ICX 10.1 +options: + count: + description: + - Number of packets to send. Default is 1. + type: int + dest: + description: + - ip-addr | host-name | vrf vrf-name | ipv6 [ ipv6-addr | host-name | vrf vrf-name] (resolvable by switch) of the remote node. + required: true + type: str + timeout: + description: + - Specifies the time, in milliseconds for which the device waits for a reply from the pinged device. + The value can range from 1 to 4294967296. The default is 5000 (5 seconds). + type: int + ttl: + description: + - Specifies the time to live as a maximum number of hops. The value can range from 1 to 255. The default is 64. + type: int + size: + description: + - Specifies the size of the ICMP data portion of the packet, in bytes. This is the payload and does not include the header. + The value can range from 0 to 10000. The default is 16.. + type: int + source: + description: + - IP address to be used as the origin of the ping packets. + type: str + vrf: + description: + - Specifies the Virtual Routing and Forwarding (VRF) instance of the device to be pinged. + type: str + state: + description: + - Determines if the expected result is success or fail. + type: str + choices: [ absent, present ] + default: present +''' + +EXAMPLES = r''' +- name: Test reachability to 10.10.10.10 + community.network.icx_ping: + dest: 10.10.10.10 + +- name: Test reachability to ipv6 address from source with timeout + community.network.icx_ping: + dest: ipv6 2001:cdba:0000:0000:0000:0000:3257:9652 + source: 10.1.1.1 + timeout: 100000 + +- name: Test reachability to 10.1.1.1 through vrf using 5 packets + community.network.icx_ping: + dest: 10.1.1.1 + vrf: x.x.x.x + count: 5 + +- name: Test unreachability to 10.30.30.30 + community.network.icx_ping: + dest: 10.40.40.40 + state: absent + +- name: Test reachability to ipv4 with ttl and packet size + community.network.icx_ping: + dest: 10.10.10.10 + ttl: 20 + size: 500 +''' + +RETURN = ''' +commands: + description: Show the command sent. + returned: always + type: list + sample: ["ping 10.40.40.40 count 20 source loopback0", "ping 10.40.40.40"] +packet_loss: + description: Percentage of packets lost. + returned: always + type: str + sample: "0%" +packets_rx: + description: Packets successfully received. + returned: always + type: int + sample: 20 +packets_tx: + description: Packets successfully transmitted. + returned: always + type: int + sample: 20 +rtt: + description: Show RTT stats. + returned: always + type: dict + sample: {"avg": 2, "max": 8, "min": 1} +''' + +from ansible.module_utils._text import to_text +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection, ConnectionError +import re + + +def build_ping(dest, count=None, source=None, timeout=None, ttl=None, size=None, vrf=None): + """ + Function to build the command to send to the terminal for the switch + to execute. All args come from the module's unique params. + """ + + if vrf is not None: + cmd = "ping vrf {0} {1}".format(vrf, dest) + else: + cmd = "ping {0}".format(dest) + + if count is not None: + cmd += " count {0}".format(str(count)) + + if timeout is not None: + cmd += " timeout {0}".format(str(timeout)) + + if ttl is not None: + cmd += " ttl {0}".format(str(ttl)) + + if size is not None: + cmd += " size {0}".format(str(size)) + + if source is not None: + cmd += " source {0}".format(source) + + return cmd + + +def parse_ping(ping_stats): + """ + Function used to parse the statistical information from the ping response. + Example: "Success rate is 100 percent (5/5), round-trip min/avg/max=40/51/55 ms." + Returns the percent of packet loss, received packets, transmitted packets, and RTT dict. + """ + if ping_stats.startswith('Success'): + rate_re = re.compile(r"^\w+\s+\w+\s+\w+\s+(?P\d+)\s+\w+\s+\((?P\d+)/(?P\d+)\)") + rtt_re = re.compile(r".*,\s+\S+\s+\S+=(?P\d+)/(?P\d+)/(?P\d+)\s+\w+\.+\s*$|.*\s*$") + + rate = rate_re.match(ping_stats) + rtt = rtt_re.match(ping_stats) + return rate.group("pct"), rate.group("rx"), rate.group("tx"), rtt.groupdict() + else: + rate_re = re.compile(r"^Sending+\s+(?P\d+),") + rate = rate_re.match(ping_stats) + rtt = {'avg': 0, 'max': 0, 'min': 0} + return 0, 0, rate.group('tx'), rtt + + +def validate_results(module, loss, results): + """ + This function is used to validate whether the ping results were unexpected per "state" param. + """ + state = module.params["state"] + if state == "present" and loss == 100: + module.fail_json(msg="Ping failed unexpectedly", **results) + elif state == "absent" and loss < 100: + module.fail_json(msg="Ping succeeded unexpectedly", **results) + + +def validate_fail(module, responses): + if ("Success" in responses or "No reply" in responses) is False: + module.fail_json(msg=responses) + + +def validate_parameters(module, timeout, count): + if timeout and not 1 <= int(timeout) <= 4294967294: + module.fail_json(msg="bad value for timeout - valid range (1-4294967294)") + if count and not 1 <= int(count) <= 4294967294: + module.fail_json(msg="bad value for count - valid range (1-4294967294)") + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + count=dict(type="int"), + dest=dict(type="str", required=True), + timeout=dict(type="int"), + ttl=dict(type="int"), + size=dict(type="int"), + source=dict(type="str"), + state=dict(type="str", choices=["absent", "present"], default="present"), + vrf=dict(type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec) + + count = module.params["count"] + dest = module.params["dest"] + source = module.params["source"] + timeout = module.params["timeout"] + ttl = module.params["ttl"] + size = module.params["size"] + vrf = module.params["vrf"] + results = {} + warnings = list() + + if warnings: + results["warnings"] = warnings + + response = '' + try: + validate_parameters(module, timeout, count) + results["commands"] = [build_ping(dest, count, source, timeout, ttl, size, vrf)] + ping_results = run_commands(module, commands=results["commands"]) + ping_results_list = ping_results[0].split("\n") + + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + validate_fail(module, ping_results[0]) + + stats = "" + statserror = '' + for line in ping_results_list: + if line.startswith('Sending'): + statserror = line + if line.startswith('Success'): + stats = line + elif line.startswith('No reply'): + stats = statserror + + success, rx, tx, rtt = parse_ping(stats) + loss = abs(100 - int(success)) + results["packet_loss"] = str(loss) + "%" + results["packets_rx"] = int(rx) + results["packets_tx"] = int(tx) + + # Convert rtt values to int + for k, v in rtt.items(): + if rtt[k] is not None: + rtt[k] = int(v) + + results["rtt"] = rtt + + validate_results(module, loss, results) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_static_route.py new file mode 100644 index 00000000..bf3fed12 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_static_route.py @@ -0,0 +1,310 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_static_route +author: "Ruckus Wireless (@Commscope)" +short_description: Manage static IP routes on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of static + IP routes on Ruckus ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + prefix: + description: + - Network prefix of the static route. + type: str + mask: + description: + - Network prefix mask of the static route. + type: str + next_hop: + description: + - Next hop IP of the static route. + type: str + admin_distance: + description: + - Admin distance of the static route. Range is 1 to 255. + type: int + aggregate: + description: List of static route definitions. + type: list + suboptions: + prefix: + description: + - Network prefix of the static route. + type: str + mask: + description: + - Network prefix mask of the static route. + type: str + next_hop: + description: + - Next hop IP of the static route. + type: str + admin_distance: + description: + - Admin distance of the static route. Range is 1 to 255. + type: int + state: + description: + - State of the static route configuration. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + purge: + description: + - Purge routes not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the static route configuration. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure static route + community.network.icx_static_route: + prefix: 192.168.2.0/24 + next_hop: 10.0.0.1 + +- name: Remove configuration + community.network.icx_static_route: + prefix: 192.168.2.0 + mask: 255.255.255.0 + next_hop: 10.0.0.1 + state: absent + +- name: Add static route aggregates + community.network.icx_static_route: + aggregate: + - { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + - { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + +- name: Remove static route aggregates + community.network.icx_static_route: + aggregate: + - { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + - { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - ip route 192.168.2.0 255.255.255.0 10.0.0.1 +""" + + +from copy import deepcopy +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + +try: + from ipaddress import ip_network + HAS_IPADDRESS = True +except ImportError: + HAS_IPADDRESS = False + + +def map_obj_to_commands(want, have, module): + commands = list() + purge = module.params['purge'] + for w in want: + for h in have: + for key in ['prefix', 'mask', 'next_hop']: + if w[key] != h[key]: + break + else: + break + else: + h = None + + prefix = w['prefix'] + mask = w['mask'] + next_hop = w['next_hop'] + admin_distance = w.get('admin_distance') + if not admin_distance and h: + w['admin_distance'] = admin_distance = h['admin_distance'] + state = w['state'] + del w['state'] + + if state == 'absent' and have == []: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + + if state == 'absent' and w in have: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + elif state == 'present' and w not in have: + if admin_distance: + commands.append('ip route %s %s %s distance %s' % (prefix, mask, next_hop, admin_distance)) + else: + commands.append('ip route %s %s %s' % (prefix, mask, next_hop)) + if purge: + commands = [] + for h in have: + if h not in want: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + return commands + + +def map_config_to_obj(module): + obj = [] + compare = module.params['check_running_config'] + out = get_config(module, flags='| include ip route', compare=compare) + + for line in out.splitlines(): + splitted_line = line.split() + if len(splitted_line) not in (4, 5, 6): + continue + cidr = ip_network(to_text(splitted_line[2])) + prefix = str(cidr.network_address) + mask = str(cidr.netmask) + next_hop = splitted_line[3] + if len(splitted_line) == 6: + admin_distance = splitted_line[5] + else: + admin_distance = '1' + + obj.append({ + 'prefix': prefix, 'mask': mask, 'next_hop': next_hop, + 'admin_distance': admin_distance + }) + + return obj + + +def prefix_length_parser(prefix, mask, module): + if '/' in prefix and mask is not None: + module.fail_json(msg='Ambigous, specifed both length and mask') + if '/' in prefix: + cidr = ip_network(to_text(prefix)) + prefix = str(cidr.network_address) + mask = str(cidr.netmask) + return prefix, mask + + +def map_params_to_obj(module, required_together=None): + keys = ['prefix', 'mask', 'next_hop', 'admin_distance', 'state'] + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + route = item.copy() + for key in keys: + if route.get(key) is None: + route[key] = module.params.get(key) + + module._check_required_together(required_together, route) + + prefix, mask = prefix_length_parser(route['prefix'], route['mask'], module) + route.update({'prefix': prefix, 'mask': mask}) + + obj.append(route) + else: + module._check_required_together(required_together, module.params) + prefix, mask = prefix_length_parser(module.params['prefix'], module.params['mask'], module) + + obj.append({ + 'prefix': prefix, + 'mask': mask, + 'next_hop': module.params['next_hop'].strip(), + 'admin_distance': module.params.get('admin_distance'), + 'state': module.params['state'], + }) + + for route in obj: + if route['admin_distance']: + route['admin_distance'] = str(route['admin_distance']) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + next_hop=dict(type='str'), + admin_distance=dict(type='int'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['prefix'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['aggregate', 'prefix']] + required_together = [['prefix', 'next_hop']] + mutually_exclusive = [['aggregate', 'prefix']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + if not HAS_IPADDRESS: + module.fail_json(msg="ipaddress python package is required") + + warnings = list() + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_together=required_together) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_system.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_system.py new file mode 100644 index 00000000..bc20c174 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_system.py @@ -0,0 +1,466 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_system +author: "Ruckus Wireless (@Commscope)" +short_description: Manage the system attributes on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of node system attributes + on Ruckus ICX 7000 series switches. It provides an option to configure host system + parameters or remove those parameters from the device active + configuration. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + hostname: + description: + - Configure the device hostname parameter. This option takes an ASCII string value. + type: str + domain_name: + description: + - Configure the IP domain name on the remote device to the provided value. + Value should be in the dotted name form and + will be appended to the hostname to create a fully-qualified domain name. + type: list + domain_search: + description: + - Provides the list of domain names to + append to the hostname for the purpose of doing name resolution. + This argument accepts a list of names and will be reconciled + with the current active configuration on the running node. + type: list + name_servers: + description: + - List of DNS name servers by IP address to use to perform name resolution + lookups. + type: list + aaa_servers: + description: + - Configures radius/tacacs server + type: list + suboptions: + type: + description: + - specify the type of the server + type: str + choices: ['radius','tacacs'] + hostname: + description: + - Configures the host name of the RADIUS server + type: str + auth_port_type: + description: + - specifies the type of the authentication port + type: str + choices: ['auth-port'] + auth_port_num: + description: + - Configures the authentication UDP port. The default value is 1812. + type: str + acct_port_num: + description: + - Configures the accounting UDP port. The default value is 1813. + type: str + acct_type: + description: + - Usage of the accounting port. + type: str + choices: ['accounting-only', 'authentication-only','authorization-only', default] + auth_key: + description: + - Configure the key for the server + type: str + auth_key_type: + description: + - List of authentication level specified in the choices + type: list + choices: ['dot1x','mac-auth','web-auth'] + state: + description: + - State of the configuration + values in the device's current active configuration. When set + to I(present), the values should be configured in the device active + configuration and when set to I(absent) the values should not be + in the device active configuration + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure hostname and domain name + community.network.icx_system: + hostname: icx + domain_search: + - ansible.com + - redhat.com + - ruckus.com + +- name: Configure radius server of type auth-port + community.network.icx_system: + aaa_servers: + - type: radius + hostname: radius-server + auth_port_type: auth-port + auth_port_num: 1821 + acct_port_num: 1321 + acct_type: accounting-only + auth_key: abc + auth_key_type: + - dot1x + - mac-auth + +- name: Configure tacacs server + community.network.icx_system: + aaa_servers: + - type: tacacs + hostname: tacacs-server + auth_port_type: auth-port + auth_port_num: 1821 + acct_port_num: 1321 + acct_type: accounting-only + auth_key: xyz + +- name: Configure name servers + community.network.icx_system: + name_servers: + - 8.8.8.8 + - 8.8.4.4 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - hostname icx + - ip domain name test.example.com + - radius-server host 172.16.10.12 auth-port 2083 acct-port 1850 default key abc dot1x mac-auth + - tacacs-server host 10.2.3.4 auth-port 4058 authorization-only key xyz + +""" + + +import re +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, validate_ip_v6_address +from ansible.module_utils.connection import Connection, ConnectionError, exec_command + + +def diff_list(want, have): + adds = [w for w in want if w not in have] + removes = [h for h in have if h not in want] + return (adds, removes) + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params['state'] + + def needs_update(x): + return want.get(x) is not None and (want.get(x) != have.get(x)) + + if state == 'absent': + if have['name_servers'] == [] and have['aaa_servers'] == [] and have['domain_search'] == [] and have['hostname'] is None: + if want['hostname']: + commands.append('no hostname') + + if want['domain_search']: + for item in want['domain_search']: + commands.append('no ip dns domain-list %s' % item) + + if want['name_servers']: + for item in want['name_servers']: + commands.append('no ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + if want_server: + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + for item in want_servers: + ipv6addr = validate_ip_v6_address(item['hostname']) + if ipv6addr: + commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname']) + else: + commands.append('no ' + item['type'] + '-server host ' + item['hostname']) + + if want['hostname']: + if have['hostname'] == want['hostname']: + commands.append('no hostname') + + if want['domain_search']: + for item in want['domain_search']: + if item in have['domain_search']: + commands.append('no ip dns domain-list %s' % item) + + if want['name_servers']: + for item in want['name_servers']: + if item in have['name_servers']: + commands.append('no ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + have_server = have['aaa_servers'] + if want_server: + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + for item in want_servers: + if item in have_server: + ipv6addr = validate_ip_v6_address(item['hostname']) + if ipv6addr: + commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname']) + else: + commands.append('no ' + item['type'] + '-server host ' + item['hostname']) + + elif state == 'present': + if needs_update('hostname'): + commands.append('hostname %s' % want['hostname']) + + if want['domain_search']: + adds, removes = diff_list(want['domain_search'], have['domain_search']) + for item in removes: + commands.append('no ip dns domain-list %s' % item) + for item in adds: + commands.append('ip dns domain-list %s' % item) + + if want['name_servers']: + adds, removes = diff_list(want['name_servers'], have['name_servers']) + for item in removes: + commands.append('no ip dns server-address %s' % item) + for item in adds: + commands.append('ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + have_server = have['aaa_servers'] + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + + adds, removes = diff_list(want_servers, have_server) + + for item in removes: + ip6addr = validate_ip_v6_address(item['hostname']) + if ip6addr: + cmd = 'no ' + item['type'] + '-server host ipv6 ' + item['hostname'] + else: + cmd = 'no ' + item['type'] + '-server host ' + item['hostname'] + commands.append(cmd) + + for w_item in adds: + for item in want_server: + if item['hostname'] == w_item['hostname'] and item['type'] == w_item['type']: + auth_key = item['auth_key'] + + ip6addr = validate_ip_v6_address(w_item['hostname']) + if ip6addr: + cmd = w_item['type'] + '-server host ipv6 ' + w_item['hostname'] + else: + cmd = w_item['type'] + '-server host ' + w_item['hostname'] + if w_item['auth_port_type']: + cmd += ' ' + w_item['auth_port_type'] + ' ' + w_item['auth_port_num'] + if w_item['acct_port_num'] and w_item['type'] == 'radius': + cmd += ' acct-port ' + w_item['acct_port_num'] + if w_item['type'] == 'tacacs': + if any((w_item['acct_port_num'], w_item['auth_key_type'])): + module.fail_json(msg='acct_port and auth_key_type is not applicable for tacacs server') + if w_item['acct_type']: + cmd += ' ' + w_item['acct_type'] + if auth_key is not None: + cmd += ' key ' + auth_key + if w_item['auth_key_type'] and w_item['type'] == 'radius': + val = '' + for y in w_item['auth_key_type']: + val = val + ' ' + y + cmd += val + commands.append(cmd) + + return commands + + +def parse_hostname(config): + match = re.search(r'^hostname (\S+)', config, re.M) + if match: + return match.group(1) + + +def parse_domain_search(config): + match = re.findall(r'^ip dns domain[- ]list (\S+)', config, re.M) + matches = list() + for name in match: + matches.append(name) + return matches + + +def parse_name_servers(config): + matches = list() + values = list() + lines = config.split('\n') + for line in lines: + if 'ip dns server-address' in line: + values = line.split(' ') + for val in values: + match = re.search(r'([0-9.]+)', val) + if match: + matches.append(match.group()) + + return matches + + +def parse_aaa_servers(config): + configlines = config.split('\n') + obj = [] + for line in configlines: + auth_key_type = [] + if 'radius-server' in line or 'tacacs-server' in line: + aaa_type = 'radius' if 'radius-server' in line else 'tacacs' + match = re.search(r'(host ipv6 (\S+))|(host (\S+))', line) + if match: + hostname = match.group(2) if match.group(2) is not None else match.group(4) + match = re.search(r'auth-port ([0-9]+)', line) + if match: + auth_port_num = match.group(1) + else: + auth_port_num = None + match = re.search(r'acct-port ([0-9]+)', line) + if match: + acct_port_num = match.group(1) + else: + acct_port_num = None + match = re.search(r'acct-port [0-9]+ (\S+)', line) + if match: + acct_type = match.group(1) + else: + acct_type = None + if aaa_type == 'tacacs': + match = re.search(r'auth-port [0-9]+ (\S+)', line) + if match: + acct_type = match.group(1) + else: + acct_type = None + match = re.search(r'(dot1x)', line) + if match: + auth_key_type.append('dot1x') + match = re.search(r'(mac-auth)', line) + if match: + auth_key_type.append('mac-auth') + match = re.search(r'(web-auth)', line) + if match: + auth_key_type.append('web-auth') + + obj.append({ + 'type': aaa_type, + 'hostname': hostname, + 'auth_port_type': 'auth-port', + 'auth_port_num': auth_port_num, + 'acct_port_num': acct_port_num, + 'acct_type': acct_type, + 'auth_key': None, + 'auth_key_type': set(auth_key_type) if len(auth_key_type) > 0 else None + }) + + return obj + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, None, compare=compare) + return { + 'hostname': parse_hostname(config), + 'domain_search': parse_domain_search(config), + 'name_servers': parse_name_servers(config), + 'aaa_servers': parse_aaa_servers(config) + } + + +def map_params_to_obj(module): + if module.params['aaa_servers']: + for item in module.params['aaa_servers']: + if item['auth_key_type']: + item['auth_key_type'] = set(item['auth_key_type']) + obj = { + 'hostname': module.params['hostname'], + 'domain_name': module.params['domain_name'], + 'domain_search': module.params['domain_search'], + 'name_servers': module.params['name_servers'], + 'state': module.params['state'], + 'aaa_servers': module.params['aaa_servers'] + } + return obj + + +def main(): + """ Main entry point for Ansible module execution + """ + server_spec = dict( + type=dict(choices=['radius', 'tacacs']), + hostname=dict(), + auth_port_type=dict(choices=['auth-port']), + auth_port_num=dict(), + acct_port_num=dict(), + acct_type=dict(choices=['accounting-only', 'authentication-only', 'authorization-only', 'default']), + auth_key=dict(type='str', no_log=True), + auth_key_type=dict(type='list', choices=['dot1x', 'mac-auth', 'web-auth']) + ) + argument_spec = dict( + hostname=dict(), + + domain_name=dict(type='list'), + domain_search=dict(type='list'), + name_servers=dict(type='list'), + + aaa_servers=dict(type='list', elements='dict', options=server_spec), + state=dict(choices=['present', 'absent'], default='present'), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_user.py new file mode 100644 index 00000000..8cd5f6c0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_user.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_user +author: "Ruckus Wireless (@Commscope)" +short_description: Manage the user accounts on Ruckus ICX 7000 series switches. +description: + - This module creates or updates user account on network devices. It allows playbooks to manage + either individual usernames or the aggregate of usernames in the + current running config. It also supports purging usernames from the + configuration that are not explicitly defined. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + aggregate: + description: + - The set of username objects to be configured on the remote + ICX device. The list entries can either be the username + or a hash of username and properties. This argument is mutually + exclusive with the C(name) argument. + aliases: ['users', 'collection'] + type: list + suboptions: + name: + description: + - The username to be configured on the ICX device. + required: true + type: str + configured_password: + description: The password to be configured on the ICX device. + type: str + update_password: + description: + - This argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + choices: ['on_create', 'always'] + type: str + privilege: + description: + - The privilege level to be granted to the user + choices: ['0', '4', '5'] + type: str + nopassword: + description: + - Defines the username without assigning + a password. This will allow the user to login to the system + without being authenticated by a password. + type: bool + state: + description: + - Configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + choices: ['present', 'absent'] + type: str + access_time: + description: + - This parameter indicates the time the file's access time should be set to. + Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now. + Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + name: + description: + - The username to be configured on the ICX device. + required: true + type: str + configured_password: + description: The password to be configured on the ICX device. + type: str + update_password: + description: + - This argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + default: always + choices: ['on_create', 'always'] + type: str + privilege: + description: + - The privilege level to be granted to the user + default: 0 + choices: ['0', '4', '5'] + type: str + nopassword: + description: + - Defines the username without assigning + a password. This will allow the user to login to the system + without being authenticated by a password. + type: bool + default: false + purge: + description: + - If set to true module will remove any previously + configured usernames on the device except the current defined set of users. + type: bool + default: false + state: + description: + - Configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + default: present + choices: ['present', 'absent'] + type: str + access_time: + description: + - This parameter indicates the time the file's access time should be set to. + Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now. + Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Create a new user without password + community.network.icx_user: + name: user1 + nopassword: true + +- name: Create a new user with password + community.network.icx_user: + name: user1 + configured_password: 'newpassword' + +- name: Remove users + community.network.icx_user: + name: user1 + state: absent + +- name: Set user privilege level to 5 + community.network.icx_user: + name: user1 + privilege: 5 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - username ansible nopassword + - username ansible password-string alethea123 + - no username ansible + - username ansible privilege 5 + - username ansible enable +""" + +from copy import deepcopy + +import re +import base64 +import hashlib + +from functools import partial + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible.module_utils.connection import exec_command +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + + +def get_param_value(key, item, module): + if not item.get(key): + value = module.params[key] + + else: + value_type = module.argument_spec[key].get('type', 'str') + type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] + type_checker(item[key]) + value = item[key] + + validator = globals().get('validate_%s' % key) + if all((value, validator)): + validator(value, module) + + return value + + +def map_params_to_obj(module): + users = module.params['aggregate'] + if not users: + if not module.params['name'] and module.params['purge']: + return list() + elif not module.params['name']: + module.fail_json(msg='username is required') + else: + aggregate = [{'name': module.params['name']}] + else: + aggregate = list() + for item in users: + if not isinstance(item, dict): + aggregate.append({'name': item}) + elif 'name' not in item: + module.fail_json(msg='name is required') + else: + aggregate.append(item) + + objects = list() + + for item in aggregate: + get_value = partial(get_param_value, item=item, module=module) + item['configured_password'] = get_value('configured_password') + item['nopassword'] = get_value('nopassword') + item['privilege'] = get_value('privilege') + item['state'] = get_value('state') + objects.append(item) + + return objects + + +def parse_privilege(data): + match = re.search(r'privilege (\S)', data, re.M) + if match: + return match.group(1) + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + data = get_config(module, flags=['| include username'], compare=compare) + + match = re.findall(r'(?:^(?:u|\s{2}u))sername (\S+)', data, re.M) + if not match: + return list() + + instances = list() + + for user in set(match): + regex = r'username %s .+$' % user + cfg = re.findall(regex, data, re.M) + cfg = '\n'.join(cfg) + obj = { + 'name': user, + 'state': 'present', + 'nopassword': 'nopassword' in cfg, + 'configured_password': None, + 'privilege': parse_privilege(cfg) + } + instances.append(obj) + + return instances + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + update_password = module.params['update_password'] + + def needs_update(want, have, x): + return want.get(x) and (want.get(x) != have.get(x)) + + def add(command, want, x): + command.append('username %s %s' % (want['name'], x)) + for update in updates: + want, have = update + if want['state'] == 'absent': + commands.append(user_del_cmd(want['name'])) + + if needs_update(want, have, 'privilege'): + add(commands, want, 'privilege %s password %s' % (want['privilege'], want['configured_password'])) + else: + if needs_update(want, have, 'configured_password'): + if update_password == 'always' or not have: + add(commands, want, '%spassword %s' % ('privilege ' + str(have.get('privilege')) + + " " if have.get('privilege') is not None else '', want['configured_password'])) + + if needs_update(want, have, 'nopassword'): + if want['nopassword']: + add(commands, want, 'nopassword') + + if needs_update(want, have, 'access_time'): + add(commands, want, 'access-time %s' % want['access_time']) + + if needs_update(want, have, 'expiry_days'): + add(commands, want, 'expires %s' % want['expiry_days']) + + return commands + + +def update_objects(want, have): + updates = list() + for entry in want: + item = next((i for i in have if i['name'] == entry['name']), None) + + if all((item is None, entry['state'] == 'present')): + updates.append((entry, {})) + + elif all((have == [], entry['state'] == 'absent')): + for key, value in iteritems(entry): + if key not in ['update_password']: + updates.append((entry, item)) + break + elif item: + for key, value in iteritems(entry): + if key not in ['update_password']: + if value is not None and value != item.get(key): + updates.append((entry, item)) + break + return updates + + +def user_del_cmd(username): + return 'no username %s' % username + + +def main(): + """entry point for module execution + """ + element_spec = dict( + name=dict(), + + configured_password=dict(no_log=True), + nopassword=dict(type='bool', default=False), + update_password=dict(default='always', choices=['on_create', 'always']), + privilege=dict(type='str', choices=['0', '4', '5']), + access_time=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users', 'collection']), + purge=dict(type='bool', default=False) + ) + + argument_spec.update(element_spec) + + mutually_exclusive = [('name', 'aggregate')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + result = {'changed': False} + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands(update_objects(want, have), module) + + if module.params['purge']: + want_users = [x['name'] for x in want] + have_users = [x['name'] for x in have] + for item in set(have_users).difference(want_users): + if item != 'admin': + commands.append(user_del_cmd(item)) + + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_vlan.py new file mode 100644 index 00000000..4c102705 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/icx_vlan.py @@ -0,0 +1,779 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_vlan +author: "Ruckus Wireless (@Commscope)" +short_description: Manage VLANs on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of VLANs + on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: int + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + delay: + description: + - Delay the play should wait to check for declarative intent params values. + default: 10 + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specify the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + aggregate: + description: + - List of VLANs definitions. + type: list + suboptions: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: str + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + delay: + description: + - Delay the play should wait to check for declarative intent params values. + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specify the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + state: + description: + - State of the VLAN configuration. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the VLAN configuration. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Add a single ethernet 1/1/48 as access(untagged) port to vlan 20 + community.network.icx_vlan: + name: test-vlan + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/48 + +- name: Add a single LAG 10 as access(untagged) port to vlan 20 + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - lag 10 + +- name: Add a range of ethernet ports as trunk(tagged) ports to vlan 20 by port + community.network.icx_vlan: + vlan_id: 20 + tagged: + name: + - ethernet 1/1/40 to 1/1/48 + +- name: Add discontinuous lags, ethernet ports as access(untagged) and trunk(tagged) port to vlan 20. + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 to 1/1/48 + - ethernet 2/1/1 + - lag 1 + - lag 3 to 5 + tagged: + name: + - ethernet 1/1/20 to 1/1/25 + - lag 1 to 3 + +- name: Remove an access and range of trunk ports from vlan + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 + tagged: + name: + - ethernet 1/1/39 to 1/1/70 + +- name: Enable dhcp snooping, disable arp inspection in vlan + community.network.icx_vlan: + vlan_id: 20 + ip_dhcp_snooping: present + ip_arp_inspection: absent + +- name: Create vlan 20. Enable arp inspection in vlan. Purge all other vlans. + community.network.icx_vlan: + vlan_id: 20 + ip_arp_inspection: present + purge: present + +- name: Remove vlan 20. + community.network.icx_vlan: + vlan_id: 20 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +from time import sleep +import itertools +from copy import deepcopy +from time import sleep +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def search_obj_in_list(vlan_id, lst): + obj = list() + for o in lst: + if str(o['vlan_id']) == vlan_id: + return o + + +def parse_vlan_brief(module, vlan_id): + command = 'show run vlan %s' % vlan_id + rc, out, err = exec_command(module, command) + lines = out.split('\n') + untagged_ports = list() + untagged_lags = list() + tagged_ports = list() + tagged_lags = list() + + for line in lines: + if 'tagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + tagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + tagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + tagged_lags.append((int(l[0]) + i)) + else: + tagged_lags.append(int(lag)) + if 'untagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + untagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + untagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + untagged_lags.append((int(l[0]) + i)) + else: + untagged_lags.append(int(lag)) + + return untagged_ports, untagged_lags, tagged_ports, tagged_lags + + +def extract_list_from_interface(interface): + if 'ethernet' in interface: + if 'to' in interface: + s = re.search(r"\d+\/\d+/(?P\d+)\sto\s+\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + elif 'lag' in interface: + if 'to' in interface: + s = re.search(r"(?P\d+)\sto\s(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + + return low, high + + +def parse_vlan_id(module): + vlans = [] + command = 'show vlan brief' + rc, out, err = exec_command(module, command) + lines = out.split('\n') + for line in lines: + if 'VLANs Configured :' in line: + values = line.split(':')[1] + vlans = [s for s in values.split() if s.isdigit()] + s = re.findall(r"(?P\d+)\sto\s(?P\d+)", values) + for ranges in s: + low = int(ranges[0]) + 1 + high = int(ranges[1]) + while(high > low): + vlans.append(str(low)) + low = low + 1 + return vlans + + +def spanning_tree(module, stp): + stp_cmd = list() + if stp.get('enabled') is False: + if stp.get('type') == '802-1w': + stp_cmd.append('no spanning-tree' + ' ' + stp.get('type')) + stp_cmd.append('no spanning-tree') + + elif stp.get('type'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type')) + if stp.get('priority') and stp.get('type') == 'rstp': + module.fail_json(msg='spanning-tree 802-1w only can have priority') + elif stp.get('priority'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type') + ' ' + 'priority' + ' ' + stp.get('priority')) + + return stp_cmd + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + stp = item.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + item.update({'stp': stp_cmd}) + + d = item.copy() + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'vlan_id': module.params['vlan_id'], + 'interfaces': module.params['interfaces'], + 'tagged': module.params['tagged'], + 'associated_interfaces': module.params['associated_interfaces'], + 'associated_tagged': module.params['associated_tagged'], + 'delay': module.params['delay'], + 'ip_dhcp_snooping': module.params['ip_dhcp_snooping'], + 'ip_arp_inspection': module.params['ip_arp_inspection'], + 'state': module.params['state'], + } + + stp = module.params.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + params.update({'stp': stp_cmd}) + + obj.append(params) + + return obj + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + state = w['state'] + name = w['name'] + interfaces = w.get('interfaces') + tagged = w.get('tagged') + dhcp = w.get('ip_dhcp_snooping') + arp = w.get('ip_arp_inspection') + stp = w.get('stp') + obj_in_have = search_obj_in_list(str(vlan_id), have) + + if state == 'absent': + if have == []: + commands.append('no vlan {0}'.format(vlan_id)) + if obj_in_have: + commands.append('no vlan {0}'.format(vlan_id)) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + for item in interfaces['name']: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + for item in tagged['name']: + commands.append('tagged {0}'.format(item)) + + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + else: + commands.append('vlan {0}'.format(vlan_id)) + if name: + if name != obj_in_have['name']: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + have_interfaces = list() + for interface in interfaces['name']: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + have_interfaces.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in interface: + have_interfaces.append('lag {0}'.format(low)) + low = low + 1 + + if interfaces['purge'] is True: + remove_interfaces = list(set(obj_in_have['interfaces']) - set(have_interfaces)) + for item in remove_interfaces: + commands.append('no untagged {0}'.format(item)) + + if interfaces['name']: + add_interfaces = list(set(have_interfaces) - set(obj_in_have['interfaces'])) + for item in add_interfaces: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + have_tagged = list() + for tag in tagged['name']: + low, high = extract_list_from_interface(tag) + + while(high >= low): + if 'ethernet' in tag: + have_tagged.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in tag: + have_tagged.append('lag {0}'.format(low)) + low = low + 1 + if tagged['purge'] is True: + remove_tagged = list(set(obj_in_have['tagged']) - set(have_tagged)) + for item in remove_tagged: + commands.append('no tagged {0}'.format(item)) + + if tagged['name']: + add_tagged = list(set(have_tagged) - set(obj_in_have['tagged'])) + for item in add_tagged: + commands.append('tagged {0}'.format(item)) + + if dhcp != obj_in_have['ip_dhcp_snooping']: + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp != obj_in_have['ip_arp_inspection']: + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif arp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + if len(commands) == 1 and 'vlan ' + str(vlan_id) in commands: + commands = [] + + if purge: + commands = [] + vlans = parse_vlan_id(module) + for h in vlans: + obj_in_want = search_obj_in_list(h, want) + if not obj_in_want and h != '1': + commands.append('no vlan {0}'.format(h)) + + return commands + + +def parse_name_argument(module, item): + command = 'show vlan {0}'.format(item) + rc, out, err = exec_command(module, command) + match = re.search(r"Name (\S+),", out) + if match: + return match.group(1) + + +def parse_interfaces_argument(module, item, port_type): + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, item) + ports = list() + if port_type == "interfaces": + if untagged_ports: + for port in untagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if untagged_lags: + for port in untagged_lags: + ports.append('lag ' + str(port)) + + elif port_type == "tagged": + if tagged_ports: + for port in tagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if tagged_lags: + for port in tagged_lags: + ports.append('lag ' + str(port)) + + return ports + + +def parse_config_argument(config, arg): + match = re.search(arg, config, re.M) + if match: + return True + else: + return False + + +def map_config_to_obj(module): + config = get_config(module) + vlans = parse_vlan_id(module) + instance = list() + + for item in set(vlans): + obj = { + 'vlan_id': item, + 'name': parse_name_argument(module, item), + 'interfaces': parse_interfaces_argument(module, item, 'interfaces'), + 'tagged': parse_interfaces_argument(module, item, 'tagged'), + 'ip_dhcp_snooping': parse_config_argument(config, 'ip dhcp snooping vlan {0}'.format(item)), + 'ip_arp_inspection': parse_config_argument(config, 'ip arp inspection vlan {0}'.format(item)), + } + instance.append(obj) + return instance + + +def check_fail(module, output): + error = [ + re.compile(r"^error", re.I) + ] + for x in output: + for regex in error: + if regex.search(x): + module.fail_json(msg=x) + + +def check_declarative_intent_params(want, module, result): + def parse_ports(interfaces, ports, lags): + for interface in interfaces: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + if not (low in ports): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + if 'lag' in interface: + if not (low in lags): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + low = low + 1 + + is_delay = False + low = 0 + high = 0 + for w in want: + if w.get('associated_interfaces') is None and w.get('associated_tagged') is None: + continue + + if result['changed'] and not is_delay: + sleep(module.params['delay']) + is_delay = True + + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, w['vlan_id']) + + if w['associated_interfaces']: + parse_ports(w.get('associated_interfaces'), untagged_ports, untagged_lags) + + if w['associated_tagged']: + parse_ports(w.get('associated_tagged'), tagged_ports, tagged_lags) + + +def main(): + """ main entry point for module execution + """ + stp_spec = dict( + type=dict(default='802-1w', choices=['802-1w', 'rstp']), + priority=dict(), + enabled=dict(type='bool'), + ) + inter_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + tagged_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='dict', options=inter_spec), + tagged=dict(type='dict', options=tagged_spec), + ip_dhcp_snooping=dict(type='bool'), + ip_arp_inspection=dict(type='bool'), + associated_interfaces=dict(type='list'), + associated_tagged=dict(type='list'), + delay=dict(default=10, type='int'), + stp=dict(type='dict', options=stp_spec), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + argument_spec.update(element_spec) + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {} + result['changed'] = False + if warnings: + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + if module.params['check_running_config'] is False: + have = [] + else: + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + output = load_config(module, commands) + if output: + check_fail(module, output) + result['output'] = output + result['changed'] = True + + check_declarative_intent_params(want, module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_config.py new file mode 100644 index 00000000..0abc5ec7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_config.py @@ -0,0 +1,564 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2018, Ingate Systems AB +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ig_config +short_description: Manage the configuration database on an Ingate SBC. +description: + - Manage the configuration database on an Ingate SBC. +extends_documentation_fragment: +- community.network.ingate + +options: + add: + description: + - Add a row to a table. + type: bool + delete: + description: + - Delete all rows in a table or a specific row. + type: bool + get: + description: + - Return all rows in a table or a specific row. + type: bool + modify: + description: + - Modify a row in a table. + type: bool + revert: + description: + - Reset the preliminary configuration. + type: bool + factory: + description: + - Reset the preliminary configuration to its factory defaults. + type: bool + store: + description: + - Store the preliminary configuration. + type: bool + no_response: + description: + - Expect no response when storing the preliminary configuration. + Refer to the C(store) option. + type: bool + default: false + return_rowid: + description: + - Get rowid(s) from a table where the columns match. + type: bool + download: + description: + - Download the configuration database from the unit. + type: bool + store_download: + description: + - If the downloaded configuration should be stored on disk. + Refer to the C(download) option. + type: bool + default: false + path: + description: + - Where in the filesystem to store the downloaded configuration. + Refer to the C(download) option. + filename: + description: + - The name of the file to store the downloaded configuration in. + Refer to the C(download) option. + table: + description: + - The name of the table. + rowid: + description: + - A row id. + type: int + columns: + description: + - A dict containing column names/values. +notes: + - If C(store_download) is set to True, and C(path) and C(filename) is omitted, + the file will be stored in the current directory with an automatic filename. +author: + - Ingate Systems AB (@ingatesystems) +''' + +EXAMPLES = ''' +- name: Add/remove DNS servers + hosts: 192.168.1.1 + connection: local + vars: + client_rw: + version: v1 + address: "{{ inventory_hostname }}" + scheme: http + username: alice + password: foobar + tasks: + + - name: Load factory defaults + community.network.ig_config: + client: "{{ client_rw }}" + factory: true + register: result + - ansible.builtin.debug: + var: result + + - name: Revert to last known applied configuration + community.network.ig_config: + client: "{{ client_rw }}" + revert: true + register: result + - ansible.builtin.debug: + var: result + + - name: Change the unit name + community.network.ig_config: + client: "{{ client_rw }}" + modify: true + table: misc.unitname + columns: + unitname: "Test Ansible" + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.21 + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.22 + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.23 + register: last_dns + - ansible.builtin.debug: + var: last_dns + + - name: Modify the last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + modify: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + columns: + server: 192.168.1.24 + register: result + - ansible.builtin.debug: + var: result + + - name: Return the last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + get: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + register: result + - ansible.builtin.debug: + var: result + + - name: Remove last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + delete: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + register: result + - ansible.builtin.debug: + var: result + + - name: Return the all rows from table misc.dns_servers + community.network.ig_config: + client: "{{ client_rw }}" + get: true + table: misc.dns_servers + register: result + - ansible.builtin.debug: + var: result + + - name: Remove remaining DNS servers + community.network.ig_config: + client: "{{ client_rw }}" + delete: true + table: misc.dns_servers + register: result + - ansible.builtin.debug: + var: result + + - name: Get rowid for interface eth0 + community.network.ig_config: + client: "{{ client_rw }}" + return_rowid: true + table: network.local_nets + columns: + interface: eth0 + register: result + - ansible.builtin.debug: + var: result + + - name: Store the preliminary configuration + community.network.ig_config: + client: "{{ client_rw }}" + store: true + register: result + - ansible.builtin.debug: + var: result + + - name: Do backup of the configuration database + community.network.ig_config: + client: "{{ client_rw }}" + download: true + store_download: true + register: result + - ansible.builtin.debug: + var: result +''' + +RETURN = ''' +add: + description: A list containing information about the added row + returned: when C(add) is yes and success + type: complex + contains: + href: + description: The REST API URL to the added row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/2 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 22 +delete: + description: A list containing information about the deleted row(s) + returned: when C(delete) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: misc.dns_servers + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 22 +get: + description: A list containing information about the row(s) + returned: when C(get) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: Testname + href: + description: The REST API URL to the row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/1 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 1 +modify: + description: A list containing information about the modified row + returned: when C(modify) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: Testname + href: + description: The REST API URL to the modified row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/1 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 10 +revert: + description: A command status message + returned: when C(revert) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: reverted the configuration to the last applied configuration. +factory: + description: A command status message + returned: when C(factory) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: reverted the configuration to the factory configuration. +store: + description: A command status message + returned: when C(store) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: Successfully applied and saved the configuration. +return_rowid: + description: The matched row id(s). + returned: when C(return_rowid) is yes and success + type: list + sample: [1, 3] +download: + description: Configuration database and meta data + returned: when C(download) is yes and success + type: complex + contains: + config: + description: The configuration database + returned: success + type: str + filename: + description: A suggested name for the configuration + returned: success + type: str + sample: testname_2018-10-01T214040.cfg + mimetype: + description: The mimetype + returned: success + type: str + sample: application/x-config-database +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ingate.common import (ingate_argument_spec, + ingate_create_client) + +try: + from ingate import ingatesdk + HAS_INGATESDK = True +except ImportError: + HAS_INGATESDK = False + + +def make_request(module): + # Create client and authenticate. + api_client = ingate_create_client(**module.params) + + if module.params.get('add'): + # Add a row to a table. + table = module.params['table'] + columns = module.params['columns'] + response = api_client.add_row(table, **columns) + return True, 'add', response + elif module.params.get('delete'): + # Delete a row/table. + changed = False + table = module.params['table'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.delete_row(table, rowid=rowid) + else: + response = api_client.delete_table(table) + if response: + changed = True + return changed, 'delete', response + elif module.params.get('get'): + # Get the contents of a table/row. + table = module.params['table'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.dump_row(table, rowid=rowid) + else: + response = api_client.dump_table(table) + if response: + changed = True + return changed, 'get', response + elif module.params.get('modify'): + # Modify a table row. + table = module.params['table'] + columns = module.params['columns'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.modify_row(table, rowid=rowid, **columns) + else: + response = api_client.modify_single_row(table, **columns) + if response: + changed = True + return changed, 'modify', response + elif module.params.get('revert'): + # Revert edits. + response = api_client.revert_edits() + if response: + response = response[0]['revert-edits'] + return True, 'revert', response + elif module.params.get('factory'): + # Load factory defaults. + response = api_client.load_factory() + if response: + response = response[0]['load-factory'] + return True, 'factory', response + elif module.params.get('store'): + # Store edit. + no_response = module.params.get('no_response') + response = api_client.store_edit(no_response=no_response) + if response: + response = response[0]['store-edit'] + return True, 'store', response + elif module.params.get('return_rowid'): + # Find matching rowid(s) in a table. + table = module.params['table'] + columns = module.params['columns'] + response = api_client.dump_table(table) + rowids = [] + for row in response: + match = False + for (name, value) in columns.items(): + if name not in row['data']: + continue + if not row['data'][name] == value: + match = False + break + else: + match = True + if match: + rowids.append(row['id']) + return False, 'return_rowid', rowids + elif module.params.get('download'): + # Download the configuration database. + store = module.params.get('store_download') + path = module.params.get('path') + filename = module.params.get('filename') + response = api_client.download_config(store=store, path=path, + filename=filename) + if response: + response = response[0]['download-config'] + return False, 'download', response + return False, '', {} + + +def main(): + argument_spec = ingate_argument_spec( + add=dict(type='bool'), + delete=dict(type='bool'), + get=dict(type='bool'), + modify=dict(type='bool'), + revert=dict(type='bool'), + factory=dict(type='bool'), + store=dict(type='bool'), + no_response=dict(type='bool', default=False), + return_rowid=dict(type='bool'), + download=dict(type='bool'), + store_download=dict(type='bool', default=False), + path=dict(), + filename=dict(), + table=dict(), + rowid=dict(type='int'), + columns=dict(type='dict'), + ) + + mutually_exclusive = [('add', 'delete', 'get', 'modify', 'revert', + 'factory', 'store', 'return_rowid', 'download')] + required_one_of = [['add', 'delete', 'get', 'modify', 'revert', 'factory', + 'store', 'return_rowid', 'download']] + required_if = [('add', True, ['table', 'columns']), + ('delete', True, ['table']), + ('get', True, ['table']), + ('modify', True, ['table', 'columns']), + ('return_rowid', True, ['table', 'columns'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + required_one_of=required_one_of, + supports_check_mode=False) + if not HAS_INGATESDK: + module.fail_json(msg='The Ingate Python SDK module is required') + + result = dict(changed=False) + try: + changed, command, response = make_request(module) + if response and command: + result[command] = response + result['changed'] = changed + except ingatesdk.SdkError as e: + module.fail_json(msg=str(e)) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_unit_information.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_unit_information.py new file mode 100644 index 00000000..5fb2bbfd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ig_unit_information.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ingate Systems AB +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ig_unit_information +short_description: Get unit information from an Ingate SBC. +description: + - Get unit information from an Ingate SBC. +extends_documentation_fragment: +- community.network.ingate + +author: + - Ingate Systems AB (@ingatesystems) +''' + +EXAMPLES = ''' +- name: Get unit information + community.network.ig_unit_information: + client: + version: v1 + scheme: http + address: 192.168.1.1 + username: alice + password: foobar +''' + +RETURN = ''' +unit-information: + description: Information about the unit + returned: success + type: complex + contains: + installid: + description: The installation identifier + returned: success + type: str + sample: any + interfaces: + description: List of interface names + returned: success + type: str + sample: eth0 eth1 eth2 eth3 eth4 eth5 + lang: + description: The unit's language + returned: success + type: str + sample: en + lic_email: + description: License email information + returned: success + type: str + sample: example@example.com + lic_mac: + description: License MAC information + returned: success + type: str + sample: any + lic_name: + description: License name information + returned: success + type: str + sample: Example Inc + macaddr: + description: The MAC address of the first interface + returned: success + type: str + sample: 52:54:00:4c:e2:07 + mode: + description: Operational mode of the unit + returned: success + type: str + sample: Siparator + modules: + description: Installed module licenses + returned: success + type: str + sample: failover vpn sip qturn ems qos rsc voipsm + patches: + description: Installed patches on the unit + returned: success + type: list + sample: [] + product: + description: The product name + returned: success + type: str + sample: Software SIParator/Firewall + serial: + description: The serial number of the unit + returned: success + type: str + sample: IG-200-839-2008-0 + systemid: + description: The system identifier of the unit + returned: success + type: str + sample: IG-200-839-2008-0 + unitname: + description: The name of the unit + returned: success + type: str + sample: Testname + version: + description: Firmware version + returned: success + type: str + sample: 6.2.0-beta2 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.network.plugins.module_utils.network.ingate.common import (ingate_argument_spec, + ingate_create_client, + is_ingatesdk_installed) + +try: + from ingate import ingatesdk +except ImportError: + pass + + +def make_request(module): + # Create client and authenticate. + api_client = ingate_create_client(**module.params) + + # Get unit information. + response = api_client.unit_information() + return response + + +def main(): + argument_spec = ingate_argument_spec() + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + + is_ingatesdk_installed(module) + + result = dict(changed=False) + try: + response = make_request(module) + result.update(response[0]) + except ingatesdk.SdkError as e: + module.fail_json(msg=to_native(e)) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addr.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addr.py new file mode 100644 index 00000000..8ce3fbf1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addr.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_addr +short_description: Manage IP addresses on an interface on Solaris/illumos systems +description: + - Create/delete static/dynamic IP addresses on network interfaces on Solaris/illumos systems. + - Up/down static/dynamic IP addresses on network interfaces on Solaris/illumos systems. + - Manage IPv6 link-local addresses on network interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + address: + description: + - Specifiies an IP address to configure in CIDR notation. + required: false + aliases: [ "addr" ] + addrtype: + description: + - Specifiies a type of IP address to configure. + required: false + default: static + choices: [ 'static', 'dhcp', 'addrconf' ] + addrobj: + description: + - Specifies an unique IP address on the system. + required: true + temporary: + description: + - Specifies that the configured IP address is temporary. Temporary + IP addresses do not persist across reboots. + required: false + default: false + type: bool + wait: + description: + - Specifies the time in seconds we wait for obtaining address via DHCP. + required: false + default: 60 + state: + description: + - Create/delete/enable/disable an IP address on the network interface. + required: false + default: present + choices: [ 'absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed' ] +''' + +EXAMPLES = ''' +- name: Configure IP address 10.0.0.1 on e1000g0 + community.network.ipadm_addr: addr=10.0.0.1/32 addrobj=e1000g0/v4 state=present + +- name: Delete addrobj + community.network.ipadm_addr: addrobj=e1000g0/v4 state=absent + +- name: Configure link-local IPv6 address + community.network.ipadm_addr: addtype=addrconf addrobj=vnic0/v6 + +- name: Configure address via DHCP and wait 180 seconds for address obtaining + community.network.ipadm_addr: addrobj=vnic0/dhcp addrtype=dhcp wait=180 +''' + +RETURN = ''' +addrobj: + description: address object name + returned: always + type: str + sample: bge0/v4 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +addrtype: + description: address type + returned: always + type: str + sample: static +address: + description: IP address + returned: only if addrtype is 'static' + type: str + sample: 1.3.3.7/32 +wait: + description: time we wait for DHCP + returned: only if addrtype is 'dhcp' + type: str + sample: 10 +''' + +import socket + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TYPES = ['static', 'addrconf', 'dhcp'] + + +class Addr(object): + + def __init__(self, module): + self.module = module + + self.address = module.params['address'] + self.addrtype = module.params['addrtype'] + self.addrobj = module.params['addrobj'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + self.wait = module.params['wait'] + + def is_cidr_notation(self): + + return self.address.count('/') == 1 + + def is_valid_address(self): + + ip_address = self.address.split('/')[0] + + try: + if len(ip_address.split('.')) == 4: + socket.inet_pton(socket.AF_INET, ip_address) + else: + socket.inet_pton(socket.AF_INET6, ip_address) + except socket.error: + return False + + return True + + def is_dhcp(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addr') + cmd.append('-p') + cmd.append('-o') + cmd.append('type') + cmd.append(self.addrobj) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + if out.rstrip() != 'dhcp': + return False + + return True + else: + self.module.fail_json(msg='Wrong addrtype %s for addrobj "%s": %s' % (out, self.addrobj, err), + rc=rc, + stderr=err) + + def addrobj_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addr') + cmd.append(self.addrobj) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def delete_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('delete-addr') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def create_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('create-addr') + cmd.append('-T') + cmd.append(self.addrtype) + + if self.temporary: + cmd.append('-t') + + if self.addrtype == 'static': + cmd.append('-a') + cmd.append(self.address) + + if self.addrtype == 'dhcp' and self.wait: + cmd.append('-w') + cmd.append(self.wait) + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def up_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('up-addr') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def down_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('down-addr') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def enable_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('enable-addr') + cmd.append('-t') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def disable_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('disable-addr') + cmd.append('-t') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def refresh_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('refresh-addr') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + address=dict(aliases=['addr']), + addrtype=dict(default='static', choices=SUPPORTED_TYPES), + addrobj=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed']), + wait=dict(default=60, type='int'), + ), + mutually_exclusive=[ + ('address', 'wait'), + ], + supports_check_mode=True + ) + + addr = Addr(module) + + rc = None + out = '' + err = '' + result = {} + result['addrobj'] = addr.addrobj + result['state'] = addr.state + result['temporary'] = addr.temporary + result['addrtype'] = addr.addrtype + + if addr.addrtype == 'static' and addr.address: + if addr.is_cidr_notation() and addr.is_valid_address(): + result['address'] = addr.address + else: + module.fail_json(msg='Invalid IP address: %s' % addr.address) + + if addr.addrtype == 'dhcp' and addr.wait: + result['wait'] = addr.wait + + if addr.state == 'absent': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.delete_addr() + if rc != 0: + module.fail_json(msg='Error while deleting addrobj: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'present': + if not addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.create_addr() + if rc != 0: + module.fail_json(msg='Error while configuring IP address: "%s"' % err, + addrobj=addr.addrobj, + addr=addr.address, + stderr=err, + rc=rc) + + elif addr.state == 'up': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.up_addr() + if rc != 0: + module.fail_json(msg='Error while bringing IP address up: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'down': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.down_addr() + if rc != 0: + module.fail_json(msg='Error while bringing IP address down: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'refreshed': + if addr.addrobj_exists(): + if addr.is_dhcp(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.refresh_addr() + if rc != 0: + module.fail_json(msg='Error while refreshing IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + else: + module.fail_json(msg='state "refreshed" cannot be used with "%s" addrtype' % addr.addrtype, + addrobj=addr.addrobj, + stderr=err, + rc=1) + + elif addr.state == 'enabled': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.enable_addr() + if rc != 0: + module.fail_json(msg='Error while enabling IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'disabled': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.disable_addr() + if rc != 0: + module.fail_json(msg='Error while disabling IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addrprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addrprop.py new file mode 100644 index 00000000..94895c39 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_addrprop.py @@ -0,0 +1,254 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_addrprop +short_description: Manage IP address properties on Solaris/illumos systems. +description: + - Modify IP address properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + addrobj: + description: + - Specifies the address object we want to manage. + required: true + aliases: [nic, interface] + property: + description: + - Specifies the name of the address property we want to manage. + required: true + aliases: [name] + value: + description: + - Specifies the value we want to set for the address property. + required: false + temporary: + description: + - Specifies that the address property value is temporary. + Temporary values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Mark address on addrobj as deprecated + community.network.ipadm_addrprop: property=deprecated value=on addrobj=e1000g0/v6 + +- name: Set network prefix length for addrobj + community.network.ipadm_addrprop: addrobj=bge0/v4 name=prefixlen value=26 +''' + +RETURN = ''' +property: + description: property name + returned: always + type: str + sample: deprecated +addrobj: + description: address object name + returned: always + type: str + sample: bge0/v4 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +value: + description: property value + returned: when value is provided + type: str + sample: 26 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class AddrProp(object): + + def __init__(self, module): + self.module = module + + self.addrobj = module.params['addrobj'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" on addrobj %s' % + (self.property, self.addrobj), + property=self.property, + addrobj=self.addrobj) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-addrprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + '=' + self.value) + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-addrprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + addrobj=dict(required=True, default=None, aliases=['nic', 'interface']), + property=dict(required=True, aliases=['name']), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + addrprop = AddrProp(module) + + rc = None + out = '' + err = '' + result = {} + result['property'] = addrprop.property + result['addrobj'] = addrprop.addrobj + result['state'] = addrprop.state + result['temporary'] = addrprop.temporary + if addrprop.value: + result['value'] = addrprop.value + + if addrprop.state == 'absent' or addrprop.state == 'reset': + if addrprop.property_exists(): + if not addrprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = addrprop.reset_property() + if rc != 0: + module.fail_json(property=addrprop.property, + addrobj=addrprop.addrobj, + msg=err, + rc=rc) + + elif addrprop.state == 'present': + if addrprop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if addrprop.property_exists(): + if not addrprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addrprop.set_property() + if rc != 0: + module.fail_json(property=addrprop.property, + addrobj=addrprop.addrobj, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_if.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_if.py new file mode 100644 index 00000000..7d4ac2aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_if.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_if +short_description: Manage IP interfaces on Solaris/illumos systems. +description: + - Create, delete, enable or disable IP interfaces on Solaris/illumos + systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - IP interface name. + required: true + temporary: + description: + - Specifies that the IP interface is temporary. Temporary IP + interfaces do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create or delete Solaris/illumos IP interfaces. + required: false + default: "present" + choices: [ "present", "absent", "enabled", "disabled" ] +''' + +EXAMPLES = ''' +- name: Create vnic0 interface + community.network.ipadm_if: + name: vnic0 + state: enabled + +- name: Disable vnic0 interface + community.network.ipadm_if: + name: vnic0 + state: disabled +''' + +RETURN = ''' +name: + description: IP interface name + returned: always + type: str + sample: "vnic0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: persistence of a IP interface + returned: always + type: bool + sample: "True" +''' +from ansible.module_utils.basic import AnsibleModule + + +class IPInterface(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def interface_exists(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + if rc == 0: + return True + else: + return False + + def interface_is_disabled(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append('-o') + cmd.append('state') + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + if rc != 0: + self.module.fail_json(name=self.name, rc=rc, msg=err) + + return 'disabled' in out + + def create_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('create-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('delete-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def enable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('enable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def disable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('disable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', + 'present', + 'enabled', + 'disabled']), + ), + supports_check_mode=True + ) + + interface = IPInterface(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = interface.name + result['state'] = interface.state + result['temporary'] = interface.temporary + + if interface.state == 'absent': + if interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.delete_interface() + if rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + elif interface.state == 'present': + if not interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.create_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'enabled': + if interface.interface_is_disabled(): + (rc, out, err) = interface.enable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'disabled': + if not interface.interface_is_disabled(): + (rc, out, err) = interface.disable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_ifprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_ifprop.py new file mode 100644 index 00000000..8707516c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_ifprop.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_ifprop +short_description: Manage IP interface properties on Solaris/illumos systems. +description: + - Modify IP interface properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + interface: + description: + - Specifies the IP interface we want to manage. + required: true + aliases: [nic] + protocol: + description: + - Specifies the protocol for which we want to manage properties. + required: true + property: + description: + - Specifies the name of the property we want to manage. + required: true + aliases: [name] + value: + description: + - Specifies the value we want to set for the property. + required: false + temporary: + description: + - Specifies that the property value is temporary. Temporary + property values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Allow forwarding of IPv4 packets on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv4 property=forwarding value=on interface=e1000g0 + +- name: Temporarily reset IPv4 forwarding property on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv4 interface=e1000g0 temporary=true property=forwarding state=reset + +- name: Configure IPv6 metric on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv6 nic=e1000g0 name=metric value=100 + +- name: Set IPv6 MTU on network interface bge0 + community.network.ipadm_ifprop: interface=bge0 name=mtu value=1280 protocol=ipv6 +''' + +RETURN = ''' +protocol: + description: property's protocol + returned: always + type: str + sample: ipv4 +property: + description: property's name + returned: always + type: str + sample: mtu +interface: + description: interface name we want to set property on + returned: always + type: str + sample: e1000g0 +state: + description: state of the target + returned: always + type: str + sample: present +value: + description: property's value + returned: when value is provided + type: str + sample: 1280 +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6'] + + +class IfProp(object): + + def __init__(self, module): + self.module = module + + self.interface = module.params['interface'] + self.protocol = module.params['protocol'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown %s property "%s" on IP interface %s' % + (self.protocol, self.property, self.interface), + protocol=self.protocol, + property=self.property, + interface=self.interface) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-ifprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + "=" + self.value) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-ifprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), + property=dict(required=True, aliases=['name']), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + interface=dict(required=True, default=None, aliases=['nic']), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + ifprop = IfProp(module) + + rc = None + out = '' + err = '' + result = {} + result['protocol'] = ifprop.protocol + result['property'] = ifprop.property + result['interface'] = ifprop.interface + result['state'] = ifprop.state + if ifprop.value: + result['value'] = ifprop.value + + if ifprop.state == 'absent' or ifprop.state == 'reset': + if ifprop.property_exists(): + if not ifprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = ifprop.reset_property() + if rc != 0: + module.fail_json(protocol=ifprop.protocol, + property=ifprop.property, + interface=ifprop.interface, + msg=err, + rc=rc) + + elif ifprop.state == 'present': + if ifprop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if ifprop.property_exists(): + if not ifprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = ifprop.set_property() + if rc != 0: + module.fail_json(protocol=ifprop.protocol, + property=ifprop.property, + interface=ifprop.interface, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_prop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_prop.py new file mode 100644 index 00000000..cfd01863 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ipadm_prop.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_prop +short_description: Manage protocol properties on Solaris/illumos systems. +description: + - Modify protocol properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + protocol: + description: + - Specifies the protocol for which we want to manage properties. + required: true + property: + description: + - Specifies the name of property we want to manage. + required: true + value: + description: + - Specifies the value we want to set for the property. + required: false + temporary: + description: + - Specifies that the property value is temporary. Temporary + property values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Set TCP receive buffer size + community.network.ipadm_prop: + protocol: tcp + property: recv_buf + value: 65536 + +- name: Reset UDP send buffer size to the default value + community.network.ipadm_prop: + protocol: udp + property: send_buf + state: reset +''' + +RETURN = ''' +protocol: + description: property's protocol + returned: always + type: str + sample: "TCP" +property: + description: name of the property + returned: always + type: str + sample: "recv_maxbuf" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: property's persistence + returned: always + type: bool + sample: "True" +value: + description: value of the property. May be int or string depending on property. + returned: always + type: int + sample: "'1024' or 'never'" +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6', 'icmp', 'tcp', 'udp', 'sctp'] + + +class Prop(object): + + def __init__(self, module): + self.module = module + + self.protocol = module.params['protocol'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" for protocol %s' % + (self.property, self.protocol), + protocol=self.protocol, + property=self.property) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + "=" + self.value) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), + property=dict(required=True), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + prop = Prop(module) + + rc = None + out = '' + err = '' + result = {} + result['protocol'] = prop.protocol + result['property'] = prop.property + result['state'] = prop.state + result['temporary'] = prop.temporary + if prop.value: + result['value'] = prop.value + + if prop.state == 'absent' or prop.state == 'reset': + if prop.property_exists(): + if not prop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = prop.reset_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + elif prop.state == 'present': + if prop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if prop.property_exists(): + if not prop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = prop.set_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_command.py new file mode 100644 index 00000000..7c3587bc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_command.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_command +author: "Paul Baker (@paulquack)" +short_description: Run arbitrary commands on Extreme IronWare devices +description: + - Sends arbitrary commands to a Extreme Ironware node and returns the + results read from the device. This module includes a I(wait_for) + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +extends_documentation_fragment: +- community.network.ironware + +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. If the value + is set to C(all) then all conditionals in the I(wait_for) must be + satisfied. If the value is set to C(any) then only one of the + values must be satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +- name: Run a command + community.network.ironware_command: + commands: + - show version + +- name: Run several commands + community.network.ironware_command: + commands: + - show interfaces brief wide + - show mpls vll +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + spec.update(ironware_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + check_args(module) + + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_config.py new file mode 100644 index 00000000..ec3a7fb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_config.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_config +author: "Paul Baker (@paulquack)" +short_description: Manage configuration sections on Extreme Ironware devices +description: + - Extreme Ironware configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with Ironware configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.ironware + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct + default: line + choices: ['line', 'block'] + update: + description: + - The I(update) argument controls how the configuration statements + are processed on the remote device. Valid choices for the I(update) + argument are I(merge) and I(check). When the argument is set to + I(merge), the configuration changes are merged with the current + device running configuration. When the argument is set to I(check) + the configuration updates are determined but not actually configured + on the remote device. + default: merge + choices: ['merge', 'check'] + commit: + description: + - This argument specifies the update method to use when applying the + configuration changes to the remote node. If the value is set to + I(merge) the configuration updates are merged with the running- + config. If the value is set to I(check), no changes are made to + the remote host. + default: merge + choices: ['merge', 'check'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config + default: never + choices: ['always', 'never', 'modified'] + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Run commands that should be configured in the section + community.network.ironware_config: + lines: + - port-name test + - enable + - load-interval 30 + - rate-limit input broadcast unknown-unicast multicast 521216 64000 + parents: ['interface ethernet 1/2'] +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ironware_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import get_config, load_config, run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + configobjs = None + + candidate = get_candidate(module) + if match != 'none': + contents = module.params['config'] + if not contents: + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + configobjs = candidate.difference(config, path=path, match=match, + replace=replace) + + else: + configobjs = candidate.items + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + if result['changed'] or module.params['save_when'] == 'always': + result['changed'] = True + if not module.check_mode: + cmd = {'command': 'write memory'} + run_commands(module, [cmd]) + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save_when=dict(choices=['always', 'never', 'modified'], default='never') + + ) + + argument_spec.update(ironware_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + check_args(module) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_facts.py new file mode 100644 index 00000000..11467bf4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ironware_facts.py @@ -0,0 +1,647 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_facts +author: "Paul Baker (@paulquack)" +short_description: Collect facts from devices running Extreme Ironware +description: + - Collects a base set of device facts from a remote device that + is running Ironware. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +extends_documentation_fragment: +- community.network.ironware + +notes: + - Tested against Ironware 5.8e +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, mpls and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: ['!config','!mpls'] +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.ironware_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.ironware_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.ironware_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# mpls +ansible_net_mpls_lsps: + description: All MPLS LSPs configured on the device + returned: When LSP is configured + type: dict +ansible_net_mpls_vll: + description: All VLL instances configured on the device + returned: When MPLS VLL is configured + type: dict +ansible_net_mpls_vll_local: + description: All VLL-LOCAL instances configured on the device + returned: When MPLS VLL-LOCAL is configured + type: dict +ansible_net_mpls_vpls: + description: All VPLS instances configured on the device + returned: When MPLS VPLS is configured + type: dict + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import run_commands +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show chassis' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + + def parse_version(self, data): + match = re.search(r'IronWare : Version (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'^\*\*\* (.+) \*\*\*$', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial #: (\S+),', data) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'dir | include Directory', + 'show memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + + data = self.responses[1] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024 / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024 / 1024, 0)) + + def parse_filesystems(self, data): + return re.findall(r'^Directory of (\S+)', data, re.M) + + def parse_memtotal(self, data): + match = re.search(r'Total SDRAM\D*(\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'(Total Free Memory|Available Memory)\D*(\d+)\s', data, re.M) + if match: + return match.group(2) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class MPLS(FactsBase): + + COMMANDS = [ + 'show mpls lsp detail', + 'show mpls vll-local detail', + 'show mpls vll detail', + 'show mpls vpls detail' + ] + + def populate(self): + super(MPLS, self).populate() + data = self.responses[0] + if data: + data = self.parse_mpls(data) + self.facts['mpls_lsps'] = self.populate_lsps(data) + + data = self.responses[1] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vll_local'] = self.populate_vll_local(data) + + data = self.responses[2] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vll'] = self.populate_vll(data) + + data = self.responses[3] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vpls'] = self.populate_vpls(data) + + def parse_mpls(self, data): + parsed = dict() + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(LSP|VLL|VPLS) ([^\s,]+)', line) + if match: + key = match.group(2) + parsed[key] = line + return parsed + + def populate_vpls(self, vpls): + facts = dict() + for key, value in iteritems(vpls): + vpls = dict() + vpls['endpoints'] = self.parse_vpls_endpoints(value) + vpls['vc-id'] = self.parse_vpls_vcid(value) + facts[key] = vpls + return facts + + def populate_vll_local(self, vll_locals): + facts = dict() + for key, value in iteritems(vll_locals): + vll = dict() + vll['endpoints'] = self.parse_vll_endpoints(value) + facts[key] = vll + return facts + + def populate_vll(self, vlls): + facts = dict() + for key, value in iteritems(vlls): + vll = dict() + vll['endpoints'] = self.parse_vll_endpoints(value) + vll['vc-id'] = self.parse_vll_vcid(value) + vll['cos'] = self.parse_vll_cos(value) + facts[key] = vll + return facts + + def parse_vll_vcid(self, data): + match = re.search(r'VC-ID (\d+),', data, re.M) + if match: + return match.group(1) + + def parse_vll_cos(self, data): + match = re.search(r'COS +: +(\d+)', data, re.M) + if match: + return match.group(1) + + def parse_vll_endpoints(self, data): + facts = list() + regex = r'End-point[0-9 ]*: +(?Ptagged|untagged) +(vlan +(?P[0-9]+) +)?(inner- vlan +(?P[0-9]+) +)?(?Pe [0-9/]+|--)' + matches = re.finditer(regex, data, re.IGNORECASE | re.DOTALL) + for match in matches: + f = match.groupdict() + f['type'] = 'local' + facts.append(f) + + regex = r'Vll-Peer +: +(?P[0-9\.]+).*Tunnel LSP +: +(?P\S+)' + matches = re.finditer(regex, data, re.IGNORECASE | re.DOTALL) + for match in matches: + f = match.groupdict() + f['type'] = 'remote' + facts.append(f) + + return facts + + def parse_vpls_vcid(self, data): + match = re.search(r'Id (\d+),', data, re.M) + if match: + return match.group(1) + + def parse_vpls_endpoints(self, data): + facts = list() + regex = r'Vlan (?P[0-9]+)\s(?: +(?:L2.*)\s| +Tagged: (?P.+)+\s| +Untagged: (?P.+)\s)*' + matches = re.finditer(regex, data, re.IGNORECASE) + for match in matches: + f = match.groupdict() + f['type'] = 'local' + facts.append(f) + + regex = r'Peer address: (?P[0-9\.]+)' + matches = re.finditer(regex, data, re.IGNORECASE) + for match in matches: + f = match.groupdict() + f['type'] = 'remote' + facts.append(f) + + return facts + + def populate_lsps(self, lsps): + facts = dict() + for key, value in iteritems(lsps): + lsp = dict() + lsp['to'] = self.parse_lsp_to(value) + lsp['from'] = self.parse_lsp_from(value) + lsp['adminstatus'] = self.parse_lsp_adminstatus(value) + lsp['operstatus'] = self.parse_lsp_operstatus(value) + lsp['pri_path'] = self.parse_lsp_pripath(value) + lsp['sec_path'] = self.parse_lsp_secpath(value) + lsp['frr'] = self.parse_lsp_frr(value) + + facts[key] = lsp + + return facts + + def parse_lsp_to(self, data): + match = re.search(r'^LSP .* to (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_lsp_from(self, data): + match = re.search(r'From: ([^\s,]+),', data, re.M) + if match: + return match.group(1) + + def parse_lsp_adminstatus(self, data): + match = re.search(r'admin: (\w+),', data, re.M) + if match: + return match.group(1) + + def parse_lsp_operstatus(self, data): + match = re.search(r'From: .* status: (\w+)', data, re.M) + if match: + return match.group(1) + + def parse_lsp_pripath(self, data): + match = re.search(r'Pri\. path: ([^\s,]+), up: (\w+), active: (\w+)', data, re.M) + if match: + path = dict() + path['name'] = match.group(1) if match.group(1) != 'NONE' else None + path['up'] = True if match.group(2) == 'yes' else False + path['active'] = True if match.group(3) == 'yes' else False + return path + + def parse_lsp_secpath(self, data): + match = re.search(r'Sec\. path: ([^\s,]+), active: (\w+).*\n.* status: (\w+)', data, re.M) + if match: + path = dict() + path['name'] = match.group(1) if match.group(1) != 'NONE' else None + path['up'] = True if match.group(3) == 'up' else False + path['active'] = True if match.group(2) == 'yes' else False + return path + + def parse_lsp_frr(self, data): + match = re.search(r'Backup LSP: (\w+)', data, re.M) + if match: + path = dict() + path['up'] = True if match.group(1) == 'UP' else False + path['name'] = None + if path['up']: + match = re.search(r'bypass_lsp: (\S)', data, re.M) + path['name'] = match.group(1) if match else None + return path + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces', + 'show ipv6 interface', + 'show lldp neighbors' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data and 'LLDP is not running' not in data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s([0-9a-f]+:+[0-9a-f:]+\/\d+)\s', value, re.M) + for addr in addresses: + address, masklen = addr.split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + for line in neighbors.split('\n'): + if line == '': + continue + match = re.search(r'([\d\/]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(5) + fact['port'] = match.group(3) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+Ethernet|eth )(\S+)', line) + if match: + key = match.group(2) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Port name is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'BW is (\d+)', data) + if match: + return int(match.group(1)) + + def parse_duplex(self, data): + match = re.search(r'configured duplex \S+ actual (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_mediatype(self, data): + match = re.search(r'Type\s*:\s*(.+)$', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + mpls=MPLS, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config", "!mpls"], type='list') + ) + + argument_spec.update(ironware_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + check_args(module) + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nclu.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nclu.py new file mode 100644 index 00000000..22537b5b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nclu.py @@ -0,0 +1,250 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016-2018, Cumulus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nclu +author: "Cumulus Networks (@isharacomix)" +short_description: Configure network interfaces using NCLU +description: + - Interface to the Network Command Line Utility, developed to make it easier + to configure operating systems running ifupdown2 and Quagga, such as + Cumulus Linux. Command documentation is available at + U(https://docs.cumulusnetworks.com/cumulus-linux/System-Configuration/Network-Command-Line-Utility-NCLU/) +options: + commands: + description: + - A list of strings containing the net commands to run. Mutually + exclusive with I(template). + template: + description: + - A single, multi-line string with jinja2 formatting. This string + will be broken by lines, and each line will be run through net. + Mutually exclusive with I(commands). + commit: + description: + - When true, performs a 'net commit' at the end of the block. + Mutually exclusive with I(atomic). + default: false + type: bool + abort: + description: + - Boolean. When true, perform a 'net abort' before the block. + This cleans out any uncommitted changes in the buffer. + Mutually exclusive with I(atomic). + default: false + type: bool + atomic: + description: + - When true, equivalent to both I(commit) and I(abort) being true. + Mutually exclusive with I(commit) and I(atomic). + default: false + type: bool + description: + description: + - Commit description that will be recorded to the commit log if + I(commit) or I(atomic) are true. + default: "Ansible-originated commit" +''' + +EXAMPLES = ''' + +- name: Add two interfaces without committing any changes + community.network.nclu: + commands: + - add int swp1 + - add int swp2 + +- name: Modify hostname to Cumulus-1 and commit the change + community.network.nclu: + commands: + - add hostname Cumulus-1 + commit: true + +- name: Add 48 interfaces and commit the change. + community.network.nclu: + template: | + {% for iface in range(1,49) %} + add int swp{{iface}} + {% endfor %} + commit: true + description: "Ansible - add swps1-48" + +- name: Fetch Status Of Interface + community.network.nclu: + commands: + - show interface swp1 + register: output + +- name: Print Status Of Interface + ansible.builtin.debug: + var: output + +- name: Fetch Details From All Interfaces In JSON Format + community.network.nclu: + commands: + - show interface json + register: output + +- name: Print Interface Details + ansible.builtin.debug: + var: output["msg"] + +- name: Atomically add an interface + community.network.nclu: + commands: + - add int swp1 + atomic: true + description: "Ansible - add swp1" + +- name: Remove IP address from interface swp1 + community.network.nclu: + commands: + - del int swp1 ip address 1.1.1.1/24 + +- name: Configure BGP AS and add 2 EBGP neighbors using BGP Unnumbered + community.network.nclu: + commands: + - add bgp autonomous-system 65000 + - add bgp neighbor swp51 interface remote-as external + - add bgp neighbor swp52 interface remote-as external + commit: true + +- name: Configure BGP AS and Add 2 EBGP neighbors Using BGP Unnumbered via Template + community.network.nclu: + template: | + {% for neighbor in range(51,53) %} + add bgp neighbor swp{{neighbor}} interface remote-as external + add bgp autonomous-system 65000 + {% endfor %} + atomic: true + +- name: Check BGP Status + community.network.nclu: + commands: + - show bgp summary json + register: output + +- name: Print BGP Status In JSON + ansible.builtin.debug: + var: output["msg"] +''' + +RETURN = ''' +changed: + description: whether the interface was changed + returned: changed + type: bool + sample: True +msg: + description: human-readable report of success or failure + returned: always + type: str + sample: "interface bond0 config updated" +''' + +from ansible.module_utils.basic import AnsibleModule + + +def command_helper(module, command, errmsg=None): + """Run a command, catch any nclu errors""" + (_rc, output, _err) = module.run_command("/usr/bin/net %s" % command) + if _rc or 'ERROR' in output or 'ERROR' in _err: + module.fail_json(msg=errmsg or output) + return str(output) + + +def check_pending(module): + """Check the pending diff of the nclu buffer.""" + pending = command_helper(module, "pending", "Error in pending config. You may want to view `net pending` on this target.") + + delimeter1 = "net add/del commands since the last 'net commit'" + color1 = '\x1b[94m' + if delimeter1 in pending: + pending = pending.split(delimeter1)[0] + pending = pending.replace(color1, '') + return pending.strip() + + +def run_nclu(module, command_list, command_string, commit, atomic, abort, description): + _changed = False + + commands = [] + if command_list: + commands = command_list + elif command_string: + commands = command_string.splitlines() + + do_commit = False + do_abort = abort + if commit or atomic: + do_commit = True + if atomic: + do_abort = True + + if do_abort: + command_helper(module, "abort") + + # First, look at the staged commands. + before = check_pending(module) + # Run all of the net commands + output_lines = [] + for line in commands: + if line.strip(): + output_lines += [command_helper(module, line.strip(), "Failed on line %s" % line)] + output = "\n".join(output_lines) + + # If pending changes changed, report a change. + after = check_pending(module) + if before == after: + _changed = False + else: + _changed = True + + # Do the commit. + if do_commit: + result = command_helper(module, "commit description '%s'" % description) + if "commit ignored" in result: + _changed = False + command_helper(module, "abort") + elif command_helper(module, "show commit last") == "": + _changed = False + + return _changed, output + + +def main(testing=False): + module = AnsibleModule(argument_spec=dict( + commands=dict(required=False, type='list'), + template=dict(required=False, type='str'), + description=dict(required=False, type='str', default="Ansible-originated commit"), + abort=dict(required=False, type='bool', default=False), + commit=dict(required=False, type='bool', default=False), + atomic=dict(required=False, type='bool', default=False)), + mutually_exclusive=[('commands', 'template'), + ('commit', 'atomic'), + ('abort', 'atomic')] + ) + command_list = module.params.get('commands', None) + command_string = module.params.get('template', None) + commit = module.params.get('commit') + atomic = module.params.get('atomic') + abort = module.params.get('abort') + description = module.params.get('description') + + _changed, output = run_nclu(module, command_list, command_string, commit, atomic, abort, description) + if not testing: + module.exit_json(changed=_changed, msg=output) + elif testing: + return {"changed": _changed, "msg": output} + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netact_cm_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netact_cm_command.py new file mode 100644 index 00000000..aba6f859 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netact_cm_command.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# Copyright: Nokia +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# pylint: disable=invalid-name +# pylint: disable=wrong-import-position +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=too-many-statements + +""" +NetAct CM ansible command module +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: netact_cm_command + +short_description: Manage network configuration data in Nokia Core and Radio networks + + +description: + netact_cm_command can be used to run various configuration management operations. + This module requires that the target hosts have Nokia NetAct network management system installed. + Module will access the Configurator command line interface in NetAct to upload network configuration to NetAct, + run configuration export, plan import and configuration provision operations + To set the scope of the operation, define Distinguished Name (DN) or Working Set (WS) or + Maintenance Region (MR) as input +options: + operation: + description: + Supported operations allow user to upload actual configuration from the network, to import and + provision prepared plans, or export reference or actual configuration for planning purposes. + Provision_Mass_Modification enables provisioning the same parameters to multiple network elements. + This operation supports modifications only to one object class at a time. With this option + NetAct Configurator creates and provisions a plan to the network with the given scope and options. + required: true + choices: + - upload + - provision + - import + - export + - Provision_Mass_Modification + aliases: + - op + opsName: + description: + - user specified operation name + required: false + DN: + description: + Sets the exact scope of the operation in form of a list of managed object + Distinguished Names (DN) in the network. + A single DN or a list of DNs can be given (comma separated list without spaces). + Alternatively, if DN or a list of DNs is not given, working set (WS) or Maintenance Region (MR) + must be provided as parameter to set the scope of operation. + required: false + + WS: + description: + Sets the scope of the operation to use one or more pre-defined working sets (WS) in NetAct. + A working set contains network elements selected by user according to defined criteria. + A single WS name, or multiple WSs can be provided (comma-separated list without spaces). + Alternatively, if a WS name or a list of WSs is not given, Distinguished Name (DN) or + Maintenance Region(MR) must be provided as parameter to set the scope of operation. + required: false + MR: + description: + Sets the scope of the operation to network elements assigned to a Maintenance Region (MR) + Value can be set as MR IDs including the Maintenance Region Collection (MRC) + information (for example MRC-FIN1/MR-Hel). + Multiple MRs can be given (comma-separated list without spaces) + The value of this parameter is searched through MR IDs under given MRC. If there is no match, + then it is searched from all MR names. + Alternatively, if MR ID or a list or MR IDs is not given, Distinguished Name (DN) or Working Set (WS) + must be provided as parameter to set the scope of operation. + required: false + planName: + description: + - Specifies a plan name. + required: false + typeOption: + description: + Specifies the type of the export operation. + required: false + choices: + - plan + - actual + - reference + - template + - siteTemplate + aliases: + - type + fileFormat: + description: + Indicates file format. + required: false + choices: + - RAML2 + - CSV + - XLSX + fileName: + description: + - Specifies a file name. Valid for Import and Export operations. + required: false + inputFile: + description: + Specifies full path to plan file location for the import operation. + This parameter (inputFile) or the fileName parameter must be filled. If both are present then + the inputFile is used. + required: false + createBackupPlan: + description: + - Specifies if backup plan generation is enabled. + required: false + type: bool + backupPlanName: + description: + - Specifies a backup plan name + required: false + verbose: + description: + NetAct Configurator will print more info + required: false + extra_opts: + description: + Extra options to be set for operations. Check Configuration Management > Configuration Management + Operating Procedures > Command Line Operations in Nokia NetAct user documentation for further + information for extra options. + required: false +notes: + - Check mode is not currently supported +author: + - Harri Tuominen (@hatuomin) +''' + +EXAMPLES = ''' +# Pass in a message +- name: Upload + community.network.netact_cm_command: + operation: "Upload" + opsname: 'Uploading_test' + dn: "PLMN-PLMN/MRBTS-746" + extra_opts: '-btsContentInUse true' + +- name: Provision + community.network.netact_cm_command: + operation: "Provision" + opsname: 'Provision_test' + dn: "PLMN-PLMN/MRBTS-746" + planName: 'mySiteTemplate' + type: 'actual' + createBackupPlan: true + backupPlanName: 'myBackupPlanName' + +- name: Export and fetching data from target + community.network.netact_cm_command: + operation: "Export" + opsname: 'Export_test' + planName: 'mySiteTemplate' + type: 'actual' + fileName: 'exportTest.xml' +- ansible.builtin.fetch: + src: /var/opt/nokia/oss/global/racops/export/exportTest.xml + dest: fetched + +- name: Import + community.network.netact_cm_command: + operation: "Import" + opsname: 'Import_test' + fileFormat: 'CSV' + type: 'plan' + fileName: 'myCSVFile' + planName: 'myPlanName' + extra_ops: 'enablePolicyPlans true' + +# fail the module +- name: Test failure of the module + community.network.netact_cm_command: + name: fail me +''' + +RETURN = ''' +original_message: + description: The original name param that was passed in + returned: Command line + type: str + sample: '/opt/oss/bin/racclimx.sh -op Upload -opsName Uploading_testi -DN PLMN-PLMN/MRBTS-746' +message: + description: The output message that the netact_cm_command module generates + returned: Command output message + type: str +changed: + description: data changed + returned: true if data is changed + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule + +racclimx = '/opt/oss/bin/racclimx.sh' + + +def main(): + """ + Main module where option are handled and command is executed + :return: + """ + # define the available arguments/parameters that a user can pass to + # the module + module_args = dict( + operation=dict(type='str', required=True, + aliases=['op'], + choices=['Upload', 'Provision', 'Import', + 'Export', 'Provision_Mass_Modification']), + opsName=dict(type='str', required=False), + DN=dict(type='str', required=False), + WS=dict(type='str', required=False), + MR=dict(type='str', required=False), + + planName=dict(type='str', required=False), + typeOption=dict(type='str', required=False, aliases=['type'], + choices=['plan', 'actual', 'reference', 'template', 'siteTemplate']), + fileFormat=dict(type='str', required=False, choices=['CSV', 'RAML2', 'XLSX']), + fileName=dict(type='str', required=False), + createBackupPlan=dict(type='bool', required=False), + backupPlanName=dict(type='str', required=False), + inputFile=dict(type='str', required=False), + + verbose=dict(type='str', required=False), + extra_opts=dict(type='str', required=False) + ) + + # seed the result dict in the object + # we primarily care about changed and state + # change is if this module effectively modified the target + # state will include any data that you want your module to pass back + # for consumption, for example, in a subsequent task + result = dict( + changed=False, + original_message='', + cmd='', + message='' + ) + + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True + ) + + # if the user is working with this module in only check mode we do not + # want to make any changes to the environment, just return the current + # state with no modifications + if module.check_mode: + result['skipped'] = True + result['msg'] = 'check mode not (yet) supported for this module' + module.exit_json(**result) + + # manipulate or modify the state as needed (this is going to be the + # part where your module will do what it needs to do) + + operation = module.params.get('operation') + if not operation: + module.fail_json(msg='Operation not defined', **result) + + opsname = module.params.get('opsName') + dn = module.params.get('DN') + ws = module.params.get('WS') + mr = module.params.get('MR') + + planname = module.params.get('planName') + typeoption = module.params.get('typeOption') + fileformat = module.params.get('fileFormat') + filename = module.params.get('fileName') + + createBackupPlan = module.params.get('createBackupPlan') + backupPlanName = module.params.get('backupPlanName') + inputfile = module.params.get('inputFile') + + extra_opts = module.params.get('extra_opts') + verbose = module.params.get('verbose') + + # parameter checks + + command = [racclimx, '-op', operation] + + if opsname: + command.append('-opsName') + command.append(opsname) + + if dn: + command.append('-DN') + command.append(dn) + + if ws: + command.append('-WS') + command.append(ws) + + if mr: + command.append('-MR') + command.append(mr) + + if planname: + command.append('-planName') + command.append(planname) + + if typeoption: + command.append('-type') + command.append(typeoption) + + if fileformat: + command.append('-fileFormat') + command.append(fileformat) + + if filename: + command.append('-fileName') + command.append(filename) + + if createBackupPlan: + command.append('-createBackupPlan') + command.append('true') + + if backupPlanName: + command.append('-backupPlanName') + command.append(backupPlanName) + + if inputfile: + command.append('-inputFile') + command.append(inputfile) + + if extra_opts: + command = command + extra_opts.split(" ") + + if verbose: + if verbose == 'True': + command.append("-v") + + rc, out, err = module.run_command(command, check_rc=True) + if rc != 0: + result['changed'] = False + module.fail_json(msg=err) + else: + result['changed'] = True + result['original_message'] = out + result['cmd'] = command + result['message'] = out + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_action.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_action.py new file mode 100644 index 00000000..8fc7511c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_action.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_action +short_description: Manage content switching actions +description: + - Manage content switching actions + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the content switching action. Must begin with an ASCII alphanumeric or underscore C(_) + character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon + C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the content + switching action is created. + + targetlbvserver: + description: + - "Name of the load balancing virtual server to which the content is switched." + + targetvserver: + description: + - "Name of the VPN virtual server to which the content is switched." + + targetvserverexpr: + description: + - "Information about this content switching action." + + comment: + description: + - "Comments associated with this cs action." + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# lb_vserver_1 must have been already created with the netscaler_lb_vserver module + +- name: Configure netscaler content switching action + delegate_to: localhost + community.network.netscaler_cs_action: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + name: action-1 + targetlbvserver: lb_vserver_1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +import json + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csaction import csaction + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) + + +def action_exists(client, module): + if csaction.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def action_identical(client, module, csaction_proxy): + if len(diff_list(client, module, csaction_proxy)) == 0: + return True + else: + return False + + +def diff_list(client, module, csaction_proxy): + action_list = csaction.get_filtered(client, 'name:%s' % module.params['name']) + diff_list = csaction_proxy.diff_object(action_list[0]) + if False and 'targetvserverexpr' in diff_list: + json_value = json.loads(action_list[0].targetvserverexpr) + if json_value == module.params['targetvserverexpr']: + del diff_list['targetvserverexpr'] + return diff_list + + +def main(): + + module_specific_arguments = dict( + + name=dict(type='str'), + targetlbvserver=dict(type='str'), + targetvserverexpr=dict(type='str'), + comment=dict(type='str'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'targetlbvserver', + 'targetvserverexpr', + 'comment', + ] + readonly_attrs = [ + 'hits', + 'referencecount', + 'undefhits', + 'builtin', + ] + + immutable_attrs = [ + 'name', + 'targetvserverexpr', + ] + + transforms = { + } + + # Instantiate config proxy + csaction_proxy = ConfigProxy( + actual=csaction(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + ensure_feature_is_enabled(client, 'CS') + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not action_exists(client, module): + if not module.check_mode: + csaction_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not action_identical(client, module, csaction_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(csaction_proxy, diff_list(client, module, csaction_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, csaction_proxy), + **module_result + ) + + if not module.check_mode: + csaction_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + log('Sanity checks for state present') + if not module.check_mode: + if not action_exists(client, module): + module.fail_json(msg='Content switching action does not exist', **module_result) + if not action_identical(client, module, csaction_proxy): + module.fail_json( + msg='Content switching action differs from configured', + diff=diff_list(client, module, csaction_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if action_exists(client, module): + if not module.check_mode: + csaction_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if action_exists(client, module): + module.fail_json(msg='Content switching action still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_policy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_policy.py new file mode 100644 index 00000000..47459608 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_policy.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_policy +short_description: Manage content switching policy +description: + - Manage content switching policy. + - "This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance." + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + policyname: + description: + - >- + Name for the content switching policy. Must begin with an ASCII alphanumeric or underscore C(_) + character, and must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon + C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Cannot be changed after a policy is + created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, my policy or my policy). + - "Minimum length = 1" + + url: + description: + - >- + URL string that is matched with the URL of a request. Can contain a wildcard character. Specify the + string value in the following format: C([[prefix] [*]] [.suffix]). + - "Minimum length = 1" + - "Maximum length = 208" + + rule: + description: + - >- + Expression, or name of a named expression, against which traffic is evaluated. Written in the classic + or default syntax. + - "Note:" + - >- + Maximum length of a string literal in the expression is 255 characters. A longer string can be split + into smaller strings of up to 255 characters each, and the smaller strings concatenated with the + + operator. For example, you can create a 500-character string as follows: '"" + ""' + + domain: + description: + - "The domain name. The string value can range to 63 characters." + - "Minimum length = 1" + + action: + description: + - >- + Content switching action that names the target load balancing virtual server to which the traffic is + switched. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Create url cs policy + delegate_to: localhost + community.network.netscaler_cs_policy: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + policyname: policy_1 + url: /example/ +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Could not load nitro python sdk" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'url': 'difference. ours: (str) example1 other: (str) /example1' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, ensure_feature_is_enabled) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy import cspolicy + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def policy_exists(client, module): + log('Checking if policy exists') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) > 0: + return True + else: + return False + + +def policy_identical(client, module, cspolicy_proxy): + log('Checking if defined policy is identical to configured') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) == 0: + return False + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + diff_dict = cspolicy_proxy.diff_object(policy_list[0]) + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, cspolicy_proxy): + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + return cspolicy_proxy.diff_object(policy_list[0]) + + +def main(): + + module_specific_arguments = dict( + policyname=dict(type='str'), + url=dict(type='str'), + rule=dict(type='str'), + domain=dict(type='str'), + action=dict(type='str'), + ) + + hand_inserted_arguments = dict( + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'policyname', + 'url', + 'rule', + 'domain', + 'action', + ] + readonly_attrs = [ + 'vstype', + 'hits', + 'bindhits', + 'labelname', + 'labeltype', + 'priority', + 'activepolicy', + 'cspolicytype', + ] + + transforms = { + } + + # Instantiate config proxy + cspolicy_proxy = ConfigProxy( + actual=cspolicy(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Sanity checks for state present') + if not policy_exists(client, module): + if not module.check_mode: + cspolicy_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not policy_identical(client, module, cspolicy_proxy): + if not module.check_mode: + cspolicy_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not policy_exists(client, module): + module.fail_json(msg='Policy does not exist', **module_result) + if not policy_identical(client, module, cspolicy_proxy): + module.fail_json(msg='Policy differs from configured', diff=diff_list(client, module, cspolicy_proxy), **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if policy_exists(client, module): + if not module.check_mode: + cspolicy_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if policy_exists(client, module): + module.fail_json(msg='Policy still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_vserver.py new file mode 100644 index 00000000..96113cdc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_cs_vserver.py @@ -0,0 +1,1302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_vserver +short_description: Manage content switching vserver +description: + - Manage content switching vserver + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the content switching virtual server. Must begin with an ASCII alphanumeric or underscore + C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, + colon C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. + - "Cannot be changed after the CS virtual server is created." + - "Minimum length = 1" + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = 0" + - "Maximum value = 4094" + + servicetype: + choices: + - 'HTTP' + - 'SSL' + - 'TCP' + - 'FTP' + - 'RTSP' + - 'SSL_TCP' + - 'UDP' + - 'DNS' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'ANY' + - 'RADIUS' + - 'RDP' + - 'MYSQL' + - 'MSSQL' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'DNS_TCP' + - 'ORACLE' + - 'SMPP' + description: + - "Protocol used by the virtual server." + + ipv46: + description: + - "IP address of the content switching virtual server." + - "Minimum length = 1" + + targettype: + choices: + - 'GSLB' + description: + - "Virtual server target type." + + ippattern: + description: + - >- + IP address pattern, in dotted decimal notation, for identifying packets to be accepted by the virtual + server. The IP Mask parameter specifies which part of the destination IP address is matched against + the pattern. Mutually exclusive with the IP Address parameter. + - >- + For example, if the IP pattern assigned to the virtual server is C(198.51.100.0) and the IP mask is + C(255.255.240.0) (a forward mask), the first 20 bits in the destination IP addresses are matched with + the first 20 bits in the pattern. The virtual server accepts requests with IP addresses that range + from 198.51.96.1 to 198.51.111.254. You can also use a pattern such as C(0.0.2.2) and a mask such as + C(0.0.255.255) (a reverse mask). + - >- + If a destination IP address matches more than one IP pattern, the pattern with the longest match is + selected, and the associated virtual server processes the request. For example, if the virtual + servers, C(vs1) and C(vs2), have the same IP pattern, C(0.0.100.128), but different IP masks of C(0.0.255.255) + and C(0.0.224.255), a destination IP address of 198.51.100.128 has the longest match with the IP pattern + of C(vs1). If a destination IP address matches two or more virtual servers to the same extent, the + request is processed by the virtual server whose port number matches the port number in the request. + + ipmask: + description: + - >- + IP mask, in dotted decimal notation, for the IP Pattern parameter. Can have leading or trailing + non-zero octets (for example, C(255.255.240.0) or C(0.0.255.255)). Accordingly, the mask specifies whether + the first n bits or the last n bits of the destination IP address in a client request are to be + matched with the corresponding bits in the IP pattern. The former is called a forward mask. The + latter is called a reverse mask. + + range: + description: + - >- + Number of consecutive IP addresses, starting with the address specified by the IP Address parameter, + to include in a range of addresses assigned to this virtual server. + - "Minimum value = C(1)" + - "Maximum value = C(254)" + + port: + description: + - "Port number for content switching virtual server." + - "Minimum value = 1" + - "Range C(1) - C(65535)" + - "* in CLI is represented as 65535 in NITRO API" + + stateupdate: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Enable state updates for a specific content switching virtual server. By default, the Content + Switching virtual server is always UP, regardless of the state of the Load Balancing virtual servers + bound to it. This parameter interacts with the global setting as follows: + - "Global Level | Vserver Level | Result" + - "enabled enabled enabled" + - "enabled disabled enabled" + - "disabled enabled enabled" + - "disabled disabled disabled" + - >- + If you want to enable state updates for only some content switching virtual servers, be sure to + disable the state update parameter. + + cacheable: + description: + - >- + Use this option to specify whether a virtual server, used for load balancing or content switching, + routes requests to the cache redirection virtual server before sending it to the configured servers. + type: bool + + redirecturl: + description: + - >- + URL to which traffic is redirected if the virtual server becomes unavailable. The service type of the + virtual server should be either C(HTTP) or C(SSL). + - >- + Caution: Make sure that the domain in the URL does not match the domain specified for a content + switching policy. If it does, requests are continuously redirected to the unavailable virtual server. + - "Minimum length = 1" + + clttimeout: + description: + - "Idle time, in seconds, after which the client connection is terminated. The default values are:" + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + precedence: + choices: + - 'RULE' + - 'URL' + description: + - >- + Type of precedence to use for both RULE-based and URL-based policies on the content switching virtual + server. With the default C(RULE) setting, incoming requests are evaluated against the rule-based + content switching policies. If none of the rules match, the URL in the request is evaluated against + the URL-based content switching policies. + + casesensitive: + description: + - >- + Consider case in URLs (for policies that use URLs instead of RULES). For example, with the C(on) + setting, the URLs /a/1.html and /A/1.HTML are treated differently and can have different targets (set + by content switching policies). With the C(off) setting, /a/1.html and /A/1.HTML are switched to the + same target. + type: bool + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - >- + Type of spillover used to divert traffic to the backup virtual server when the primary virtual server + reaches the spillover threshold. Connection spillover is based on the number of connections. + Bandwidth spillover is based on the total Kbps of incoming and outgoing traffic. + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - "Maintain source-IP based persistence on primary and backup virtual servers." + + sopersistencetimeout: + description: + - "Time-out value, in minutes, for spillover persistence." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + sothreshold: + description: + - >- + Depending on the spillover method, the maximum number of connections or the maximum total bandwidth + (Kbps) that a virtual server can handle before spillover occurs. + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + redirectportrewrite: + choices: + - 'enabled' + - 'disabled' + description: + - "State of port rewrite while performing HTTP redirect." + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a virtual server whose state transitions from UP to + DOWN. Do not enable this option for applications that must complete their transactions. + + backupvserver: + description: + - >- + Name of the backup virtual server that you are configuring. Must begin with an ASCII alphanumeric or + underscore C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), + space C( ), colon C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the + backup virtual server is created. You can assign a different backup virtual server or rename the + existing virtual server. + - "Minimum length = 1" + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Continue forwarding the traffic to backup virtual server even after the primary server comes UP from + the DOWN state. + + insertvserveripport: + choices: + - 'OFF' + - 'VIPADDR' + - 'V6TOV4MAPPING' + description: + - >- + Insert the virtual server's VIP address and port number in the request header. Available values + function as follows: + - "C(VIPADDR) - Header contains the vserver's IP address and port number without any translation." + - "C(OFF) - The virtual IP and port header insertion option is disabled." + - >- + C(V6TOV4MAPPING) - Header contains the mapped IPv4 address corresponding to the IPv6 address of the + vserver and the port number. An IPv6 address can be mapped to a user-specified IPv4 address using the + set ns ip6 command. + + vipheader: + description: + - "Name of virtual server IP and port header, for use with the VServer IP Port Insertion parameter." + - "Minimum length = 1" + + rtspnat: + description: + - "Enable network address translation (NAT) for real-time streaming protocol (RTSP) connections." + type: bool + + authenticationhost: + description: + - >- + FQDN of the authentication virtual server. The service type of the virtual server should be either + C(HTTP) or C(SSL). + - "Minimum length = 3" + - "Maximum length = 252" + + authentication: + description: + - "Authenticate users who request a connection to the content switching virtual server." + type: bool + + listenpolicy: + description: + - >- + String specifying the listen policy for the content switching virtual server. Can be either the name + of an existing expression or an in-line expression. + + authn401: + description: + - "Enable HTTP 401-response based authentication." + type: bool + + authnvsname: + description: + - >- + Name of authentication virtual server that authenticates the incoming user requests to this content + switching virtual server. . + - "Minimum length = 1" + - "Maximum length = 252" + + push: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Process traffic with the push virtual server that is bound to this content switching virtual server + (specified by the Push VServer parameter). The service type of the push virtual server should be + either C(HTTP) or C(SSL). + + pushvserver: + description: + - >- + Name of the load balancing virtual server, of type C(PUSH) or C(SSL_PUSH), to which the server pushes + updates received on the client-facing load balancing virtual server. + - "Minimum length = 1" + + pushlabel: + description: + - >- + Expression for extracting the label from the response received from server. This string can be either + an existing rule name or an inline expression. The service type of the virtual server should be + either C(HTTP) or C(SSL). + + pushmulticlients: + description: + - >- + Allow multiple Web 2.0 connections from the same client to connect to the virtual server and expect + updates. + type: bool + + tcpprofilename: + description: + - "Name of the TCP profile containing TCP configuration settings for the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - >- + Name of the HTTP profile containing HTTP configuration settings for the virtual server. The service + type of the virtual server should be either C(HTTP) or C(SSL). + - "Minimum length = 1" + - "Maximum length = 127" + + dbprofilename: + description: + - "Name of the DB profile." + - "Minimum length = 1" + - "Maximum length = 127" + + oracleserverversion: + choices: + - '10G' + - '11G' + description: + - "Oracle server version." + + comment: + description: + - "Information about this virtual server." + + mssqlserverversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - "The version of the MSSQL server." + + l2conn: + description: + - "Use L2 Parameters to identify a connection." + type: bool + + mysqlprotocolversion: + description: + - "The protocol version returned by the mysql vserver." + + mysqlserverversion: + description: + - "The server version string returned by the mysql vserver." + - "Minimum length = 1" + - "Maximum length = 31" + + mysqlcharacterset: + description: + - "The character set returned by the mysql vserver." + + mysqlservercapabilities: + description: + - "The server capabilities returned by the mysql vserver." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + netprofile: + description: + - "The name of the network profile." + - "Minimum length = 1" + - "Maximum length = 127" + + icmpvsrresponse: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "Can be active or passive." + + rhistate: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "A host route is injected according to the setting on the virtual servers" + - >- + * If set to C(PASSIVE) on all the virtual servers that share the IP address, the appliance always + injects the hostroute. + - >- + * If set to C(ACTIVE) on all the virtual servers that share the IP address, the appliance injects even + if one virtual server is UP. + - >- + * If set to C(ACTIVE) on some virtual servers and C(PASSIVE) on the others, the appliance, injects even if + one virtual server set to C(ACTIVE) is UP. + + authnprofile: + description: + - "Name of the authentication profile to be used when authentication is turned on." + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the VServer. DNS profile properties will applied to the + transactions processed by a VServer. This parameter is valid only for DNS and DNS-TCP VServers. + - "Minimum length = 1" + - "Maximum length = 127" + + domainname: + description: + - "Domain name for which to change the time to live (TTL) and/or backup service IP address." + - "Minimum length = 1" + + ttl: + description: + - "." + - "Minimum value = C(1)" + + backupip: + description: + - "." + - "Minimum length = 1" + + cookiedomain: + description: + - "." + - "Minimum length = 1" + + cookietimeout: + description: + - "." + - "Minimum value = C(0)" + - "Maximum value = C(1440)" + + sitedomainttl: + description: + - "." + - "Minimum value = C(1)" + + lbvserver: + description: + - The default Load Balancing virtual server. + + ssl_certkey: + description: + - The name of the ssl certificate that is bound to this service. + - The ssl certificate must already exist. + - Creating the certificate can be done with the M(community.network.netscaler_ssl_certkey) module. + - This option is only applicable only when C(servicetype) is C(SSL). + + disabled: + description: + - When set to C(yes) the cs vserver will be disabled. + - When set to C(no) the cs vserver will be enabled. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: 'no' + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# policy_1 must have been already created with the netscaler_cs_policy module +# lbvserver_1 must have been already created with the netscaler_lb_vserver module + +- name: Setup content switching vserver + delegate_to: localhost + community.network.netscaler_cs_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: cs_vserver_1 + ipv46: 192.168.1.1 + port: 80 + servicetype: HTTP + + policybindings: + - policyname: policy_1 + targetlbvserver: lbvserver_1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 100.0 other: (float) 60.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver import csvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_lbvserver_binding import csvserver_lbvserver_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_cspolicy_binding import csvserver_cspolicy_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding import sslvserver_sslcertkey_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def cs_vserver_exists(client, module): + if csvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def cs_vserver_identical(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = csvserver_proxy.diff_object(csvserver_list[0]) + if len(diff_dict) == 0: + return True + else: + return False + + +def get_configured_policybindings(client, module): + log('Getting configured policy bindigs') + bindings = {} + if module.params['policybindings'] is None: + return bindings + + for binding in module.params['policybindings']: + binding['name'] = module.params['name'] + key = binding['policyname'] + binding_proxy = ConfigProxy( + actual=csvserver_cspolicy_binding(), + client=client, + readwrite_attrs=[ + 'priority', + 'bindpoint', + 'policyname', + 'labelname', + 'gotopriorityexpression', + 'targetlbvserver', + 'name', + 'invoke', + 'labeltype', + ], + readonly_attrs=[], + attribute_values_dict=binding + ) + bindings[key] = binding_proxy + return bindings + + +def get_default_lb_vserver(client, module): + try: + default_lb_vserver = csvserver_lbvserver_binding.get(client, module.params['name']) + return default_lb_vserver[0] + except nitro_exception as e: + if e.errorcode == 258: + return csvserver_lbvserver_binding() + else: + raise + + +def default_lb_vserver_identical(client, module): + d = get_default_lb_vserver(client, module) + configured = ConfigProxy( + actual=csvserver_lbvserver_binding(), + client=client, + readwrite_attrs=[ + 'name', + 'lbvserver', + ], + attribute_values_dict={ + 'name': module.params['name'], + 'lbvserver': module.params['lbvserver'], + } + ) + log('default lb vserver %s' % ((d.name, d.lbvserver),)) + if d.name is None and module.params['lbvserver'] is None: + log('Default lb vserver identical missing') + return True + elif d.name is not None and module.params['lbvserver'] is None: + log('Default lb vserver needs removing') + return False + elif configured.has_equal_attributes(d): + log('Default lb vserver identical') + return True + else: + log('Default lb vserver not identical') + return False + + +def sync_default_lb_vserver(client, module): + d = get_default_lb_vserver(client, module) + + if module.params['lbvserver'] is not None: + configured = ConfigProxy( + actual=csvserver_lbvserver_binding(), + client=client, + readwrite_attrs=[ + 'name', + 'lbvserver', + ], + attribute_values_dict={ + 'name': module.params['name'], + 'lbvserver': module.params['lbvserver'], + } + ) + + if not configured.has_equal_attributes(d): + if d.name is not None: + log('Deleting default lb vserver %s' % d.lbvserver) + csvserver_lbvserver_binding.delete(client, d) + log('Adding default lb vserver %s' % configured.lbvserver) + configured.add() + else: + if d.name is not None: + log('Deleting default lb vserver %s' % d.lbvserver) + csvserver_lbvserver_binding.delete(client, d) + + +def get_actual_policybindings(client, module): + log('Getting actual policy bindigs') + bindings = {} + try: + count = csvserver_cspolicy_binding.count(client, name=module.params['name']) + if count == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + for binding in csvserver_cspolicy_binding.get(client, name=module.params['name']): + key = binding.policyname + bindings[key] = binding + + return bindings + + +def cs_policybindings_identical(client, module): + log('Checking policy bindings identical') + actual_bindings = get_actual_policybindings(client, module) + configured_bindings = get_configured_policybindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_bindings.keys()) + if len(actual_keyset ^ configured_keyset) > 0: + return False + + # Compare item to item + for key in actual_bindings.keys(): + configured_binding_proxy = configured_bindings[key] + actual_binding_object = actual_bindings[key] + if not configured_binding_proxy.has_equal_attributes(actual_binding_object): + return False + + # Fallthrough to success + return True + + +def sync_cs_policybindings(client, module): + log('Syncing cs policybindings') + actual_bindings = get_actual_policybindings(client, module) + configured_bindings = get_configured_policybindings(client, module) + + # Delete actual bindings not in configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindings.keys())) + for key in delete_keys: + log('Deleting binding for policy %s' % key) + csvserver_cspolicy_binding.delete(client, actual_bindings[key]) + + # Add configured bindings not in actual + add_keys = list(set(configured_bindings.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding binding for policy %s' % key) + configured_bindings[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindings.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindings[key].has_equal_attributes(actual_bindings[key]): + log('Updating binding for policy %s' % key) + csvserver_cspolicy_binding.delete(client, actual_bindings[key]) + configured_bindings[key].add() + + +def ssl_certkey_bindings_identical(client, module): + log('Checking if ssl cert key bindings are identical') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + if module.params['ssl_certkey'] is None: + if len(bindings) == 0: + return True + else: + return False + else: + certificate_list = [item.certkeyname for item in bindings] + if certificate_list == [module.params['ssl_certkey']]: + return True + else: + return False + + +def ssl_certkey_bindings_sync(client, module): + log('Syncing certkey bindings') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + # Delete existing bindings + for binding in bindings: + log('Deleting existing binding for certkey %s' % binding.certkeyname) + sslvserver_sslcertkey_binding.delete(client, binding) + + # Add binding if appropriate + if module.params['ssl_certkey'] is not None: + log('Adding binding for certkey %s' % module.params['ssl_certkey']) + binding = sslvserver_sslcertkey_binding() + binding.vservername = module.params['name'] + binding.certkeyname = module.params['ssl_certkey'] + sslvserver_sslcertkey_binding.add(client, binding) + + +def diff_list(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + return csvserver_proxy.diff_object(csvserver_list[0]) + + +def do_state_change(client, module, csvserver_proxy): + if module.params['disabled']: + log('Disabling cs vserver') + result = csvserver.disable(client, csvserver_proxy.actual) + else: + log('Enabling cs vserver') + result = csvserver.enable(client, csvserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + + name=dict(type='str'), + td=dict(type='float'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'SSL', + 'TCP', + 'FTP', + 'RTSP', + 'SSL_TCP', + 'UDP', + 'DNS', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'ANY', + 'RADIUS', + 'RDP', + 'MYSQL', + 'MSSQL', + 'DIAMETER', + 'SSL_DIAMETER', + 'DNS_TCP', + 'ORACLE', + 'SMPP' + ] + ), + + ipv46=dict(type='str'), + dnsrecordtype=dict( + type='str', + choices=[ + 'A', + 'AAAA', + 'CNAME', + 'NAPTR', + ] + ), + ippattern=dict(type='str'), + ipmask=dict(type='str'), + range=dict(type='float'), + port=dict(type='int'), + stateupdate=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cacheable=dict(type='bool'), + redirecturl=dict(type='str'), + clttimeout=dict(type='float'), + precedence=dict( + type='str', + choices=[ + 'RULE', + 'URL', + ] + ), + casesensitive=dict(type='bool'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + redirectportrewrite=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + insertvserveripport=dict( + type='str', + choices=[ + 'OFF', + 'VIPADDR', + 'V6TOV4MAPPING', + ] + ), + vipheader=dict(type='str'), + rtspnat=dict(type='bool'), + authenticationhost=dict(type='str'), + authentication=dict(type='bool'), + listenpolicy=dict(type='str'), + authn401=dict(type='bool'), + authnvsname=dict(type='str'), + push=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + pushvserver=dict(type='str'), + pushlabel=dict(type='str'), + pushmulticlients=dict(type='bool'), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + dbprofilename=dict(type='str'), + oracleserverversion=dict( + type='str', + choices=[ + '10G', + '11G', + ] + ), + comment=dict(type='str'), + mssqlserverversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + l2conn=dict(type='bool'), + mysqlprotocolversion=dict(type='float'), + mysqlserverversion=dict(type='str'), + mysqlcharacterset=dict(type='float'), + mysqlservercapabilities=dict(type='float'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + icmpvsrresponse=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + rhistate=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + authnprofile=dict(type='str'), + dnsprofilename=dict(type='str'), + ) + + hand_inserted_arguments = dict( + policybindings=dict(type='list'), + ssl_certkey=dict(type='str'), + disabled=dict( + type='bool', + default=False + ), + lbvserver=dict(type='str'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'td', + 'servicetype', + 'ipv46', + 'dnsrecordtype', + 'ippattern', + 'ipmask', + 'range', + 'port', + 'stateupdate', + 'cacheable', + 'redirecturl', + 'clttimeout', + 'precedence', + 'casesensitive', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'sothreshold', + 'sobackupaction', + 'redirectportrewrite', + 'downstateflush', + 'disableprimaryondown', + 'insertvserveripport', + 'vipheader', + 'rtspnat', + 'authenticationhost', + 'authentication', + 'listenpolicy', + 'authn401', + 'authnvsname', + 'push', + 'pushvserver', + 'pushlabel', + 'pushmulticlients', + 'tcpprofilename', + 'httpprofilename', + 'dbprofilename', + 'oracleserverversion', + 'comment', + 'mssqlserverversion', + 'l2conn', + 'mysqlprotocolversion', + 'mysqlserverversion', + 'mysqlcharacterset', + 'mysqlservercapabilities', + 'appflowlog', + 'netprofile', + 'icmpvsrresponse', + 'rhistate', + 'authnprofile', + 'dnsprofilename', + ] + + readonly_attrs = [ + 'ip', + 'value', + 'ngname', + 'type', + 'curstate', + 'sc', + 'status', + 'cachetype', + 'redirect', + 'homepage', + 'dnsvservername', + 'domain', + 'policyname', + 'servicename', + 'weight', + 'cachevserver', + 'targetvserver', + 'priority', + 'url', + 'gotopriorityexpression', + 'bindpoint', + 'invoke', + 'labeltype', + 'labelname', + 'gt2gb', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'ruletype', + 'lbvserver', + 'targetlbvserver', + ] + + immutable_attrs = [ + 'name', + 'td', + 'servicetype', + 'ipv46', + 'targettype', + 'range', + 'port', + 'state', + 'vipheader', + 'newname', + ] + + transforms = { + 'cacheable': ['bool_yes_no'], + 'rtspnat': ['bool_on_off'], + 'authn401': ['bool_on_off'], + 'casesensitive': ['bool_on_off'], + 'authentication': ['bool_on_off'], + 'l2conn': ['bool_on_off'], + 'pushmulticlients': ['bool_yes_no'], + 'stateupdate': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'redirectportrewrite': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'push': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + csvserver_proxy = ConfigProxy( + actual=csvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not cs_vserver_identical(client, module, csvserver_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(csvserver_proxy, diff_list(client, module, csvserver_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, csvserver_proxy), + **module_result + ) + + if not module.check_mode: + csvserver_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Check policybindings + if not cs_policybindings_identical(client, module): + if not module.check_mode: + sync_cs_policybindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if module.params['servicetype'] != 'SSL' and module.params['ssl_certkey'] is not None: + module.fail_json(msg='ssl_certkey is applicable only to SSL vservers', **module_result) + + # Check ssl certkey bindings + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + if not module.check_mode: + ssl_certkey_bindings_sync(client, module) + + module_result['changed'] = True + + # Check default lb vserver + if not default_lb_vserver_identical(client, module): + if not module.check_mode: + sync_default_lb_vserver(client, module) + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, csvserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not cs_vserver_exists(client, module): + module.fail_json(msg='CS vserver does not exist', **module_result) + if not cs_vserver_identical(client, module, csvserver_proxy): + module.fail_json(msg='CS vserver differs from configured', diff=diff_list(client, module, csvserver_proxy), **module_result) + if not cs_policybindings_identical(client, module): + module.fail_json(msg='Policy bindings differ') + + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + module.fail_json(msg='sll certkey bindings not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if cs_vserver_exists(client, module): + module.fail_json(msg='CS vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_service.py new file mode 100644 index 00000000..5151e67c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_service.py @@ -0,0 +1,691 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_service +short_description: Manage gslb service entities in Netscaler. +description: + - Manage gslb service entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + servicename: + description: + - >- + Name for the GSLB service. Must begin with an ASCII alphanumeric or underscore C(_) character, and + must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@), + equals C(=), and hyphen C(-) characters. Can be changed after the GSLB service is created. + - >- + - "Minimum length = 1" + + cnameentry: + description: + - "Canonical name of the GSLB service. Used in CNAME-based GSLB." + - "Minimum length = 1" + + + servername: + description: + - "Name of the server hosting the GSLB service." + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'NNTP' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'RADIUS' + - 'RDP' + - 'RTSP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + description: + - "Type of service to create." + + port: + description: + - "Port on which the load balancing entity represented by this GSLB service listens." + - "Minimum value = 1" + - "Range 1 - 65535" + - "* in CLI is represented as 65535 in NITRO API" + + publicip: + description: + - >- + The public IP address that a NAT device translates to the GSLB service's private IP address. + Optional. + + publicport: + description: + - >- + The public port associated with the GSLB service's public IP address. The port is mapped to the + service's private port number. Applicable to the local GSLB service. Optional. + + maxclient: + description: + - >- + The maximum number of open connections that the service can support at any given time. A GSLB service + whose connection count reaches the maximum is not considered when a GSLB decision is made, until the + connection count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(4294967294)" + + healthmonitor: + description: + - "Monitor the health of the GSLB service." + type: bool + + sitename: + description: + - "Name of the GSLB site to which the service belongs." + - "Minimum length = 1" + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - >- + In the request that is forwarded to the GSLB service, insert a header that stores the client's IP + address. Client IP header insertion is used in connection-proxy based site persistence. + + cipheader: + description: + - >- + Name for the HTTP header that stores the client's IP address. Used with the Client IP option. If + client IP header insertion is enabled on the service and a name is not specified for the header, the + NetScaler appliance uses the name specified by the cipHeader parameter in the set ns param command + or, in the GUI, the Client IP Header parameter in the Configure HTTP Parameters dialog box. + - "Minimum length = 1" + + sitepersistence: + choices: + - 'ConnectionProxy' + - 'HTTPRedirect' + - 'NONE' + description: + - "Use cookie-based site persistence. Applicable only to C(HTTP) and C(SSL) GSLB services." + + siteprefix: + description: + - >- + The site's prefix string. When the service is bound to a GSLB virtual server, a GSLB site domain is + generated internally for each bound service-domain pair by concatenating the site prefix of the + service and the name of the domain. If the special string NONE is specified, the site-prefix string + is unset. When implementing HTTP redirect site persistence, the NetScaler appliance redirects GSLB + requests to GSLB services by using their site domains. + + clttimeout: + description: + - >- + Idle time, in seconds, after which a client connection is terminated. Applicable if connection proxy + based site persistence is used. + - "Minimum value = 0" + - "Maximum value = 31536000" + + maxbandwidth: + description: + - >- + Integer specifying the maximum bandwidth allowed for the service. A GSLB service whose bandwidth + reaches the maximum is not considered when a GSLB decision is made, until its bandwidth consumption + drops below the maximum. + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with the GSLB service when its state transitions from UP to + DOWN. Do not enable this option for services that must complete their transactions. Applicable if + connection proxy based site persistence is used. + + maxaaausers: + description: + - >- + Maximum number of SSL VPN users that can be logged on concurrently to the VPN virtual server that is + represented by this GSLB service. A GSLB service whose user count reaches the maximum is not + considered when a GSLB decision is made, until the count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + monthreshold: + description: + - >- + Monitoring threshold value for the GSLB service. If the sum of the weights of the monitors that are + bound to this GSLB service and are in the UP state is not equal to or greater than this threshold + value, the service is marked as DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + hashid: + description: + - "Unique hash identifier for the GSLB service, used by hash based load balancing methods." + - "Minimum value = C(1)" + + comment: + description: + - "Any comments that you might want to associate with the GSLB service." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + ipaddress: + description: + - >- + IP address for the GSLB service. Should represent a load balancing, content switching, or VPN virtual + server on the NetScaler appliance, or the IP address of another load balancing device. + + monitor_bindings: + description: + - Bind monitors to this gslb service + suboptions: + + weight: + description: + - Weight to assign to the monitor-service binding. + - A larger number specifies a greater weight. + - Contributes to the monitoring threshold, which determines the state of the service. + - Minimum value = C(1) + - Maximum value = C(100) + + monitor_name: + description: + - Monitor name. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup gslb service 2 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + community.network.netscaler_gslb_service: + operation: present + + servicename: gslb-service-2 + cnameentry: example.com + sitename: gslb-site-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +import copy + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + monkey_patch_nitro_api, + get_immutables_intersection, +) + +try: + monkey_patch_nitro_api() + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice import gslbservice + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding import gslbservice_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def gslb_service_exists(client, module): + if gslbservice.count_filtered(client, 'servicename:%s' % module.params['servicename']) > 0: + return True + else: + return False + + +def gslb_service_identical(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_dict = gslb_service_proxy.diff_object(gslb_service_list[0]) + # Ignore ip attribute missing + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def get_actual_monitor_bindings(client, module): + log('get_actual_monitor_bindings') + # Get actual monitor bindings and index them by monitor_name + actual_monitor_bindings = {} + if gslbservice_lbmonitor_binding.count(client, servicename=module.params['servicename']) != 0: + # Get all monitor bindings associated with the named gslb vserver + fetched_bindings = gslbservice_lbmonitor_binding.get(client, servicename=module.params['servicename']) + # index by monitor name + for binding in fetched_bindings: + # complete_missing_attributes(binding, gslbservice_lbmonitor_binding_rw_attrs, fill_value=None) + actual_monitor_bindings[binding.monitor_name] = binding + return actual_monitor_bindings + + +def get_configured_monitor_bindings(client, module): + log('get_configured_monitor_bindings') + configured_monitor_proxys = {} + gslbservice_lbmonitor_binding_rw_attrs = [ + 'weight', + 'servicename', + 'monitor_name', + ] + # Get configured monitor bindings and index them by monitor_name + if module.params['monitor_bindings'] is not None: + for configured_monitor_bindings in module.params['monitor_bindings']: + binding_values = copy.deepcopy(configured_monitor_bindings) + binding_values['servicename'] = module.params['servicename'] + proxy = ConfigProxy( + actual=gslbservice_lbmonitor_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbservice_lbmonitor_binding_rw_attrs, + readonly_attrs=[], + ) + configured_monitor_proxys[configured_monitor_bindings['monitor_name']] = proxy + return configured_monitor_proxys + + +def monitor_bindings_identical(client, module): + log('monitor_bindings_identical') + actual_bindings = get_actual_monitor_bindings(client, module) + configured_proxys = get_configured_monitor_bindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + if len(symmetric_difference) != 0: + log('Symmetric difference %s' % symmetric_difference) + return False + + # Item for item equality test + for key, proxy in configured_proxys.items(): + if not proxy.has_equal_attributes(actual_bindings[key]): + log('monitor binding difference %s' % proxy.diff_object(actual_bindings[key])) + return False + + # Fallthrough to True result + return True + + +def sync_monitor_bindings(client, module): + log('sync_monitor_bindings') + + actual_monitor_bindings = get_actual_monitor_bindings(client, module) + configured_monitor_proxys = get_configured_monitor_bindings(client, module) + + # Delete actual bindings not in configured bindings + for monitor_name, actual_binding in actual_monitor_bindings.items(): + if monitor_name not in configured_monitor_proxys.keys(): + log('Deleting absent binding for monitor %s' % monitor_name) + log('dir is %s' % dir(actual_binding)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + + # Delete and re-add actual bindings that differ from configured + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key in actual_monitor_bindings: + actual_binding = actual_monitor_bindings[proxy_key] + if not binding_proxy.has_equal_attributes(actual_binding): + log('Deleting differing binding for monitor %s' % actual_binding.monitor_name) + log('dir %s' % dir(actual_binding)) + log('attribute monitor_name %s' % getattr(actual_binding, 'monitor_name')) + log('attribute monitorname %s' % getattr(actual_binding, 'monitorname', None)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + log('Adding anew binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + # Add configured monitors that are missing from actual + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key not in actual_monitor_bindings.keys(): + log('Adding monitor binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + +def diff_list(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_list = gslb_service_proxy.diff_object(gslb_service_list[0]) + if 'ip' in diff_list: + del diff_list['ip'] + return diff_list + + +def all_identical(client, module, gslb_service_proxy): + return gslb_service_identical(client, module, gslb_service_proxy) and monitor_bindings_identical(client, module) + + +def main(): + + module_specific_arguments = dict( + servicename=dict(type='str'), + cnameentry=dict(type='str'), + servername=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'NNTP', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'RADIUS', + 'RDP', + 'RTSP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + ] + ), + port=dict(type='int'), + publicip=dict(type='str'), + publicport=dict(type='int'), + maxclient=dict(type='float'), + healthmonitor=dict(type='bool'), + sitename=dict(type='str'), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + sitepersistence=dict( + type='str', + choices=[ + 'ConnectionProxy', + 'HTTPRedirect', + 'NONE', + ] + ), + siteprefix=dict(type='str'), + clttimeout=dict(type='float'), + maxbandwidth=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + maxaaausers=dict(type='float'), + monthreshold=dict(type='float'), + hashid=dict(type='float'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + ipaddress=dict(type='str'), + ) + + hand_inserted_arguments = dict( + monitor_bindings=dict(type='list'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'publicip', + 'publicport', + 'maxclient', + 'healthmonitor', + 'sitename', + 'cip', + 'cipheader', + 'sitepersistence', + 'siteprefix', + 'clttimeout', + 'maxbandwidth', + 'downstateflush', + 'maxaaausers', + 'monthreshold', + 'hashid', + 'comment', + 'appflowlog', + 'ipaddress', + ] + + readonly_attrs = [ + 'gslb', + 'svrstate', + 'svreffgslbstate', + 'gslbthreshold', + 'gslbsvcstats', + 'monstate', + 'preferredlocation', + 'monitor_state', + 'statechangetimesec', + 'tickssincelaststatechange', + 'threshold', + 'clmonowner', + 'clmonview', + '__count', + ] + + immutable_attrs = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'sitename', + 'state', + 'cipheader', + 'cookietimeout', + 'clttimeout', + 'svrtimeout', + 'viewip', + 'monitor_name_svc', + 'newname', + ] + + transforms = { + 'healthmonitor': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # params = copy.deepcopy(module.params) + module.params['ip'] = module.params['ipaddress'] + + # Instantiate config proxy + gslb_service_proxy = ConfigProxy( + actual=gslbservice(), + client=client, + attribute_values_dict=module.params, + transforms=transforms, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + # Apply appropriate state + if module.params['state'] == 'present': + if not gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_proxy.add() + sync_monitor_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, gslb_service_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(gslb_service_proxy, diff_list(client, module, gslb_service_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + # Update main configuration object + if not gslb_service_identical(client, module, gslb_service_proxy): + if not module.check_mode: + gslb_service_proxy.update() + + # Update monitor bindigns + if not monitor_bindings_identical(client, module): + if not module.check_mode: + sync_monitor_bindings(client, module) + + # Fallthrough to save and change status update + module_result['changed'] = True + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if not gslb_service_exists(client, module): + module.fail_json(msg='GSLB service does not exist', **module_result) + if not gslb_service_identical(client, module, gslb_service_proxy): + module.fail_json( + msg='GSLB service differs from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + if not monitor_bindings_identical(client, module): + module.fail_json( + msg='Monitor bindings differ from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + if gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if gslb_service_exists(client, module): + module.fail_json(msg='GSLB service still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_site.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_site.py new file mode 100644 index 00000000..96b9faeb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_site.py @@ -0,0 +1,419 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_site +short_description: Manage gslb site entities in Netscaler. +description: + - Manage gslb site entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + sitename: + description: + - >- + Name for the GSLB site. Must begin with an ASCII alphanumeric or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Cannot be changed after the virtual server is created. + - "Minimum length = 1" + + sitetype: + choices: + - 'REMOTE' + - 'LOCAL' + description: + - >- + Type of site to create. If the type is not specified, the appliance automatically detects and sets + the type on the basis of the IP address being assigned to the site. If the specified site IP address + is owned by the appliance (for example, a MIP address or SNIP address), the site is a local site. + Otherwise, it is a remote site. + + siteipaddress: + description: + - >- + IP address for the GSLB site. The GSLB site uses this IP address to communicate with other GSLB + sites. For a local site, use any IP address that is owned by the appliance (for example, a SNIP or + MIP address, or the IP address of the ADNS service). + - "Minimum length = 1" + + publicip: + description: + - >- + Public IP address for the local site. Required only if the appliance is deployed in a private address + space and the site has a public IP address hosted on an external firewall or a NAT device. + - "Minimum length = 1" + + metricexchange: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Exchange metrics with other sites. Metrics are exchanged by using Metric Exchange Protocol (MEP). The + appliances in the GSLB setup exchange health information once every second. + - >- + If you disable metrics exchange, you can use only static load balancing methods (such as round robin, + static proximity, or the hash-based methods), and if you disable metrics exchange when a dynamic load + balancing method (such as least connection) is in operation, the appliance falls back to round robin. + Also, if you disable metrics exchange, you must use a monitor to determine the state of GSLB + services. Otherwise, the service is marked as DOWN. + + nwmetricexchange: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Exchange, with other GSLB sites, network metrics such as round-trip time (RTT), learned from + communications with various local DNS (LDNS) servers used by clients. RTT information is used in the + dynamic RTT load balancing method, and is exchanged every 5 seconds. + + sessionexchange: + choices: + - 'enabled' + - 'disabled' + description: + - "Exchange persistent session entries with other GSLB sites every five seconds." + + triggermonitor: + choices: + - 'ALWAYS' + - 'MEPDOWN' + - 'MEPDOWN_SVCDOWN' + description: + - >- + Specify the conditions under which the GSLB service must be monitored by a monitor, if one is bound. + Available settings function as follows: + - "* C(ALWAYS) - Monitor the GSLB service at all times." + - >- + * C(MEPDOWN) - Monitor the GSLB service only when the exchange of metrics through the Metrics Exchange + Protocol (MEP) is disabled. + - "C(MEPDOWN_SVCDOWN) - Monitor the service in either of the following situations:" + - "* The exchange of metrics through MEP is disabled." + - >- + * The exchange of metrics through MEP is enabled but the status of the service, learned through + metrics exchange, is DOWN. + + parentsite: + description: + - "Parent site of the GSLB site, in a parent-child topology." + + clip: + description: + - >- + Cluster IP address. Specify this parameter to connect to the remote cluster site for GSLB auto-sync. + Note: The cluster IP address is defined when creating the cluster. + + publicclip: + description: + - >- + IP address to be used to globally access the remote cluster when it is deployed behind a NAT. It can + be same as the normal cluster IP address. + + naptrreplacementsuffix: + description: + - >- + The naptr replacement suffix configured here will be used to construct the naptr replacement field in + NAPTR record. + - "Minimum length = 1" + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup gslb site + delegate_to: localhost + community.network.netscaler_gslb_site: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + sitename: gslb-site-1 + siteipaddress: 192.168.1.1 + sitetype: LOCAL + publicip: 192.168.1.1 + metricexchange: enabled + nwmetricexchange: enabled + sessionexchange: enabled + triggermonitor: ALWAYS + +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbsite import gslbsite + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection, +) + + +def gslb_site_exists(client, module): + if gslbsite.count_filtered(client, 'sitename:%s' % module.params['sitename']) > 0: + return True + else: + return False + + +def gslb_site_identical(client, module, gslb_site_proxy): + gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename']) + diff_dict = gslb_site_proxy.diff_object(gslb_site_list[0]) + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, gslb_site_proxy): + gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename']) + return gslb_site_proxy.diff_object(gslb_site_list[0]) + + +def main(): + + module_specific_arguments = dict( + sitename=dict(type='str'), + sitetype=dict( + type='str', + choices=[ + 'REMOTE', + 'LOCAL', + ] + ), + siteipaddress=dict(type='str'), + publicip=dict(type='str'), + metricexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + nwmetricexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sessionexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + triggermonitor=dict( + type='str', + choices=[ + 'ALWAYS', + 'MEPDOWN', + 'MEPDOWN_SVCDOWN', + ] + ), + parentsite=dict(type='str'), + clip=dict(type='str'), + publicclip=dict(type='str'), + naptrreplacementsuffix=dict(type='str'), + ) + + hand_inserted_arguments = dict( + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'sitename', + 'sitetype', + 'siteipaddress', + 'publicip', + 'metricexchange', + 'nwmetricexchange', + 'sessionexchange', + 'triggermonitor', + 'parentsite', + 'clip', + 'publicclip', + 'naptrreplacementsuffix', + ] + + readonly_attrs = [ + 'status', + 'persistencemepstatus', + 'version', + '__count', + ] + + immutable_attrs = [ + 'sitename', + 'sitetype', + 'siteipaddress', + 'publicip', + 'parentsite', + 'clip', + 'publicclip', + ] + + transforms = { + 'metricexchange': [lambda v: v.upper()], + 'nwmetricexchange': [lambda v: v.upper()], + 'sessionexchange': [lambda v: v.upper()], + } + + # Instantiate config proxy + gslb_site_proxy = ConfigProxy( + actual=gslbsite(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not gslb_site_exists(client, module): + if not module.check_mode: + gslb_site_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not gslb_site_identical(client, module, gslb_site_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(gslb_site_proxy, diff_list(client, module, gslb_site_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_site_proxy), + **module_result + ) + + if not module.check_mode: + gslb_site_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not gslb_site_exists(client, module): + module.fail_json(msg='GSLB site does not exist', **module_result) + if not gslb_site_identical(client, module, gslb_site_proxy): + module.fail_json(msg='GSLB site differs from configured', diff=diff_list(client, module, gslb_site_proxy), **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if gslb_site_exists(client, module): + if not module.check_mode: + gslb_site_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if gslb_site_exists(client, module): + module.fail_json(msg='GSLB site still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_vserver.py new file mode 100644 index 00000000..2f10fcf7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_gslb_vserver.py @@ -0,0 +1,951 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_vserver +short_description: Configure gslb vserver entities in Netscaler. +description: + - Configure gslb vserver entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the GSLB virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character, + and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@), + equals C(=), and hyphen C(-) characters. Can be changed after the virtual server is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'NNTP' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'RADIUS' + - 'RDP' + - 'RTSP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + description: + - "Protocol used by services bound to the virtual server." + - >- + + dnsrecordtype: + choices: + - 'A' + - 'AAAA' + - 'CNAME' + - 'NAPTR' + description: + - "DNS record type to associate with the GSLB virtual server's domain name." + - "Default value: A" + - "Possible values = A, AAAA, CNAME, NAPTR" + + lbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'STATICPROXIMITY' + - 'RTT' + - 'CUSTOMLOAD' + description: + - "Load balancing method for the GSLB virtual server." + - "Default value: LEASTCONNECTION" + - >- + Possible values = ROUNDROBIN, LEASTCONNECTION, LEASTRESPONSETIME, SOURCEIPHASH, LEASTBANDWIDTH, + LEASTPACKETS, STATICPROXIMITY, RTT, CUSTOMLOAD + + backuplbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'STATICPROXIMITY' + - 'RTT' + - 'CUSTOMLOAD' + description: + - >- + Backup load balancing method. Becomes operational if the primary load balancing method fails or + cannot be used. Valid only if the primary method is based on either round-trip time (RTT) or static + proximity. + + netmask: + description: + - "IPv4 network mask for use in the SOURCEIPHASH load balancing method." + - "Minimum length = 1" + + v6netmasklen: + description: + - >- + Number of bits to consider, in an IPv6 source IP address, for creating the hash that is required by + the C(SOURCEIPHASH) load balancing method. + - "Default value: C(128)" + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + tolerance: + description: + - >- + Site selection tolerance, in milliseconds, for implementing the RTT load balancing method. If a + site's RTT deviates from the lowest RTT by more than the specified tolerance, the site is not + considered when the NetScaler appliance makes a GSLB decision. The appliance implements the round + robin method of global server load balancing between sites whose RTT values are within the specified + tolerance. If the tolerance is 0 (zero), the appliance always sends clients the IP address of the + site with the lowest RTT. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + persistencetype: + choices: + - 'SOURCEIP' + - 'NONE' + description: + - "Use source IP address based persistence for the virtual server." + - >- + After the load balancing method selects a service for the first packet, the IP address received in + response to the DNS query is used for subsequent requests from the same client. + + persistenceid: + description: + - >- + The persistence ID for the GSLB virtual server. The ID is a positive integer that enables GSLB sites + to identify the GSLB virtual server, and is required if source IP address based or spill over based + persistence is enabled on the virtual server. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + persistmask: + description: + - >- + The optional IPv4 network mask applied to IPv4 addresses to establish source IP address based + persistence. + - "Minimum length = 1" + + v6persistmasklen: + description: + - >- + Number of bits to consider in an IPv6 source IP address when creating source IP address based + persistence sessions. + - "Default value: C(128)" + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + timeout: + description: + - "Idle time, in minutes, after which a persistence entry is cleared." + - "Default value: C(2)" + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + mir: + choices: + - 'enabled' + - 'disabled' + description: + - "Include multiple IP addresses in the DNS responses sent to clients." + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Continue to direct traffic to the backup chain even after the primary GSLB virtual server returns to + the UP state. Used when spillover is configured for the virtual server. + + dynamicweight: + choices: + - 'SERVICECOUNT' + - 'SERVICEWEIGHT' + - 'DISABLED' + description: + - >- + Specify if the appliance should consider the service count, service weights, or ignore both when + using weight-based load balancing methods. The state of the number of services bound to the virtual + server help the appliance to select the service. + + considereffectivestate: + choices: + - 'NONE' + - 'STATE_ONLY' + description: + - >- + If the primary state of all bound GSLB services is DOWN, consider the effective states of all the + GSLB services, obtained through the Metrics Exchange Protocol (MEP), when determining the state of + the GSLB virtual server. To consider the effective state, set the parameter to STATE_ONLY. To + disregard the effective state, set the parameter to NONE. + - >- + The effective state of a GSLB service is the ability of the corresponding virtual server to serve + traffic. The effective state of the load balancing virtual server, which is transferred to the GSLB + service, is UP even if only one virtual server in the backup chain of virtual servers is in the UP + state. + + comment: + description: + - "Any comments that you might want to associate with the GSLB virtual server." + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:" + - "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold." + - >- + * C(DYNAMICCONNECTION) - Spillover occurs when the number of client connections at the GSLB virtual + server exceeds the sum of the maximum client (Max Clients) settings for bound GSLB services. Do not + specify a spillover threshold for this setting, because the threshold is implied by the Max Clients + settings of the bound GSLB services. + - >- + * C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the GSLB virtual server's incoming and + outgoing traffic exceeds the threshold. + - >- + * C(HEALTH) - Spillover occurs when the percentage of weights of the GSLB services that are UP drops + below the threshold. For example, if services gslbSvc1, gslbSvc2, and gslbSvc3 are bound to a virtual + server, with weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if gslbSvc1 + and gslbSvc3 or gslbSvc2 and gslbSvc3 transition to DOWN. + - "* C(NONE) - Spillover does not occur." + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If spillover occurs, maintain source IP address based persistence for both primary and backup GSLB + virtual servers. + + sopersistencetimeout: + description: + - "Timeout for spillover persistence, in minutes." + - "Default value: C(2)" + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + sothreshold: + description: + - >- + Threshold at which spillover occurs. Specify an integer for the CONNECTION spillover method, a + bandwidth value in kilobits per second for the BANDWIDTH method (do not enter the units), or a + percentage for the HEALTH method (do not enter the percentage symbol). + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + domain_bindings: + description: + - >- + List of bindings for domains for this glsb vserver. + suboptions: + cookietimeout: + description: + - Timeout, in minutes, for the GSLB site cookie. + + domainname: + description: + - Domain name for which to change the time to live (TTL) and/or backup service IP address. + + ttl: + description: + - Time to live (TTL) for the domain. + + sitedomainttl: + description: + - >- + TTL, in seconds, for all internally created site domains (created when a site prefix is + configured on a GSLB service) that are associated with this virtual server. + - Minimum value = C(1) + + service_bindings: + description: + - List of bindings for gslb services bound to this gslb virtual server. + suboptions: + servicename: + description: + - Name of the GSLB service for which to change the weight. + weight: + description: + - Weight to assign to the GSLB service. + + disabled: + description: + - When set to C(yes) the GSLB Vserver state will be set to C(disabled). + - When set to C(no) the GSLB Vserver state will be set to C(enabled). + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + + + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# FIXME: Add examples +''' + +RETURN = ''' +''' + + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver import gslbvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_gslbservice_binding import gslbvserver_gslbservice_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_domain_binding import gslbvserver_domain_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection, + complete_missing_attributes +) + + +gslbvserver_domain_binding_rw_attrs = [ + 'name', + 'domainname', + 'backupipflag', + 'cookietimeout', + 'backupip', + 'ttl', + 'sitedomainttl', + 'cookie_domainflag', +] + +gslbvserver_gslbservice_binding_rw_attrs = [ + 'name', + 'servicename', + 'weight', +] + + +def get_actual_domain_bindings(client, module): + log('get_actual_domain_bindings') + # Get actual domain bindings and index them by domainname + actual_domain_bindings = {} + if gslbvserver_domain_binding.count(client, name=module.params['name']) != 0: + # Get all domain bindings associated with the named gslb vserver + fetched_domain_bindings = gslbvserver_domain_binding.get(client, name=module.params['name']) + # index by domainname + for binding in fetched_domain_bindings: + complete_missing_attributes(binding, gslbvserver_domain_binding_rw_attrs, fill_value=None) + actual_domain_bindings[binding.domainname] = binding + return actual_domain_bindings + + +def get_configured_domain_bindings_proxys(client, module): + log('get_configured_domain_bindings_proxys') + configured_domain_proxys = {} + # Get configured domain bindings and index them by domainname + if module.params['domain_bindings'] is not None: + for configured_domain_binding in module.params['domain_bindings']: + binding_values = copy.deepcopy(configured_domain_binding) + binding_values['name'] = module.params['name'] + gslbvserver_domain_binding_proxy = ConfigProxy( + actual=gslbvserver_domain_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbvserver_domain_binding_rw_attrs, + readonly_attrs=[], + ) + configured_domain_proxys[configured_domain_binding['domainname']] = gslbvserver_domain_binding_proxy + return configured_domain_proxys + + +def sync_domain_bindings(client, module): + log('sync_domain_bindings') + + actual_domain_bindings = get_actual_domain_bindings(client, module) + configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) + + # Delete actual bindings not in configured bindings + for domainname, actual_domain_binding in actual_domain_bindings.items(): + if domainname not in configured_domain_proxys.keys(): + log('Deleting absent binding for domain %s' % domainname) + gslbvserver_domain_binding.delete(client, actual_domain_binding) + + # Delete actual bindings that differ from configured + for proxy_key, binding_proxy in configured_domain_proxys.items(): + if proxy_key in actual_domain_bindings: + actual_binding = actual_domain_bindings[proxy_key] + if not binding_proxy.has_equal_attributes(actual_binding): + log('Deleting differing binding for domain %s' % binding_proxy.domainname) + gslbvserver_domain_binding.delete(client, actual_binding) + log('Adding anew binding for domain %s' % binding_proxy.domainname) + binding_proxy.add() + + # Add configured domains that are missing from actual + for proxy_key, binding_proxy in configured_domain_proxys.items(): + if proxy_key not in actual_domain_bindings.keys(): + log('Adding domain binding for domain %s' % binding_proxy.domainname) + binding_proxy.add() + + +def domain_bindings_identical(client, module): + log('domain_bindings_identical') + actual_domain_bindings = get_actual_domain_bindings(client, module) + configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) + + actual_keyset = set(actual_domain_bindings.keys()) + configured_keyset = set(configured_domain_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + + log('symmetric difference %s' % symmetric_difference) + if len(symmetric_difference) != 0: + return False + + # Item for item equality test + for key, proxy in configured_domain_proxys.items(): + diff = proxy.diff_object(actual_domain_bindings[key]) + if 'backupipflag' in diff: + del diff['backupipflag'] + if not len(diff) == 0: + return False + # Fallthrough to True result + return True + + +def get_actual_service_bindings(client, module): + log('get_actual_service_bindings') + # Get actual domain bindings and index them by domainname + actual_bindings = {} + if gslbvserver_gslbservice_binding.count(client, name=module.params['name']) != 0: + # Get all service bindings associated with the named gslb vserver + fetched_bindings = gslbvserver_gslbservice_binding.get(client, name=module.params['name']) + # index by servicename + for binding in fetched_bindings: + complete_missing_attributes(binding, gslbvserver_gslbservice_binding_rw_attrs, fill_value=None) + actual_bindings[binding.servicename] = binding + + return actual_bindings + + +def get_configured_service_bindings(client, module): + log('get_configured_service_bindings_proxys') + configured_proxys = {} + # Get configured domain bindings and index them by domainname + if module.params['service_bindings'] is not None: + for configured_binding in module.params['service_bindings']: + binding_values = copy.deepcopy(configured_binding) + binding_values['name'] = module.params['name'] + gslbvserver_service_binding_proxy = ConfigProxy( + actual=gslbvserver_gslbservice_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbvserver_gslbservice_binding_rw_attrs, + readonly_attrs=[], + ) + configured_proxys[configured_binding['servicename']] = gslbvserver_service_binding_proxy + return configured_proxys + + +def sync_service_bindings(client, module): + actual = get_actual_service_bindings(client, module) + configured = get_configured_service_bindings(client, module) + + # Delete extraneous + extraneous_service_bindings = list(set(actual.keys()) - set(configured.keys())) + for servicename in extraneous_service_bindings: + log('Deleting missing binding from service %s' % servicename) + binding = actual[servicename] + binding.name = module.params['name'] + gslbvserver_gslbservice_binding.delete(client, binding) + + # Recreate different + common_service_bindings = list(set(actual.keys()) & set(configured.keys())) + for servicename in common_service_bindings: + proxy = configured[servicename] + binding = actual[servicename] + if not proxy.has_equal_attributes(actual): + log('Recreating differing service binding %s' % servicename) + gslbvserver_gslbservice_binding.delete(client, binding) + proxy.add() + + # Add missing + missing_service_bindings = list(set(configured.keys()) - set(actual.keys())) + for servicename in missing_service_bindings: + proxy = configured[servicename] + log('Adding missing service binding %s' % servicename) + proxy.add() + + +def service_bindings_identical(client, module): + actual_bindings = get_actual_service_bindings(client, module) + configured_proxys = get_configured_service_bindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + if len(symmetric_difference) != 0: + return False + + # Item for item equality test + for key, proxy in configured_proxys.items(): + if key in actual_bindings.keys(): + if not proxy.has_equal_attributes(actual_bindings[key]): + return False + + # Fallthrough to True result + return True + + +def gslb_vserver_exists(client, module): + if gslbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def gslb_vserver_identical(client, module, gslb_vserver_proxy): + gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) + if len(diff_dict) != 0: + return False + else: + return True + + +def all_identical(client, module, gslb_vserver_proxy): + return ( + gslb_vserver_identical(client, module, gslb_vserver_proxy) and + domain_bindings_identical(client, module) and + service_bindings_identical(client, module) + ) + + +def diff_list(client, module, gslb_vserver_proxy): + gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) + return gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) + + +def do_state_change(client, module, gslb_vserver_proxy): + if module.params['disabled']: + log('Disabling glsb_vserver') + result = gslbvserver.disable(client, gslb_vserver_proxy.actual) + else: + log('Enabling gslbvserver') + result = gslbvserver.enable(client, gslb_vserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'NNTP', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'RADIUS', + 'RDP', + 'RTSP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + ] + ), + dnsrecordtype=dict( + type='str', + choices=[ + 'A', + 'AAAA', + 'CNAME', + 'NAPTR', + ] + ), + lbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'STATICPROXIMITY', + 'RTT', + 'CUSTOMLOAD', + ] + ), + backuplbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'STATICPROXIMITY', + 'RTT', + 'CUSTOMLOAD', + ] + ), + netmask=dict(type='str'), + v6netmasklen=dict(type='float'), + tolerance=dict(type='float'), + persistencetype=dict( + type='str', + choices=[ + 'SOURCEIP', + 'NONE', + ] + ), + persistenceid=dict(type='float'), + persistmask=dict(type='str'), + v6persistmasklen=dict(type='float'), + timeout=dict(type='float'), + mir=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dynamicweight=dict( + type='str', + choices=[ + 'SERVICECOUNT', + 'SERVICEWEIGHT', + 'DISABLED', + ] + ), + considereffectivestate=dict( + type='str', + choices=[ + 'NONE', + 'STATE_ONLY', + ] + ), + comment=dict(type='str'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + domainname=dict(type='str'), + cookie_domain=dict(type='str'), + ) + + hand_inserted_arguments = dict( + domain_bindings=dict(type='list'), + service_bindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'servicetype', + 'dnsrecordtype', + 'lbmethod', + 'backuplbmethod', + 'netmask', + 'v6netmasklen', + 'tolerance', + 'persistencetype', + 'persistenceid', + 'persistmask', + 'v6persistmasklen', + 'timeout', + 'mir', + 'disableprimaryondown', + 'dynamicweight', + 'considereffectivestate', + 'comment', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'sothreshold', + 'sobackupaction', + 'appflowlog', + 'cookie_domain', + ] + + readonly_attrs = [ + 'curstate', + 'status', + 'lbrrreason', + 'iscname', + 'sitepersistence', + 'totalservices', + 'activeservices', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'health', + 'policyname', + 'priority', + 'gotopriorityexpression', + 'type', + 'vsvrbindsvcip', + 'vsvrbindsvcport', + '__count', + ] + + immutable_attrs = [ + 'name', + 'servicetype', + ] + + transforms = { + 'mir': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + gslb_vserver_proxy = ConfigProxy( + actual=gslbvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying state present') + if not gslb_vserver_exists(client, module): + log('Creating object') + if not module.check_mode: + gslb_vserver_proxy.add() + sync_domain_bindings(client, module) + sync_service_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, gslb_vserver_proxy): + log('Entering update actions') + + # Check if we try to change value of immutable attributes + if not gslb_vserver_identical(client, module, gslb_vserver_proxy): + log('Updating gslb vserver') + immutables_changed = get_immutables_intersection(gslb_vserver_proxy, diff_list(client, module, gslb_vserver_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_vserver_proxy), + **module_result + ) + if not module.check_mode: + gslb_vserver_proxy.update() + + # Update domain bindings + if not domain_bindings_identical(client, module): + if not module.check_mode: + sync_domain_bindings(client, module) + + # Update service bindings + if not service_bindings_identical(client, module): + if not module.check_mode: + sync_service_bindings(client, module) + + module_result['changed'] = True + if not module.check_mode: + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, gslb_vserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + if not gslb_vserver_exists(client, module): + module.fail_json(msg='GSLB Vserver does not exist', **module_result) + if not gslb_vserver_identical(client, module, gslb_vserver_proxy): + module.fail_json(msg='GSLB Vserver differs from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + if not domain_bindings_identical(client, module): + module.fail_json(msg='Domain bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + if not service_bindings_identical(client, module): + module.fail_json(msg='Service bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + + elif module.params['state'] == 'absent': + + if gslb_vserver_exists(client, module): + if not module.check_mode: + gslb_vserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if gslb_vserver_exists(client, module): + module.fail_json(msg='GSLB Vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_monitor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_monitor.py new file mode 100644 index 00000000..9221afb4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_monitor.py @@ -0,0 +1,1376 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +module: netscaler_lb_monitor +short_description: Manage load balancing monitors +description: + - Manage load balancing monitors. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + monitorname: + description: + - >- + Name for the monitor. Must begin with an ASCII alphanumeric or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. + - "Minimum length = 1" + + type: + choices: + - 'PING' + - 'TCP' + - 'HTTP' + - 'TCP-ECV' + - 'HTTP-ECV' + - 'UDP-ECV' + - 'DNS' + - 'FTP' + - 'LDNS-PING' + - 'LDNS-TCP' + - 'LDNS-DNS' + - 'RADIUS' + - 'USER' + - 'HTTP-INLINE' + - 'SIP-UDP' + - 'SIP-TCP' + - 'LOAD' + - 'FTP-EXTENDED' + - 'SMTP' + - 'SNMP' + - 'NNTP' + - 'MYSQL' + - 'MYSQL-ECV' + - 'MSSQL-ECV' + - 'ORACLE-ECV' + - 'LDAP' + - 'POP3' + - 'CITRIX-XML-SERVICE' + - 'CITRIX-WEB-INTERFACE' + - 'DNS-TCP' + - 'RTSP' + - 'ARP' + - 'CITRIX-AG' + - 'CITRIX-AAC-LOGINPAGE' + - 'CITRIX-AAC-LAS' + - 'CITRIX-XD-DDC' + - 'ND6' + - 'CITRIX-WI-EXTENDED' + - 'DIAMETER' + - 'RADIUS_ACCOUNTING' + - 'STOREFRONT' + - 'APPC' + - 'SMPP' + - 'CITRIX-XNC-ECV' + - 'CITRIX-XDM' + - 'CITRIX-STA-SERVICE' + - 'CITRIX-STA-SERVICE-NHOP' + description: + - "Type of monitor that you want to create." + + action: + choices: + - 'NONE' + - 'LOG' + - 'DOWN' + description: + - >- + Action to perform when the response to an inline monitor (a monitor of type C(HTTP-INLINE)) indicates + that the service is down. A service monitored by an inline monitor is considered C(DOWN) if the response + code is not one of the codes that have been specified for the Response Code parameter. + - "Available settings function as follows:" + - >- + * C(NONE) - Do not take any action. However, the show service command and the show lb monitor command + indicate the total number of responses that were checked and the number of consecutive error + responses received after the last successful probe. + - "* C(LOG) - Log the event in NSLOG or SYSLOG." + - >- + * C(DOWN) - Mark the service as being down, and then do not direct any traffic to the service until the + configured down time has expired. Persistent connections to the service are terminated as soon as the + service is marked as C(DOWN). Also, log the event in NSLOG or SYSLOG. + + respcode: + description: + - >- + Response codes for which to mark the service as UP. For any other response code, the action performed + depends on the monitor type. C(HTTP) monitors and C(RADIUS) monitors mark the service as C(DOWN), while + C(HTTP-INLINE) monitors perform the action indicated by the Action parameter. + + httprequest: + description: + - 'HTTP request to send to the server (for example, C("HEAD /file.html")).' + + rtsprequest: + description: + - 'RTSP request to send to the server (for example, C("OPTIONS *")).' + + customheaders: + description: + - "Custom header string to include in the monitoring probes." + + maxforwards: + description: + - >- + Maximum number of hops that the SIP request used for monitoring can traverse to reach the server. + Applicable only to monitors of type C(SIP-UDP). + - "Minimum value = C(0)" + - "Maximum value = C(255)" + + sipmethod: + choices: + - 'OPTIONS' + - 'INVITE' + - 'REGISTER' + description: + - "SIP method to use for the query. Applicable only to monitors of type C(SIP-UDP)." + + sipuri: + description: + - >- + SIP URI string to send to the service (for example, C(sip:sip.test)). Applicable only to monitors of + type C(SIP-UDP). + - "Minimum length = 1" + + sipreguri: + description: + - >- + SIP user to be registered. Applicable only if the monitor is of type C(SIP-UDP) and the SIP Method + parameter is set to C(REGISTER). + - "Minimum length = 1" + + send: + description: + - "String to send to the service. Applicable to C(TCP-ECV), C(HTTP-ECV), and C(UDP-ECV) monitors." + + recv: + description: + - >- + String expected from the server for the service to be marked as UP. Applicable to C(TCP-ECV), C(HTTP-ECV), + and C(UDP-ECV) monitors. + + query: + description: + - "Domain name to resolve as part of monitoring the DNS service (for example, C(example.com))." + + querytype: + choices: + - 'Address' + - 'Zone' + - 'AAAA' + description: + - >- + Type of DNS record for which to send monitoring queries. Set to C(Address) for querying A records, C(AAAA) + for querying AAAA records, and C(Zone) for querying the SOA record. + + scriptname: + description: + - >- + Path and name of the script to execute. The script must be available on the NetScaler appliance, in + the /nsconfig/monitors/ directory. + - "Minimum length = 1" + + scriptargs: + description: + - "String of arguments for the script. The string is copied verbatim into the request." + + dispatcherip: + description: + - "IP address of the dispatcher to which to send the probe." + + dispatcherport: + description: + - "Port number on which the dispatcher listens for the monitoring probe." + + username: + description: + - >- + User name with which to probe the C(RADIUS), C(NNTP), C(FTP), C(FTP-EXTENDED), C(MYSQL), C(MSSQL), C(POP3), C(CITRIX-AG), + C(CITRIX-XD-DDC), C(CITRIX-WI-EXTENDED), C(CITRIX-XNC) or C(CITRIX-XDM) server. + - "Minimum length = 1" + + password: + description: + - >- + Password that is required for logging on to the C(RADIUS), C(NNTP), C(FTP), C(FTP-EXTENDED), C(MYSQL), C(MSSQL), C(POP3), + C(CITRIX-AG), C(CITRIX-XD-DDC), C(CITRIX-WI-EXTENDED), C(CITRIX-XNC-ECV) or C(CITRIX-XDM) server. Used in + conjunction with the user name specified for the C(username) parameter. + - "Minimum length = 1" + + secondarypassword: + description: + - >- + Secondary password that users might have to provide to log on to the Access Gateway server. + Applicable to C(CITRIX-AG) monitors. + + logonpointname: + description: + - >- + Name of the logon point that is configured for the Citrix Access Gateway Advanced Access Control + software. Required if you want to monitor the associated login page or Logon Agent. Applicable to + C(CITRIX-AAC-LAS) and C(CITRIX-AAC-LOGINPAGE) monitors. + + lasversion: + description: + - >- + Version number of the Citrix Advanced Access Control Logon Agent. Required by the C(CITRIX-AAC-LAS) + monitor. + + radkey: + description: + - >- + Authentication key (shared secret text string) for RADIUS clients and servers to exchange. Applicable + to monitors of type C(RADIUS) and C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radnasid: + description: + - "NAS-Identifier to send in the Access-Request packet. Applicable to monitors of type C(RADIUS)." + - "Minimum length = 1" + + radnasip: + description: + - >- + Network Access Server (NAS) IP address to use as the source IP address when monitoring a RADIUS + server. Applicable to monitors of type C(RADIUS) and C(RADIUS_ACCOUNTING). + + radaccounttype: + description: + - "Account Type to be used in Account Request Packet. Applicable to monitors of type C(RADIUS_ACCOUNTING)." + - "Minimum value = 0" + - "Maximum value = 15" + + radframedip: + description: + - "Source ip with which the packet will go out . Applicable to monitors of type C(RADIUS_ACCOUNTING)." + + radapn: + description: + - >- + Called Station Id to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radmsisdn: + description: + - >- + Calling Stations Id to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radaccountsession: + description: + - >- + Account Session ID to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + lrtm: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Calculate the least response times for bound services. If this parameter is not enabled, the + appliance does not learn the response times of the bound services. Also used for LRTM load balancing. + + deviation: + description: + - >- + Time value added to the learned average response time in dynamic response time monitoring (DRTM). + When a deviation is specified, the appliance learns the average response time of bound services and + adds the deviation to the average. The final value is then continually adjusted to accommodate + response time variations over time. Specified in milliseconds, seconds, or minutes. + - "Minimum value = C(0)" + - "Maximum value = C(20939)" + + units1: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "Unit of measurement for the Deviation parameter. Cannot be changed after the monitor is created." + + interval: + description: + - "Time interval between two successive probes. Must be greater than the value of Response Time-out." + - "Minimum value = C(1)" + - "Maximum value = C(20940)" + + units3: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "monitor interval units." + + resptimeout: + description: + - >- + Amount of time for which the appliance must wait before it marks a probe as FAILED. Must be less than + the value specified for the Interval parameter. + - >- + Note: For C(UDP-ECV) monitors for which a receive string is not configured, response timeout does not + apply. For C(UDP-ECV) monitors with no receive string, probe failure is indicated by an ICMP port + unreachable error received from the service. + - "Minimum value = C(1)" + - "Maximum value = C(20939)" + + units4: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "monitor response timeout units." + + resptimeoutthresh: + description: + - >- + Response time threshold, specified as a percentage of the Response Time-out parameter. If the + response to a monitor probe has not arrived when the threshold is reached, the appliance generates an + SNMP trap called monRespTimeoutAboveThresh. After the response time returns to a value below the + threshold, the appliance generates a monRespTimeoutBelowThresh SNMP trap. For the traps to be + generated, the "MONITOR-RTO-THRESHOLD" alarm must also be enabled. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + retries: + description: + - >- + Maximum number of probes to send to establish the state of a service for which a monitoring probe + failed. + - "Minimum value = C(1)" + - "Maximum value = C(127)" + + failureretries: + description: + - >- + Number of retries that must fail, out of the number specified for the Retries parameter, for a + service to be marked as DOWN. For example, if the Retries parameter is set to 10 and the Failure + Retries parameter is set to 6, out of the ten probes sent, at least six probes must fail if the + service is to be marked as DOWN. The default value of 0 means that all the retries must fail if the + service is to be marked as DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(32)" + + alertretries: + description: + - >- + Number of consecutive probe failures after which the appliance generates an SNMP trap called + monProbeFailed. + - "Minimum value = C(0)" + - "Maximum value = C(32)" + + successretries: + description: + - "Number of consecutive successful probes required to transition a service's state from DOWN to UP." + - "Minimum value = C(1)" + - "Maximum value = C(32)" + + downtime: + description: + - >- + Time duration for which to wait before probing a service that has been marked as DOWN. Expressed in + milliseconds, seconds, or minutes. + - "Minimum value = C(1)" + - "Maximum value = C(20939)" + + units2: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "Unit of measurement for the Down Time parameter. Cannot be changed after the monitor is created." + + destip: + description: + - >- + IP address of the service to which to send probes. If the parameter is set to 0, the IP address of + the server to which the monitor is bound is considered the destination IP address. + + destport: + description: + - >- + TCP or UDP port to which to send the probe. If the parameter is set to 0, the port number of the + service to which the monitor is bound is considered the destination port. For a monitor of type C(USER), + however, the destination port is the port number that is included in the HTTP request sent to the + dispatcher. Does not apply to monitors of type C(PING). + + state: + choices: + - 'enabled' + - 'disabled' + description: + - >- + State of the monitor. The C(disabled) setting disables not only the monitor being configured, but all + monitors of the same type, until the parameter is set to C(enabled). If the monitor is bound to a + service, the state of the monitor is not taken into account when the state of the service is + determined. + + reverse: + description: + - >- + Mark a service as DOWN, instead of UP, when probe criteria are satisfied, and as UP instead of DOWN + when probe criteria are not satisfied. + type: bool + + transparent: + description: + - >- + The monitor is bound to a transparent device such as a firewall or router. The state of a transparent + device depends on the responsiveness of the services behind it. If a transparent device is being + monitored, a destination IP address must be specified. The probe is sent to the specified IP address + by using the MAC address of the transparent device. + type: bool + + iptunnel: + description: + - >- + Send the monitoring probe to the service through an IP tunnel. A destination IP address must be + specified. + type: bool + + tos: + description: + - "Probe the service by encoding the destination IP address in the IP TOS (6) bits." + type: bool + + tosid: + description: + - "The TOS ID of the specified destination IP. Applicable only when the TOS parameter is set." + - "Minimum value = C(1)" + - "Maximum value = C(63)" + + secure: + description: + - >- + Use a secure SSL connection when monitoring a service. Applicable only to TCP based monitors. The + secure option cannot be used with a C(CITRIX-AG) monitor, because a CITRIX-AG monitor uses a secure + connection by default. + type: bool + + validatecred: + description: + - >- + Validate the credentials of the Xen Desktop DDC server user. Applicable to monitors of type + C(CITRIX-XD-DDC). + type: bool + + domain: + description: + - >- + Domain in which the XenDesktop Desktop Delivery Controller (DDC) servers or Web Interface servers are + present. Required by C(CITRIX-XD-DDC) and C(CITRIX-WI-EXTENDED) monitors for logging on to the DDC servers + and Web Interface servers, respectively. + + ipaddress: + description: + - >- + Set of IP addresses expected in the monitoring response from the DNS server, if the record type is A + or AAAA. Applicable to C(DNS) monitors. + - "Minimum length = 1" + + group: + description: + - >- + Name of a newsgroup available on the NNTP service that is to be monitored. The appliance periodically + generates an NNTP query for the name of the newsgroup and evaluates the response. If the newsgroup is + found on the server, the service is marked as UP. If the newsgroup does not exist or if the search + fails, the service is marked as DOWN. Applicable to NNTP monitors. + - "Minimum length = 1" + + filename: + description: + - >- + Name of a file on the FTP server. The appliance monitors the FTP service by periodically checking the + existence of the file on the server. Applicable to C(FTP-EXTENDED) monitors. + - "Minimum length = 1" + + basedn: + description: + - >- + The base distinguished name of the LDAP service, from where the LDAP server can begin the search for + the attributes in the monitoring query. Required for C(LDAP) service monitoring. + - "Minimum length = 1" + + binddn: + description: + - >- + The distinguished name with which an LDAP monitor can perform the Bind operation on the LDAP server. + Optional. Applicable to C(LDAP) monitors. + - "Minimum length = 1" + + filter: + description: + - "Filter criteria for the LDAP query. Optional." + - "Minimum length = 1" + + attribute: + description: + - >- + Attribute to evaluate when the LDAP server responds to the query. Success or failure of the + monitoring probe depends on whether the attribute exists in the response. Optional. + - "Minimum length = 1" + + database: + description: + - "Name of the database to connect to during authentication." + - "Minimum length = 1" + + oraclesid: + description: + - "Name of the service identifier that is used to connect to the Oracle database during authentication." + - "Minimum length = 1" + + sqlquery: + description: + - >- + SQL query for a C(MYSQL-ECV) or C(MSSQL-ECV) monitor. Sent to the database server after the server + authenticates the connection. + - "Minimum length = 1" + + evalrule: + description: + - >- + Default syntax expression that evaluates the database server's response to a MYSQL-ECV or MSSQL-ECV + monitoring query. Must produce a Boolean result. The result determines the state of the server. If + the expression returns TRUE, the probe succeeds. + - >- + For example, if you want the appliance to evaluate the error message to determine the state of the + server, use the rule C(MYSQL.RES.ROW(10) .TEXT_ELEM(2).EQ("MySQL")). + + mssqlprotocolversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - "Version of MSSQL server that is to be monitored." + + Snmpoid: + description: + - "SNMP OID for C(SNMP) monitors." + - "Minimum length = 1" + + snmpcommunity: + description: + - "Community name for C(SNMP) monitors." + - "Minimum length = 1" + + snmpthreshold: + description: + - "Threshold for C(SNMP) monitors." + - "Minimum length = 1" + + snmpversion: + choices: + - 'V1' + - 'V2' + description: + - "SNMP version to be used for C(SNMP) monitors." + + metrictable: + description: + - "Metric table to which to bind metrics." + - "Minimum length = 1" + - "Maximum length = 99" + + application: + description: + - >- + Name of the application used to determine the state of the service. Applicable to monitors of type + C(CITRIX-XML-SERVICE). + - "Minimum length = 1" + + sitepath: + description: + - >- + URL of the logon page. For monitors of type C(CITRIX-WEB-INTERFACE), to monitor a dynamic page under the + site path, terminate the site path with a slash C(/). Applicable to C(CITRIX-WEB-INTERFACE), + C(CITRIX-WI-EXTENDED) and C(CITRIX-XDM) monitors. + - "Minimum length = 1" + + storename: + description: + - >- + Store Name. For monitors of type C(STOREFRONT), C(storename) is an optional argument defining storefront + service store name. Applicable to C(STOREFRONT) monitors. + - "Minimum length = 1" + + storefrontacctservice: + description: + - >- + Enable/Disable probing for Account Service. Applicable only to Store Front monitors. For + multi-tenancy configuration users my skip account service. + type: bool + + hostname: + description: + - "Hostname in the FQDN format (Example: C(porche.cars.org)). Applicable to C(STOREFRONT) monitors." + - "Minimum length = 1" + + netprofile: + description: + - "Name of the network profile." + - "Minimum length = 1" + - "Maximum length = 127" + + originhost: + description: + - >- + Origin-Host value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + originrealm: + description: + - >- + Origin-Realm value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + hostipaddress: + description: + - >- + Host-IP-Address value for the Capabilities-Exchange-Request (CER) message to use for monitoring + Diameter servers. If Host-IP-Address is not specified, the appliance inserts the mapped IP (MIP) + address or subnet IP (SNIP) address from which the CER request (the monitoring probe) is sent. + - "Minimum length = 1" + + vendorid: + description: + - >- + Vendor-Id value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + + productname: + description: + - >- + Product-Name value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + firmwarerevision: + description: + - >- + Firmware-Revision value for the Capabilities-Exchange-Request (CER) message to use for monitoring + Diameter servers. + + authapplicationid: + description: + - >- + List of Auth-Application-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum of eight of these AVPs are supported in a + monitoring CER message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + acctapplicationid: + description: + - >- + List of Acct-Application-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum of eight of these AVPs are supported in a + monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + inbandsecurityid: + choices: + - 'NO_INBAND_SECURITY' + - 'TLS' + description: + - >- + Inband-Security-Id for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + + supportedvendorids: + description: + - >- + List of Supported-Vendor-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum eight of these AVPs are supported in a + monitoring message. + - "Minimum value = C(1)" + - "Maximum value = C(4294967295)" + + vendorspecificvendorid: + description: + - >- + Vendor-Id to use in the Vendor-Specific-Application-Id grouped attribute-value pair (AVP) in the + monitoring CER message. To specify Auth-Application-Id or Acct-Application-Id in + Vendor-Specific-Application-Id, use vendorSpecificAuthApplicationIds or + vendorSpecificAcctApplicationIds, respectively. Only one Vendor-Id is supported for all the + Vendor-Specific-Application-Id AVPs in a CER monitoring message. + - "Minimum value = 1" + + vendorspecificauthapplicationids: + description: + - >- + List of Vendor-Specific-Auth-Application-Id attribute value pairs (AVPs) for the + Capabilities-Exchange-Request (CER) message to use for monitoring Diameter servers. A maximum of + eight of these AVPs are supported in a monitoring message. The specified value is combined with the + value of vendorSpecificVendorId to obtain the Vendor-Specific-Application-Id AVP in the CER + monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + vendorspecificacctapplicationids: + description: + - >- + List of Vendor-Specific-Acct-Application-Id attribute value pairs (AVPs) to use for monitoring + Diameter servers. A maximum of eight of these AVPs are supported in a monitoring message. The + specified value is combined with the value of vendorSpecificVendorId to obtain the + Vendor-Specific-Application-Id AVP in the CER monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + kcdaccount: + description: + - "KCD Account used by C(MSSQL) monitor." + - "Minimum length = 1" + - "Maximum length = 32" + + storedb: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Store the database list populated with the responses to monitor probes. Used in database specific + load balancing if C(MSSQL-ECV)/C(MYSQL-ECV) monitor is configured. + + storefrontcheckbackendservices: + description: + - >- + This option will enable monitoring of services running on storefront server. Storefront services are + monitored by probing to a Windows service that runs on the Storefront server and exposes details of + which storefront services are running. + type: bool + + trofscode: + description: + - "Code expected when the server is under maintenance." + + trofsstring: + description: + - >- + String expected from the server for the service to be marked as trofs. Applicable to HTTP-ECV/TCP-ECV + monitors. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Set lb monitor + local_action: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + + module: netscaler_lb_monitor + state: present + + monitorname: monitor_1 + type: HTTP-INLINE + action: DOWN + respcode: ['400'] +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' } +''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor import lbmonitor + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def lbmonitor_exists(client, module): + log('Checking if monitor exists') + if lbmonitor.count_filtered(client, 'monitorname:%s' % module.params['monitorname']) > 0: + return True + else: + return False + + +def lbmonitor_identical(client, module, lbmonitor_proxy): + log('Checking if monitor is identical') + + count = lbmonitor.count_filtered(client, 'monitorname:%s' % module.params['monitorname']) + if count == 0: + return False + + lbmonitor_list = lbmonitor.get_filtered(client, 'monitorname:%s' % module.params['monitorname']) + diff_dict = lbmonitor_proxy.diff_object(lbmonitor_list[0]) + + # Skipping hashed fields since the cannot be compared directly + hashed_fields = [ + 'password', + 'secondarypassword', + 'radkey', + ] + for key in hashed_fields: + if key in diff_dict: + del diff_dict[key] + + if diff_dict == {}: + return True + else: + return False + + +def diff_list(client, module, lbmonitor_proxy): + monitor_list = lbmonitor.get_filtered(client, 'monitorname:%s' % module.params['monitorname']) + return lbmonitor_proxy.diff_object(monitor_list[0]) + + +def main(): + + module_specific_arguments = dict( + + monitorname=dict(type='str'), + + type=dict( + type='str', + choices=[ + 'PING', + 'TCP', + 'HTTP', + 'TCP-ECV', + 'HTTP-ECV', + 'UDP-ECV', + 'DNS', + 'FTP', + 'LDNS-PING', + 'LDNS-TCP', + 'LDNS-DNS', + 'RADIUS', + 'USER', + 'HTTP-INLINE', + 'SIP-UDP', + 'SIP-TCP', + 'LOAD', + 'FTP-EXTENDED', + 'SMTP', + 'SNMP', + 'NNTP', + 'MYSQL', + 'MYSQL-ECV', + 'MSSQL-ECV', + 'ORACLE-ECV', + 'LDAP', + 'POP3', + 'CITRIX-XML-SERVICE', + 'CITRIX-WEB-INTERFACE', + 'DNS-TCP', + 'RTSP', + 'ARP', + 'CITRIX-AG', + 'CITRIX-AAC-LOGINPAGE', + 'CITRIX-AAC-LAS', + 'CITRIX-XD-DDC', + 'ND6', + 'CITRIX-WI-EXTENDED', + 'DIAMETER', + 'RADIUS_ACCOUNTING', + 'STOREFRONT', + 'APPC', + 'SMPP', + 'CITRIX-XNC-ECV', + 'CITRIX-XDM', + 'CITRIX-STA-SERVICE', + 'CITRIX-STA-SERVICE-NHOP', + ] + ), + + action=dict( + type='str', + choices=[ + 'NONE', + 'LOG', + 'DOWN', + ] + ), + respcode=dict(type='list'), + httprequest=dict(type='str'), + rtsprequest=dict(type='str'), + customheaders=dict(type='str'), + maxforwards=dict(type='float'), + sipmethod=dict( + type='str', + choices=[ + 'OPTIONS', + 'INVITE', + 'REGISTER', + ] + ), + sipuri=dict(type='str'), + sipreguri=dict(type='str'), + send=dict(type='str'), + recv=dict(type='str'), + query=dict(type='str'), + querytype=dict( + type='str', + choices=[ + 'Address', + 'Zone', + 'AAAA', + ] + ), + scriptname=dict(type='str'), + scriptargs=dict(type='str'), + dispatcherip=dict(type='str'), + dispatcherport=dict(type='int'), + username=dict(type='str'), + password=dict(type='str', no_log=True), + secondarypassword=dict(type='str', no_log=True), + logonpointname=dict(type='str'), + lasversion=dict(type='str'), + radkey=dict(type='str', no_log=True), + radnasid=dict(type='str'), + radnasip=dict(type='str'), + radaccounttype=dict(type='float'), + radframedip=dict(type='str'), + radapn=dict(type='str'), + radmsisdn=dict(type='str'), + radaccountsession=dict(type='str'), + lrtm=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + deviation=dict(type='float'), + units1=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + interval=dict(type='int'), + units3=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + resptimeout=dict(type='int'), + units4=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + resptimeoutthresh=dict(type='float'), + retries=dict(type='int'), + failureretries=dict(type='int'), + alertretries=dict(type='int'), + successretries=dict(type='int'), + downtime=dict(type='int'), + units2=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + destip=dict(type='str'), + destport=dict(type='int'), + reverse=dict(type='bool'), + transparent=dict(type='bool'), + iptunnel=dict(type='bool'), + tos=dict(type='bool'), + tosid=dict(type='float'), + secure=dict(type='bool'), + validatecred=dict(type='bool'), + domain=dict(type='str'), + ipaddress=dict(type='list'), + group=dict(type='str'), + filename=dict(type='str'), + basedn=dict(type='str'), + binddn=dict(type='str'), + filter=dict(type='str'), + attribute=dict(type='str'), + database=dict(type='str'), + oraclesid=dict(type='str'), + sqlquery=dict(type='str'), + evalrule=dict(type='str'), + mssqlprotocolversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + Snmpoid=dict(type='str'), + snmpcommunity=dict(type='str'), + snmpthreshold=dict(type='str'), + snmpversion=dict( + type='str', + choices=[ + 'V1', + 'V2', + ] + ), + application=dict(type='str'), + sitepath=dict(type='str'), + storename=dict(type='str'), + storefrontacctservice=dict(type='bool'), + hostname=dict(type='str'), + netprofile=dict(type='str'), + originhost=dict(type='str'), + originrealm=dict(type='str'), + hostipaddress=dict(type='str'), + vendorid=dict(type='float'), + productname=dict(type='str'), + firmwarerevision=dict(type='float'), + authapplicationid=dict(type='list'), + acctapplicationid=dict(type='list'), + inbandsecurityid=dict( + type='str', + choices=[ + 'NO_INBAND_SECURITY', + 'TLS', + ] + ), + supportedvendorids=dict(type='list'), + vendorspecificvendorid=dict(type='float'), + vendorspecificauthapplicationids=dict(type='list'), + vendorspecificacctapplicationids=dict(type='list'), + storedb=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + storefrontcheckbackendservices=dict(type='bool'), + trofscode=dict(type='float'), + trofsstring=dict(type='str'), + ) + + hand_inserted_arguments = dict() + + argument_spec = dict() + argument_spec.update(module_specific_arguments) + argument_spec.update(netscaler_common_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk', **module_result) + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate lb monitor object + readwrite_attrs = [ + 'monitorname', + 'type', + 'action', + 'respcode', + 'httprequest', + 'rtsprequest', + 'customheaders', + 'maxforwards', + 'sipmethod', + 'sipuri', + 'sipreguri', + 'send', + 'recv', + 'query', + 'querytype', + 'scriptname', + 'scriptargs', + 'dispatcherip', + 'dispatcherport', + 'username', + 'password', + 'secondarypassword', + 'logonpointname', + 'lasversion', + 'radkey', + 'radnasid', + 'radnasip', + 'radaccounttype', + 'radframedip', + 'radapn', + 'radmsisdn', + 'radaccountsession', + 'lrtm', + 'deviation', + 'units1', + 'interval', + 'units3', + 'resptimeout', + 'units4', + 'resptimeoutthresh', + 'retries', + 'failureretries', + 'alertretries', + 'successretries', + 'downtime', + 'units2', + 'destip', + 'destport', + 'reverse', + 'transparent', + 'iptunnel', + 'tos', + 'tosid', + 'secure', + 'validatecred', + 'domain', + 'ipaddress', + 'group', + 'filename', + 'basedn', + 'binddn', + 'filter', + 'attribute', + 'database', + 'oraclesid', + 'sqlquery', + 'evalrule', + 'mssqlprotocolversion', + 'Snmpoid', + 'snmpcommunity', + 'snmpthreshold', + 'snmpversion', + 'application', + 'sitepath', + 'storename', + 'storefrontacctservice', + 'netprofile', + 'originhost', + 'originrealm', + 'hostipaddress', + 'vendorid', + 'productname', + 'firmwarerevision', + 'authapplicationid', + 'acctapplicationid', + 'inbandsecurityid', + 'supportedvendorids', + 'vendorspecificvendorid', + 'vendorspecificauthapplicationids', + 'vendorspecificacctapplicationids', + 'storedb', + 'storefrontcheckbackendservices', + 'trofscode', + 'trofsstring', + ] + + readonly_attrs = [ + 'lrtmconf', + 'lrtmconfstr', + 'dynamicresponsetimeout', + 'dynamicinterval', + 'multimetrictable', + 'dup_state', + 'dup_weight', + 'weight', + ] + + immutable_attrs = [ + 'monitorname', + 'type', + 'units1', + 'units3', + 'units4', + 'units2', + 'Snmpoid', + 'hostname', + 'servicename', + 'servicegroupname', + ] + + transforms = { + 'storefrontcheckbackendservices': ['bool_yes_no'], + 'secure': ['bool_yes_no'], + 'tos': ['bool_yes_no'], + 'validatecred': ['bool_yes_no'], + 'storefrontacctservice': ['bool_yes_no'], + 'iptunnel': ['bool_yes_no'], + 'transparent': ['bool_yes_no'], + 'reverse': ['bool_yes_no'], + 'lrtm': [lambda v: v.upper()], + 'storedb': [lambda v: v.upper()], + } + + lbmonitor_proxy = ConfigProxy( + actual=lbmonitor(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'LB') + + if module.params['state'] == 'present': + log('Applying actions for state present') + if not lbmonitor_exists(client, module): + if not module.check_mode: + log('Adding monitor') + lbmonitor_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not lbmonitor_identical(client, module, lbmonitor_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(lbmonitor_proxy, diff_list(client, module, lbmonitor_proxy).keys()) + if immutables_changed != []: + diff = diff_list(client, module, lbmonitor_proxy) + msg = 'Cannot update immutable attributes %s' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff, **module_result) + + if not module.check_mode: + log('Updating monitor') + lbmonitor_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Doing nothing for monitor') + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state present') + if not module.check_mode: + if not lbmonitor_exists(client, module): + module.fail_json(msg='lb monitor does not exist', **module_result) + if not lbmonitor_identical(client, module, lbmonitor_proxy): + module.fail_json( + msg='lb monitor is not configured correctly', + diff=diff_list(client, module, lbmonitor_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if lbmonitor_exists(client, module): + if not module.check_mode: + lbmonitor_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state absent') + if not module.check_mode: + if lbmonitor_exists(client, module): + module.fail_json(msg='lb monitor still exists', **module_result) + + module_result['actual_attributes'] = lbmonitor_proxy.get_actual_rw_attributes(filter='monitorname') + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_vserver.py new file mode 100644 index 00000000..492a7d1a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_lb_vserver.py @@ -0,0 +1,1936 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_lb_vserver +short_description: Manage load balancing vserver configuration +description: + - Manage load balancing vserver configuration + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character, and + must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon C(:), at sign + C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the virtual server is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'DNS' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'RTSP' + - 'PUSH' + - 'SSL_PUSH' + - 'RADIUS' + - 'RDP' + - 'MYSQL' + - 'MSSQL' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'ORACLE' + - 'SMPP' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol used by the service (also called the service type)." + + ipv46: + description: + - "IPv4 or IPv6 address to assign to the virtual server." + + ippattern: + description: + - >- + IP address pattern, in dotted decimal notation, for identifying packets to be accepted by the virtual + server. The IP Mask parameter specifies which part of the destination IP address is matched against + the pattern. Mutually exclusive with the IP Address parameter. + - >- + For example, if the IP pattern assigned to the virtual server is C(198.51.100.0) and the IP mask is + C(255.255.240.0) (a forward mask), the first 20 bits in the destination IP addresses are matched with + the first 20 bits in the pattern. The virtual server accepts requests with IP addresses that range + from C(198.51.96.1) to C(198.51.111.254). You can also use a pattern such as C(0.0.2.2) and a mask such as + C(0.0.255.255) (a reverse mask). + - >- + If a destination IP address matches more than one IP pattern, the pattern with the longest match is + selected, and the associated virtual server processes the request. For example, if virtual servers + C(vs1) and C(vs2) have the same IP pattern, C(0.0.100.128), but different IP masks of C(0.0.255.255) and + C(0.0.224.255), a destination IP address of C(198.51.100.128) has the longest match with the IP pattern of + vs1. If a destination IP address matches two or more virtual servers to the same extent, the request + is processed by the virtual server whose port number matches the port number in the request. + + ipmask: + description: + - >- + IP mask, in dotted decimal notation, for the IP Pattern parameter. Can have leading or trailing + non-zero octets (for example, C(255.255.240.0) or C(0.0.255.255)). Accordingly, the mask specifies whether + the first n bits or the last n bits of the destination IP address in a client request are to be + matched with the corresponding bits in the IP pattern. The former is called a forward mask. The + latter is called a reverse mask. + + port: + description: + - "Port number for the virtual server." + - "Range C(1) - C(65535)" + - "* in CLI is represented as C(65535) in NITRO API" + + range: + description: + - >- + Number of IP addresses that the appliance must generate and assign to the virtual server. The virtual + server then functions as a network virtual server, accepting traffic on any of the generated IP + addresses. The IP addresses are generated automatically, as follows: + - >- + * For a range of n, the last octet of the address specified by the IP Address parameter increments + n-1 times. + - "* If the last octet exceeds 255, it rolls over to 0 and the third octet increments by 1." + - >- + Note: The Range parameter assigns multiple IP addresses to one virtual server. To generate an array + of virtual servers, each of which owns only one IP address, use brackets in the IP Address and Name + parameters to specify the range. For example: + - "add lb vserver my_vserver[1-3] HTTP 192.0.2.[1-3] 80." + - "Minimum value = C(1)" + - "Maximum value = C(254)" + + persistencetype: + choices: + - 'SOURCEIP' + - 'COOKIEINSERT' + - 'SSLSESSION' + - 'RULE' + - 'URLPASSIVE' + - 'CUSTOMSERVERID' + - 'DESTIP' + - 'SRCIPDESTIP' + - 'CALLID' + - 'RTSPSID' + - 'DIAMETER' + - 'FIXSESSION' + - 'NONE' + description: + - "Type of persistence for the virtual server. Available settings function as follows:" + - "* C(SOURCEIP) - Connections from the same client IP address belong to the same persistence session." + - >- + * C(COOKIEINSERT) - Connections that have the same HTTP Cookie, inserted by a Set-Cookie directive from + a server, belong to the same persistence session. + - "* C(SSLSESSION) - Connections that have the same SSL Session ID belong to the same persistence session." + - >- + * C(CUSTOMSERVERID) - Connections with the same server ID form part of the same session. For this + persistence type, set the Server ID (CustomServerID) parameter for each service and configure the + Rule parameter to identify the server ID in a request. + - "* C(RULE) - All connections that match a user defined rule belong to the same persistence session." + - >- + * C(URLPASSIVE) - Requests that have the same server ID in the URL query belong to the same persistence + session. The server ID is the hexadecimal representation of the IP address and port of the service to + which the request must be forwarded. This persistence type requires a rule to identify the server ID + in the request. + - "* C(DESTIP) - Connections to the same destination IP address belong to the same persistence session." + - >- + * C(SRCIPDESTIP) - Connections that have the same source IP address and destination IP address belong to + the same persistence session. + - "* C(CALLID) - Connections that have the same CALL-ID SIP header belong to the same persistence session." + - "* C(RTSPSID) - Connections that have the same RTSP Session ID belong to the same persistence session." + - >- + * FIXSESSION - Connections that have the same SenderCompID and TargetCompID values belong to the same + persistence session. + + timeout: + description: + - "Time period for which a persistence session is in effect." + - "Minimum value = C(0)" + - "Maximum value = C(1440)" + + persistencebackup: + choices: + - 'SOURCEIP' + - 'NONE' + description: + - >- + Backup persistence type for the virtual server. Becomes operational if the primary persistence + mechanism fails. + + backuppersistencetimeout: + description: + - "Time period for which backup persistence is in effect." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + lbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'URLHASH' + - 'DOMAINHASH' + - 'DESTINATIONIPHASH' + - 'SOURCEIPHASH' + - 'SRCIPDESTIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'TOKEN' + - 'SRCIPSRCPORTHASH' + - 'LRTM' + - 'CALLIDHASH' + - 'CUSTOMLOAD' + - 'LEASTREQUEST' + - 'AUDITLOGHASH' + - 'STATICPROXIMITY' + description: + - "Load balancing method. The available settings function as follows:" + - >- + * C(ROUNDROBIN) - Distribute requests in rotation, regardless of the load. Weights can be assigned to + services to enforce weighted round robin distribution. + - "* C(LEASTCONNECTION) (default) - Select the service with the fewest connections." + - "* C(LEASTRESPONSETIME) - Select the service with the lowest average response time." + - "* C(LEASTBANDWIDTH) - Select the service currently handling the least traffic." + - "* C(LEASTPACKETS) - Select the service currently serving the lowest number of packets per second." + - "* C(CUSTOMLOAD) - Base service selection on the SNMP metrics obtained by custom load monitors." + - >- + * C(LRTM) - Select the service with the lowest response time. Response times are learned through + monitoring probes. This method also takes the number of active connections into account. + - >- + Also available are a number of hashing methods, in which the appliance extracts a predetermined + portion of the request, creates a hash of the portion, and then checks whether any previous requests + had the same hash value. If it finds a match, it forwards the request to the service that served + those previous requests. Following are the hashing methods: + - "* C(URLHASH) - Create a hash of the request URL (or part of the URL)." + - >- + * C(DOMAINHASH) - Create a hash of the domain name in the request (or part of the domain name). The + domain name is taken from either the URL or the Host header. If the domain name appears in both + locations, the URL is preferred. If the request does not contain a domain name, the load balancing + method defaults to C(LEASTCONNECTION). + - "* C(DESTINATIONIPHASH) - Create a hash of the destination IP address in the IP header." + - "* C(SOURCEIPHASH) - Create a hash of the source IP address in the IP header." + - >- + * C(TOKEN) - Extract a token from the request, create a hash of the token, and then select the service + to which any previous requests with the same token hash value were sent. + - >- + * C(SRCIPDESTIPHASH) - Create a hash of the string obtained by concatenating the source IP address and + destination IP address in the IP header. + - "* C(SRCIPSRCPORTHASH) - Create a hash of the source IP address and source port in the IP header." + - "* C(CALLIDHASH) - Create a hash of the SIP Call-ID header." + + hashlength: + description: + - >- + Number of bytes to consider for the hash value used in the URLHASH and DOMAINHASH load balancing + methods. + - "Minimum value = C(1)" + - "Maximum value = C(4096)" + + netmask: + description: + - >- + IPv4 subnet mask to apply to the destination IP address or source IP address when the load balancing + method is C(DESTINATIONIPHASH) or C(SOURCEIPHASH). + - "Minimum length = 1" + + v6netmasklen: + description: + - >- + Number of bits to consider in an IPv6 destination or source IP address, for creating the hash that is + required by the C(DESTINATIONIPHASH) and C(SOURCEIPHASH) load balancing methods. + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + backuplbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'CUSTOMLOAD' + description: + - "Backup load balancing method. Becomes operational if the primary load balancing me" + - "thod fails or cannot be used." + - "Valid only if the primary method is based on static proximity." + + cookiename: + description: + - >- + Use this parameter to specify the cookie name for C(COOKIE) persistence type. It specifies the name of + cookie with a maximum of 32 characters. If not specified, cookie name is internally generated. + + + listenpolicy: + description: + - >- + Default syntax expression identifying traffic accepted by the virtual server. Can be either an + expression (for example, C(CLIENT.IP.DST.IN_SUBNET(192.0.2.0/24)) or the name of a named expression. In + the above example, the virtual server accepts all requests whose destination IP address is in the + 192.0.2.0/24 subnet. + + listenpriority: + description: + - >- + Integer specifying the priority of the listen policy. A higher number specifies a lower priority. If + a request matches the listen policies of more than one virtual server the virtual server whose listen + policy has the highest priority (the lowest priority number) accepts the request. + - "Minimum value = C(0)" + - "Maximum value = C(101)" + + resrule: + description: + - >- + Default syntax expression specifying which part of a server's response to use for creating rule based + persistence sessions (persistence type RULE). Can be either an expression or the name of a named + expression. + - "Example:" + - 'C(HTTP.RES.HEADER("setcookie").VALUE(0).TYPECAST_NVLIST_T("=",";").VALUE("server1")).' + + persistmask: + description: + - "Persistence mask for IP based persistence types, for IPv4 virtual servers." + - "Minimum length = 1" + + v6persistmasklen: + description: + - "Persistence mask for IP based persistence types, for IPv6 virtual servers." + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + rtspnat: + description: + - "Use network address translation (NAT) for RTSP data connections." + type: bool + + m: + choices: + - 'IP' + - 'MAC' + - 'IPTUNNEL' + - 'TOS' + description: + - "Redirection mode for load balancing. Available settings function as follows:" + - >- + * C(IP) - Before forwarding a request to a server, change the destination IP address to the server's IP + address. + - >- + * C(MAC) - Before forwarding a request to a server, change the destination MAC address to the server's + MAC address. The destination IP address is not changed. MAC-based redirection mode is used mostly in + firewall load balancing deployments. + - >- + * C(IPTUNNEL) - Perform IP-in-IP encapsulation for client IP packets. In the outer IP headers, set the + destination IP address to the IP address of the server and the source IP address to the subnet IP + (SNIP). The client IP packets are not modified. Applicable to both IPv4 and IPv6 packets. + - "* C(TOS) - Encode the virtual server's TOS ID in the TOS field of the IP header." + - "You can use either the C(IPTUNNEL) or the C(TOS) option to implement Direct Server Return (DSR)." + + tosid: + description: + - >- + TOS ID of the virtual server. Applicable only when the load balancing redirection mode is set to TOS. + - "Minimum value = C(1)" + - "Maximum value = C(63)" + + datalength: + description: + - >- + Length of the token to be extracted from the data segment of an incoming packet, for use in the token + method of load balancing. The length of the token, specified in bytes, must not be greater than 24 + KB. Applicable to virtual servers of type TCP. + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + dataoffset: + description: + - >- + Offset to be considered when extracting a token from the TCP payload. Applicable to virtual servers, + of type TCP, using the token method of load balancing. Must be within the first 24 KB of the TCP + payload. + - "Minimum value = C(0)" + - "Maximum value = C(25400)" + + sessionless: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Perform load balancing on a per-packet basis, without establishing sessions. Recommended for load + balancing of intrusion detection system (IDS) servers and scenarios involving direct server return + (DSR), where session information is unnecessary. + + connfailover: + choices: + - 'DISABLED' + - 'STATEFUL' + - 'STATELESS' + description: + - >- + Mode in which the connection failover feature must operate for the virtual server. After a failover, + established TCP connections and UDP packet flows are kept active and resumed on the secondary + appliance. Clients remain connected to the same servers. Available settings function as follows: + - >- + * C(STATEFUL) - The primary appliance shares state information with the secondary appliance, in real + time, resulting in some runtime processing overhead. + - >- + * C(STATELESS) - State information is not shared, and the new primary appliance tries to re-create the + packet flow on the basis of the information contained in the packets it receives. + - "* C(DISABLED) - Connection failover does not occur." + + redirurl: + description: + - "URL to which to redirect traffic if the virtual server becomes unavailable." + - >- + WARNING! Make sure that the domain in the URL does not match the domain specified for a content + switching policy. If it does, requests are continuously redirected to the unavailable virtual server. + - "Minimum length = 1" + + cacheable: + description: + - >- + Route cacheable requests to a cache redirection virtual server. The load balancing virtual server can + forward requests only to a transparent cache redirection virtual server that has an IP address and + port combination of *:80, so such a cache redirection virtual server must be configured on the + appliance. + type: bool + + clttimeout: + description: + - "Idle time, in seconds, after which a client connection is terminated." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:" + - "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold." + - >- + * DYNAMICCONNECTION - Spillover occurs when the number of client connections at the virtual server + exceeds the sum of the maximum client (Max Clients) settings for bound services. Do not specify a + spillover threshold for this setting, because the threshold is implied by the Max Clients settings of + bound services. + - >- + * C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the virtual server's incoming and + outgoing traffic exceeds the threshold. + - >- + * C(HEALTH) - Spillover occurs when the percentage of weights of the services that are UP drops below + the threshold. For example, if services svc1, svc2, and svc3 are bound to a virtual server, with + weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if svc1 and svc3 or svc2 + and svc3 transition to DOWN. + - "* C(NONE) - Spillover does not occur." + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If spillover occurs, maintain source IP address based persistence for both primary and backup virtual + servers. + + sopersistencetimeout: + description: + - "Timeout for spillover persistence, in minutes." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + healththreshold: + description: + - >- + Threshold in percent of active services below which vserver state is made down. If this threshold is + 0, vserver state will be up even if one bound service is up. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + sothreshold: + description: + - >- + Threshold at which spillover occurs. Specify an integer for the C(CONNECTION) spillover method, a + bandwidth value in kilobits per second for the C(BANDWIDTH) method (do not enter the units), or a + percentage for the C(HEALTH) method (do not enter the percentage symbol). + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + redirectportrewrite: + choices: + - 'enabled' + - 'disabled' + description: + - "Rewrite the port and change the protocol to ensure successful HTTP redirects from services." + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a virtual server whose state transitions from UP to + DOWN. Do not enable this option for applications that must complete their transactions. + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If the primary virtual server goes down, do not allow it to return to primary status until manually + enabled. + + insertvserveripport: + choices: + - 'OFF' + - 'VIPADDR' + - 'V6TOV4MAPPING' + description: + - >- + Insert an HTTP header, whose value is the IP address and port number of the virtual server, before + forwarding a request to the server. The format of the header is : _, where vipHeader is the name that you specify for the header. If the virtual + server has an IPv6 address, the address in the header is enclosed in brackets ([ and ]) to separate + it from the port number. If you have mapped an IPv4 address to a virtual server's IPv6 address, the + value of this parameter determines which IP address is inserted in the header, as follows: + - >- + * C(VIPADDR) - Insert the IP address of the virtual server in the HTTP header regardless of whether the + virtual server has an IPv4 address or an IPv6 address. A mapped IPv4 address, if configured, is + ignored. + - >- + * C(V6TOV4MAPPING) - Insert the IPv4 address that is mapped to the virtual server's IPv6 address. If a + mapped IPv4 address is not configured, insert the IPv6 address. + - "* C(OFF) - Disable header insertion." + + vipheader: + description: + - "Name for the inserted header. The default name is vip-header." + - "Minimum length = 1" + + authenticationhost: + description: + - >- + Fully qualified domain name (FQDN) of the authentication virtual server to which the user must be + redirected for authentication. Make sure that the Authentication parameter is set to C(yes). + - "Minimum length = 3" + - "Maximum length = 252" + + authentication: + description: + - "Enable or disable user authentication." + type: bool + + authn401: + description: + - "Enable or disable user authentication with HTTP 401 responses." + type: bool + + authnvsname: + description: + - "Name of an authentication virtual server with which to authenticate users." + - "Minimum length = 1" + - "Maximum length = 252" + + push: + choices: + - 'enabled' + - 'disabled' + description: + - "Process traffic with the push virtual server that is bound to this load balancing virtual server." + + pushvserver: + description: + - >- + Name of the load balancing virtual server, of type PUSH or SSL_PUSH, to which the server pushes + updates received on the load balancing virtual server that you are configuring. + - "Minimum length = 1" + + pushlabel: + description: + - >- + Expression for extracting a label from the server's response. Can be either an expression or the name + of a named expression. + + pushmulticlients: + description: + - >- + Allow multiple Web 2.0 connections from the same client to connect to the virtual server and expect + updates. + type: bool + + tcpprofilename: + description: + - "Name of the TCP profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + dbprofilename: + description: + - "Name of the DB profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + comment: + description: + - "Any comments that you might want to associate with the virtual server." + + l2conn: + description: + - >- + Use Layer 2 parameters (channel number, MAC address, and VLAN ID) in addition to the 4-tuple (::::) that is used to identify a connection. Allows + multiple TCP and non-TCP connections with the same 4-tuple to co-exist on the NetScaler appliance. + type: bool + + oracleserverversion: + choices: + - '10G' + - '11G' + description: + - "Oracle server version." + + mssqlserverversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - >- + For a load balancing virtual server of type C(MSSQL), the Microsoft SQL Server version. Set this + parameter if you expect some clients to run a version different from the version of the database. + This setting provides compatibility between the client-side and server-side connections by ensuring + that all communication conforms to the server's version. + + mysqlprotocolversion: + description: + - "MySQL protocol version that the virtual server advertises to clients." + + mysqlserverversion: + description: + - "MySQL server version string that the virtual server advertises to clients." + - "Minimum length = 1" + - "Maximum length = 31" + + mysqlcharacterset: + description: + - "Character set that the virtual server advertises to clients." + + mysqlservercapabilities: + description: + - "Server capabilities that the virtual server advertises to clients." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Apply AppFlow logging to the virtual server." + + netprofile: + description: + - >- + Name of the network profile to associate with the virtual server. If you set this parameter, the + virtual server uses only the IP addresses in the network profile as source IP addresses when + initiating connections with servers. + - "Minimum length = 1" + - "Maximum length = 127" + + icmpvsrresponse: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - >- + How the NetScaler appliance responds to ping requests received for an IP address that is common to + one or more virtual servers. Available settings function as follows: + - >- + * If set to C(PASSIVE) on all the virtual servers that share the IP address, the appliance always + responds to the ping requests. + - >- + * If set to C(ACTIVE) on all the virtual servers that share the IP address, the appliance responds to + the ping requests if at least one of the virtual servers is UP. Otherwise, the appliance does not + respond. + - >- + * If set to C(ACTIVE) on some virtual servers and PASSIVE on the others, the appliance responds if at + least one virtual server with the ACTIVE setting is UP. Otherwise, the appliance does not respond. + - >- + Note: This parameter is available at the virtual server level. A similar parameter, ICMP Response, is + available at the IP address level, for IPv4 addresses of type VIP. To set that parameter, use the add + ip command in the CLI or the Create IP dialog box in the GUI. + + rhistate: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - >- + Route Health Injection (RHI) functionality of the NetSaler appliance for advertising the route of the + VIP address associated with the virtual server. When Vserver RHI Level (RHI) parameter is set to + VSVR_CNTRLD, the following are different RHI behaviors for the VIP address on the basis of RHIstate + (RHI STATE) settings on the virtual servers associated with the VIP address: + - >- + * If you set C(rhistate) to C(PASSIVE) on all virtual servers, the NetScaler ADC always advertises the + route for the VIP address. + - >- + * If you set C(rhistate) to C(ACTIVE) on all virtual servers, the NetScaler ADC advertises the route for + the VIP address if at least one of the associated virtual servers is in UP state. + - >- + * If you set C(rhistate) to C(ACTIVE) on some and PASSIVE on others, the NetScaler ADC advertises the + route for the VIP address if at least one of the associated virtual servers, whose C(rhistate) set to + C(ACTIVE), is in UP state. + + newservicerequest: + description: + - >- + Number of requests, or percentage of the load on existing services, by which to increase the load on + a new service at each interval in slow-start mode. A non-zero value indicates that slow-start is + applicable. A zero value indicates that the global RR startup parameter is applied. Changing the + value to zero will cause services currently in slow start to take the full traffic as determined by + the LB method. Subsequently, any new services added will use the global RR factor. + + newservicerequestunit: + choices: + - 'PER_SECOND' + - 'PERCENT' + description: + - "Units in which to increment load at each interval in slow-start mode." + + newservicerequestincrementinterval: + description: + - >- + Interval, in seconds, between successive increments in the load on a new service or a service whose + state has just changed from DOWN to UP. A value of 0 (zero) specifies manual slow start. + - "Minimum value = C(0)" + - "Maximum value = C(3600)" + + minautoscalemembers: + description: + - "Minimum number of members expected to be present when vserver is used in Autoscale." + - "Minimum value = C(0)" + - "Maximum value = C(5000)" + + maxautoscalemembers: + description: + - "Maximum number of members expected to be present when vserver is used in Autoscale." + - "Minimum value = C(0)" + - "Maximum value = C(5000)" + + persistavpno: + description: + - "Persist AVP number for Diameter Persistency." + - "In case this AVP is not defined in Base RFC 3588 and it is nested inside a Grouped AVP," + - "define a sequence of AVP numbers (max 3) in order of parent to child. So say persist AVP number X" + - "is nested inside AVP Y which is nested in Z, then define the list as Z Y X." + - "Minimum value = C(1)" + + skippersistency: + choices: + - 'Bypass' + - 'ReLb' + - 'None' + description: + - >- + This argument decides the behavior incase the service which is selected from an existing persistence + session has reached threshold. + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = C(0)" + - "Maximum value = C(4094)" + + authnprofile: + description: + - "Name of the authentication profile to be used when authentication is turned on." + + macmoderetainvlan: + choices: + - 'enabled' + - 'disabled' + description: + - "This option is used to retain vlan information of incoming packet when macmode is enabled." + + dbslb: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable database specific load balancing for MySQL and MSSQL service types." + + dns64: + choices: + - 'enabled' + - 'disabled' + description: + - "This argument is for enabling/disabling the C(dns64) on lbvserver." + + bypassaaaa: + description: + - >- + If this option is enabled while resolving DNS64 query AAAA queries are not sent to back end dns + server. + type: bool + + recursionavailable: + description: + - >- + When set to YES, this option causes the DNS replies from this vserver to have the RA bit turned on. + Typically one would set this option to YES, when the vserver is load balancing a set of DNS servers + thatsupport recursive queries. + type: bool + + processlocal: + choices: + - 'enabled' + - 'disabled' + description: + - >- + By turning on this option packets destined to a vserver in a cluster will not under go any steering. + Turn this option for single packet request response mode or when the upstream device is performing a + proper RSS for connection based distribution. + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the VServer. DNS profile properties will be applied to + the transactions processed by a VServer. This parameter is valid only for DNS and DNS-TCP VServers. + - "Minimum length = 1" + - "Maximum length = 127" + + servicebindings: + description: + - List of services along with the weights that are load balanced. + - The following suboptions are available. + suboptions: + servicename: + description: + - "Service to bind to the virtual server." + - "Minimum length = 1" + weight: + description: + - "Weight to assign to the specified service." + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + servicegroupbindings: + description: + - List of service groups along with the weights that are load balanced. + - The following suboptions are available. + suboptions: + servicegroupname: + description: + - "The service group name bound to the selected load balancing virtual server." + weight: + description: + - >- + Integer specifying the weight of the service. A larger number specifies a greater weight. Defines the + capacity of the service relative to the other services in the load balancing configuration. + Determines the priority given to the service in load balancing decisions. + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + ssl_certkey: + description: + - The name of the ssl certificate that is bound to this service. + - The ssl certificate must already exist. + - Creating the certificate can be done with the M(community.network.netscaler_ssl_certkey) module. + - This option is only applicable only when C(servicetype) is C(SSL). + + disabled: + description: + - When set to C(yes) the lb vserver will be disabled. + - When set to C(no) the lb vserver will be enabled. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: 'no' + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# Netscaler services service-http-1, service-http-2 must have been already created with the netscaler_service module + +- name: Create a load balancing vserver bound to services + delegate_to: localhost + community.network.netscaler_lb_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + name: lb_vserver_1 + servicetype: HTTP + timeout: 12 + ipv46: 6.93.3.3 + port: 80 + servicebindings: + - servicename: service-http-1 + weight: 80 + - servicename: service-http-2 + weight: 20 + +# Service group service-group-1 must have been already created with the netscaler_servicegroup module + +- name: Create load balancing vserver bound to servicegroup + delegate_to: localhost + community.network.netscaler_lb_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + state: present + + name: lb_vserver_2 + servicetype: HTTP + ipv46: 6.92.2.2 + port: 80 + timeout: 10 + servicegroupbindings: + - servicegroupname: service-group-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + get_immutables_intersection, + ensure_feature_is_enabled +) +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver import lbvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver_servicegroup_binding import lbvserver_servicegroup_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding import lbvserver_service_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding import sslvserver_sslcertkey_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + + PYTHON_SDK_IMPORTED = True +except ImportError as e: + IMPORT_ERROR = str(e) + PYTHON_SDK_IMPORTED = False + + +def lb_vserver_exists(client, module): + log('Checking if lb vserver exists') + if lbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def lb_vserver_identical(client, module, lbvserver_proxy): + log('Checking if configured lb vserver is identical') + lbvserver_list = lbvserver.get_filtered(client, 'name:%s' % module.params['name']) + if lbvserver_proxy.has_equal_attributes(lbvserver_list[0]): + return True + else: + return False + + +def lb_vserver_diff(client, module, lbvserver_proxy): + lbvserver_list = lbvserver.get_filtered(client, 'name:%s' % module.params['name']) + return lbvserver_proxy.diff_object(lbvserver_list[0]) + + +def get_configured_service_bindings(client, module): + log('Getting configured service bindings') + + readwrite_attrs = [ + 'weight', + 'name', + 'servicename', + 'servicegroupname' + ] + readonly_attrs = [ + 'preferredlocation', + 'vserverid', + 'vsvrbindsvcip', + 'servicetype', + 'cookieipport', + 'port', + 'vsvrbindsvcport', + 'curstate', + 'ipv46', + 'dynamicweight', + ] + + configured_bindings = {} + if 'servicebindings' in module.params and module.params['servicebindings'] is not None: + for binding in module.params['servicebindings']: + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['name'] = module.params['name'] + key = binding['servicename'].strip() + configured_bindings[key] = ConfigProxy( + actual=lbvserver_service_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + return configured_bindings + + +def get_configured_servicegroup_bindings(client, module): + log('Getting configured service group bindings') + readwrite_attrs = [ + 'weight', + 'name', + 'servicename', + 'servicegroupname', + ] + readonly_attrs = [] + + configured_bindings = {} + + if 'servicegroupbindings' in module.params and module.params['servicegroupbindings'] is not None: + for binding in module.params['servicegroupbindings']: + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['name'] = module.params['name'] + key = binding['servicegroupname'].strip() + configured_bindings[key] = ConfigProxy( + actual=lbvserver_servicegroup_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + + return configured_bindings + + +def get_actual_service_bindings(client, module): + log('Getting actual service bindings') + bindings = {} + try: + if lbvserver_service_binding.count(client, module.params['name']) == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + bindigs_list = lbvserver_service_binding.get(client, module.params['name']) + + for item in bindigs_list: + key = item.servicename + bindings[key] = item + + return bindings + + +def get_actual_servicegroup_bindings(client, module): + log('Getting actual service group bindings') + bindings = {} + + try: + if lbvserver_servicegroup_binding.count(client, module.params['name']) == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + bindigs_list = lbvserver_servicegroup_binding.get(client, module.params['name']) + + for item in bindigs_list: + key = item.servicegroupname + bindings[key] = item + + return bindings + + +def service_bindings_identical(client, module): + log('service_bindings_identical') + + # Compare service keysets + configured_service_bindings = get_configured_service_bindings(client, module) + service_bindings = get_actual_service_bindings(client, module) + configured_keyset = set(configured_service_bindings.keys()) + service_keyset = set(service_bindings.keys()) + if len(configured_keyset ^ service_keyset) > 0: + return False + + # Compare service item to item + for key in configured_service_bindings.keys(): + conf = configured_service_bindings[key] + serv = service_bindings[key] + log('s diff %s' % conf.diff_object(serv)) + if not conf.has_equal_attributes(serv): + return False + + # Fallthrough to success + return True + + +def servicegroup_bindings_identical(client, module): + log('servicegroup_bindings_identical') + + # Compare servicegroup keysets + configured_servicegroup_bindings = get_configured_servicegroup_bindings(client, module) + servicegroup_bindings = get_actual_servicegroup_bindings(client, module) + configured_keyset = set(configured_servicegroup_bindings.keys()) + service_keyset = set(servicegroup_bindings.keys()) + log('len %s' % len(configured_keyset ^ service_keyset)) + if len(configured_keyset ^ service_keyset) > 0: + return False + + # Compare servicegroup item to item + for key in configured_servicegroup_bindings.keys(): + conf = configured_servicegroup_bindings[key] + serv = servicegroup_bindings[key] + log('sg diff %s' % conf.diff_object(serv)) + if not conf.has_equal_attributes(serv): + return False + + # Fallthrough to success + return True + + +def sync_service_bindings(client, module): + log('sync_service_bindings') + + actual_bindings = get_actual_service_bindings(client, module) + configured_bindigns = get_configured_service_bindings(client, module) + + # Delete actual but not configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindigns.keys())) + for key in delete_keys: + log('Deleting service binding %s' % key) + actual_bindings[key].servicegroupname = '' + actual_bindings[key].delete(client, actual_bindings[key]) + + # Add configured but not in actual + add_keys = list(set(configured_bindigns.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding service binding %s' % key) + configured_bindigns[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindigns.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindigns[key].has_equal_attributes(actual_bindings[key]): + log('Updating service binding %s' % key) + actual_bindings[key].servicegroupname = '' + actual_bindings[key].delete(client, actual_bindings[key]) + configured_bindigns[key].add() + + +def sync_servicegroup_bindings(client, module): + log('sync_servicegroup_bindings') + + actual_bindings = get_actual_servicegroup_bindings(client, module) + configured_bindigns = get_configured_servicegroup_bindings(client, module) + + # Delete actual but not configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindigns.keys())) + for key in delete_keys: + log('Deleting servicegroup binding %s' % key) + actual_bindings[key].servicename = None + actual_bindings[key].delete(client, actual_bindings[key]) + + # Add configured but not in actual + add_keys = list(set(configured_bindigns.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding servicegroup binding %s' % key) + configured_bindigns[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindigns.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindigns[key].has_equal_attributes(actual_bindings[key]): + log('Updating servicegroup binding %s' % key) + actual_bindings[key].servicename = None + actual_bindings[key].delete(client, actual_bindings[key]) + configured_bindigns[key].add() + + +def ssl_certkey_bindings_identical(client, module): + log('Entering ssl_certkey_bindings_identical') + vservername = module.params['name'] + + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + log('Existing certs %s' % bindings) + + if module.params['ssl_certkey'] is None: + if len(bindings) == 0: + return True + else: + return False + else: + certificate_list = [item.certkeyname for item in bindings] + log('certificate_list %s' % certificate_list) + if certificate_list == [module.params['ssl_certkey']]: + return True + else: + return False + + +def ssl_certkey_bindings_sync(client, module): + log('Syncing ssl certificates') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + log('bindings len is %s' % len(bindings)) + + # Delete existing bindings + for binding in bindings: + sslvserver_sslcertkey_binding.delete(client, binding) + + # Add binding if appropriate + if module.params['ssl_certkey'] is not None: + binding = sslvserver_sslcertkey_binding() + binding.vservername = module.params['name'] + binding.certkeyname = module.params['ssl_certkey'] + sslvserver_sslcertkey_binding.add(client, binding) + + +def do_state_change(client, module, lbvserver_proxy): + if module.params['disabled']: + log('Disabling lb server') + result = lbvserver.disable(client, lbvserver_proxy.actual) + else: + log('Enabling lb server') + result = lbvserver.enable(client, lbvserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'DNS', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'RTSP', + 'PUSH', + 'SSL_PUSH', + 'RADIUS', + 'RDP', + 'MYSQL', + 'MSSQL', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'ORACLE', + 'SMPP', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX', + ] + ), + ipv46=dict(type='str'), + ippattern=dict(type='str'), + ipmask=dict(type='str'), + port=dict(type='int'), + range=dict(type='float'), + persistencetype=dict( + type='str', + choices=[ + 'SOURCEIP', + 'COOKIEINSERT', + 'SSLSESSION', + 'RULE', + 'URLPASSIVE', + 'CUSTOMSERVERID', + 'DESTIP', + 'SRCIPDESTIP', + 'CALLID', + 'RTSPSID', + 'DIAMETER', + 'FIXSESSION', + 'NONE', + ] + ), + timeout=dict(type='float'), + persistencebackup=dict( + type='str', + choices=[ + 'SOURCEIP', + 'NONE', + ] + ), + backuppersistencetimeout=dict(type='float'), + lbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'URLHASH', + 'DOMAINHASH', + 'DESTINATIONIPHASH', + 'SOURCEIPHASH', + 'SRCIPDESTIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'TOKEN', + 'SRCIPSRCPORTHASH', + 'LRTM', + 'CALLIDHASH', + 'CUSTOMLOAD', + 'LEASTREQUEST', + 'AUDITLOGHASH', + 'STATICPROXIMITY', + ] + ), + hashlength=dict(type='float'), + netmask=dict(type='str'), + v6netmasklen=dict(type='float'), + backuplbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'CUSTOMLOAD', + ] + ), + cookiename=dict(type='str'), + listenpolicy=dict(type='str'), + listenpriority=dict(type='float'), + persistmask=dict(type='str'), + v6persistmasklen=dict(type='float'), + rtspnat=dict(type='bool'), + m=dict( + type='str', + choices=[ + 'IP', + 'MAC', + 'IPTUNNEL', + 'TOS', + ] + ), + tosid=dict(type='float'), + datalength=dict(type='float'), + dataoffset=dict(type='float'), + sessionless=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + connfailover=dict( + type='str', + choices=[ + 'DISABLED', + 'STATEFUL', + 'STATELESS', + ] + ), + redirurl=dict(type='str'), + cacheable=dict(type='bool'), + clttimeout=dict(type='float'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + healththreshold=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + redirectportrewrite=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + insertvserveripport=dict( + type='str', + choices=[ + 'OFF', + 'VIPADDR', + 'V6TOV4MAPPING', + ] + ), + vipheader=dict(type='str'), + authenticationhost=dict(type='str'), + authentication=dict(type='bool'), + authn401=dict(type='bool'), + authnvsname=dict(type='str'), + push=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + pushvserver=dict(type='str'), + pushlabel=dict(type='str'), + pushmulticlients=dict(type='bool'), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + dbprofilename=dict(type='str'), + comment=dict(type='str'), + l2conn=dict(type='bool'), + oracleserverversion=dict( + type='str', + choices=[ + '10G', + '11G', + ] + ), + mssqlserverversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + mysqlprotocolversion=dict(type='float'), + mysqlserverversion=dict(type='str'), + mysqlcharacterset=dict(type='float'), + mysqlservercapabilities=dict(type='float'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + icmpvsrresponse=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + rhistate=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + newservicerequest=dict(type='float'), + newservicerequestunit=dict( + type='str', + choices=[ + 'PER_SECOND', + 'PERCENT', + ] + ), + newservicerequestincrementinterval=dict(type='float'), + minautoscalemembers=dict(type='float'), + maxautoscalemembers=dict(type='float'), + skippersistency=dict( + type='str', + choices=[ + 'Bypass', + 'ReLb', + 'None', + ] + ), + authnprofile=dict(type='str'), + macmoderetainvlan=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dbslb=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dns64=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + bypassaaaa=dict(type='bool'), + recursionavailable=dict(type='bool'), + processlocal=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dnsprofilename=dict(type='str'), + ) + + hand_inserted_arguments = dict( + servicebindings=dict(type='list'), + servicegroupbindings=dict(type='list'), + ssl_certkey=dict(type='str'), + disabled=dict( + type='bool', + default=False + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'servicetype', + 'ipv46', + 'ippattern', + 'ipmask', + 'port', + 'range', + 'persistencetype', + 'timeout', + 'persistencebackup', + 'backuppersistencetimeout', + 'lbmethod', + 'hashlength', + 'netmask', + 'v6netmasklen', + 'backuplbmethod', + 'cookiename', + 'listenpolicy', + 'listenpriority', + 'persistmask', + 'v6persistmasklen', + 'rtspnat', + 'm', + 'tosid', + 'datalength', + 'dataoffset', + 'sessionless', + 'connfailover', + 'redirurl', + 'cacheable', + 'clttimeout', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'healththreshold', + 'sothreshold', + 'sobackupaction', + 'redirectportrewrite', + 'downstateflush', + 'disableprimaryondown', + 'insertvserveripport', + 'vipheader', + 'authenticationhost', + 'authentication', + 'authn401', + 'authnvsname', + 'push', + 'pushvserver', + 'pushlabel', + 'pushmulticlients', + 'tcpprofilename', + 'httpprofilename', + 'dbprofilename', + 'comment', + 'l2conn', + 'oracleserverversion', + 'mssqlserverversion', + 'mysqlprotocolversion', + 'mysqlserverversion', + 'mysqlcharacterset', + 'mysqlservercapabilities', + 'appflowlog', + 'netprofile', + 'icmpvsrresponse', + 'rhistate', + 'newservicerequest', + 'newservicerequestunit', + 'newservicerequestincrementinterval', + 'minautoscalemembers', + 'maxautoscalemembers', + 'skippersistency', + 'authnprofile', + 'macmoderetainvlan', + 'dbslb', + 'dns64', + 'bypassaaaa', + 'recursionavailable', + 'processlocal', + 'dnsprofilename', + ] + + readonly_attrs = [ + 'value', + 'ipmapping', + 'ngname', + 'type', + 'curstate', + 'effectivestate', + 'status', + 'lbrrreason', + 'redirect', + 'precedence', + 'homepage', + 'dnsvservername', + 'domain', + 'policyname', + 'cachevserver', + 'health', + 'gotopriorityexpression', + 'ruletype', + 'groupname', + 'cookiedomain', + 'map', + 'gt2gb', + 'consolidatedlconn', + 'consolidatedlconngbl', + 'thresholdvalue', + 'bindpoint', + 'invoke', + 'labeltype', + 'labelname', + 'version', + 'totalservices', + 'activeservices', + 'statechangetimesec', + 'statechangetimeseconds', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'isgslb', + 'vsvrdynconnsothreshold', + 'backupvserverstatus', + '__count', + ] + + immutable_attrs = [ + 'name', + 'servicetype', + 'ipv46', + 'port', + 'range', + 'state', + 'redirurl', + 'vipheader', + 'newservicerequestunit', + 'td', + ] + + transforms = { + 'rtspnat': ['bool_on_off'], + 'authn401': ['bool_on_off'], + 'bypassaaaa': ['bool_yes_no'], + 'authentication': ['bool_on_off'], + 'cacheable': ['bool_yes_no'], + 'l2conn': ['bool_on_off'], + 'pushmulticlients': ['bool_yes_no'], + 'recursionavailable': ['bool_yes_no'], + 'sessionless': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'redirectportrewrite': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'push': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + 'macmoderetainvlan': [lambda v: v.upper()], + 'dbslb': [lambda v: v.upper()], + 'dns64': [lambda v: v.upper()], + 'processlocal': [lambda v: v.upper()], + } + + lbvserver_proxy = ConfigProxy( + actual=lbvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'LB') + if module.params['state'] == 'present': + log('Applying actions for state present') + + if not lb_vserver_exists(client, module): + log('Add lb vserver') + if not module.check_mode: + lbvserver_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not lb_vserver_identical(client, module, lbvserver_proxy): + + # Check if we try to change value of immutable attributes + diff_dict = lb_vserver_diff(client, module, lbvserver_proxy) + immutables_changed = get_immutables_intersection(lbvserver_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + log('Update lb vserver') + if not module.check_mode: + lbvserver_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Present noop') + + if not service_bindings_identical(client, module): + if not module.check_mode: + sync_service_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not servicegroup_bindings_identical(client, module): + if not module.check_mode: + sync_servicegroup_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if module.params['servicetype'] != 'SSL' and module.params['ssl_certkey'] is not None: + module.fail_json(msg='ssl_certkey is applicable only to SSL vservers', **module_result) + + # Check if SSL certkey is sane + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + if not module.check_mode: + ssl_certkey_bindings_sync(client, module) + + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, lbvserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check + log('Sanity checks for state present') + if not module.check_mode: + if not lb_vserver_exists(client, module): + module.fail_json(msg='Did not create lb vserver', **module_result) + + if not lb_vserver_identical(client, module, lbvserver_proxy): + msg = 'lb vserver is not configured correctly' + module.fail_json(msg=msg, diff=lb_vserver_diff(client, module, lbvserver_proxy), **module_result) + + if not service_bindings_identical(client, module): + module.fail_json(msg='service bindings are not identical', **module_result) + + if not servicegroup_bindings_identical(client, module): + module.fail_json(msg='servicegroup bindings are not identical', **module_result) + + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + module.fail_json(msg='sll certkey bindings not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if lb_vserver_exists(client, module): + if not module.check_mode: + log('Delete lb vserver') + lbvserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Absent noop') + module_result['changed'] = False + + # Sanity check + log('Sanity checks for state absent') + if not module.check_mode: + if lb_vserver_exists(client, module): + module.fail_json(msg='lb vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_nitro_request.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_nitro_request.py new file mode 100644 index 00000000..31292c7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_nitro_request.py @@ -0,0 +1,902 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_nitro_request +short_description: Issue Nitro API requests to a Netscaler instance. +description: + - Issue Nitro API requests to a Netscaler instance. + - This is intended to be a short hand for using the uri Ansible module to issue the raw HTTP requests directly. + - It provides consistent return values and has no other dependencies apart from the base Ansible runtime environment. + - This module is intended to run either on the Ansible control node or a bastion (jumpserver) with access to the actual Netscaler instance + + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + nsip: + description: + - The IP address of the Netscaler or MAS instance where the Nitro API calls will be made. + - "The port can be specified with the colon C(:). E.g. C(192.168.1.1:555)." + + nitro_user: + description: + - The username with which to authenticate to the Netscaler node. + required: true + + nitro_pass: + description: + - The password with which to authenticate to the Netscaler node. + required: true + + nitro_protocol: + choices: [ 'http', 'https' ] + default: http + description: + - Which protocol to use when accessing the Nitro API objects. + + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. + default: 'yes' + type: bool + + nitro_auth_token: + description: + - The authentication token provided by the C(mas_login) operation. It is required when issuing Nitro API calls through a MAS proxy. + + resource: + description: + - The type of resource we are operating on. + - It is required for all I(operation) values except C(mas_login) and C(save_config). + + name: + description: + - The name of the resource we are operating on. + - "It is required for the following I(operation) values: C(update), C(get), C(delete)." + + attributes: + description: + - The attributes of the Nitro object we are operating on. + - "It is required for the following I(operation) values: C(add), C(update), C(action)." + + args: + description: + - A dictionary which defines the key arguments by which we will select the Nitro object to operate on. + - "It is required for the following I(operation) values: C(get_by_args), C('delete_by_args')." + + filter: + description: + - A dictionary which defines the filter with which to refine the Nitro objects returned by the C(get_filtered) I(operation). + + operation: + description: + - Define the Nitro operation that we want to perform. + choices: + - add + - update + - get + - get_by_args + - get_filtered + - get_all + - delete + - delete_by_args + - count + - mas_login + - save_config + - action + + expected_nitro_errorcode: + description: + - A list of numeric values that signify that the operation was successful. + default: [0] + required: true + + action: + description: + - The action to perform when the I(operation) value is set to C(action). + - Some common values for this parameter are C(enable), C(disable), C(rename). + + instance_ip: + description: + - The IP address of the target Netscaler instance when issuing a Nitro request through a MAS proxy. + + instance_name: + description: + - The name of the target Netscaler instance when issuing a Nitro request through a MAS proxy. + + instance_id: + description: + - The id of the target Netscaler instance when issuing a Nitro request through a MAS proxy. +''' + +EXAMPLES = ''' +- name: Add a server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: add + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.1 + +- name: Update server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: update + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.2 + +- name: Get server + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get + resource: server + name: test-server-1 + +- name: Delete server + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: delete + resource: server + name: test-server-1 + +- name: Rename server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: action + action: rename + resource: server + attributes: + name: test-server-1 + newname: test-server-2 + +- name: Get server by args + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get_by_args + resource: server + args: + name: test-server-1 + +- name: Get server by filter + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get_filtered + resource: server + filter: + ipaddress: 192.168.1.2 + +# Doing a NITRO request through MAS. +# Requires to have an authentication token from the mas_login and used as the nitro_auth_token parameter +# Also nsip is the MAS address and the target Netscaler IP must be defined with instance_ip +# The rest of the task arguments remain the same as when issuing the NITRO request directly to a Netscaler instance. + +- name: Do mas login + delegate_to: localhost + register: login_result + community.network.netscaler_nitro_request: + nsip: "{{ mas_ip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: mas_login + +- name: Add resource through MAS proxy + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ mas_ip }}" + nitro_auth_token: "{{ login_result.nitro_auth_token }}" + instance_ip: "{{ nsip }}" + operation: add + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.7 +''' + +RETURN = ''' +nitro_errorcode: + description: A numeric value containing the return code of the NITRO operation. When 0 the operation is successful. Any non zero value indicates an error. + returned: always + type: int + sample: 0 + +nitro_message: + description: A string containing a human readable explanation for the NITRO operation result. + returned: always + type: str + sample: Success + +nitro_severity: + description: A string describing the severity of the NITRO operation error or NONE. + returned: always + type: str + sample: NONE + +http_response_data: + description: A dictionary that contains all the HTTP response's data. + returned: always + type: dict + sample: "status: 200" + +http_response_body: + description: A string with the actual HTTP response body content if existent. If there is no HTTP response body it is an empty string. + returned: always + type: str + sample: "{ errorcode: 0, message: Done, severity: NONE }" + +nitro_object: + description: The object returned from the NITRO operation. This is applicable to the various get operations which return an object. + returned: when applicable + type: list + sample: + - + ipaddress: "192.168.1.8" + ipv6address: "NO" + maxbandwidth: "0" + name: "test-server-1" + port: 0 + sp: "OFF" + state: "ENABLED" + +nitro_auth_token: + description: The token returned by the C(mas_login) operation when successful. + returned: when applicable + type: str + sample: "##E8D7D74DDBD907EE579E8BB8FF4529655F22227C1C82A34BFC93C9539D66" +''' + + +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.basic import AnsibleModule +import codecs + + +class NitroAPICaller(object): + + _argument_spec = dict( + nsip=dict( + fallback=(env_fallback, ['NETSCALER_NSIP']), + ), + nitro_user=dict( + fallback=(env_fallback, ['NETSCALER_NITRO_USER']), + ), + nitro_pass=dict( + fallback=(env_fallback, ['NETSCALER_NITRO_PASS']), + no_log=True + ), + nitro_protocol=dict( + choices=['http', 'https'], + fallback=(env_fallback, ['NETSCALER_NITRO_PROTOCOL']), + default='http' + ), + validate_certs=dict( + default=True, + type='bool' + ), + nitro_auth_token=dict( + type='str', + no_log=True + ), + resource=dict(type='str'), + name=dict(type='str'), + attributes=dict(type='dict'), + + args=dict(type='dict'), + filter=dict(type='dict'), + + operation=dict( + type='str', + required=True, + choices=[ + 'add', + 'update', + 'get', + 'get_by_args', + 'get_filtered', + 'get_all', + 'delete', + 'delete_by_args', + 'count', + + 'mas_login', + + # Actions + 'save_config', + + # Generic action handler + 'action', + ] + ), + expected_nitro_errorcode=dict( + type='list', + default=[0], + ), + action=dict(type='str'), + instance_ip=dict(type='str'), + instance_name=dict(type='str'), + instance_id=dict(type='str'), + ) + + def __init__(self): + + self._module = AnsibleModule( + argument_spec=self._argument_spec, + supports_check_mode=False, + ) + + self._module_result = dict( + failed=False, + ) + + # Prepare the http headers according to module arguments + self._headers = {} + self._headers['Content-Type'] = 'application/json' + + # Check for conflicting authentication methods + have_token = self._module.params['nitro_auth_token'] is not None + have_userpass = None not in (self._module.params['nitro_user'], self._module.params['nitro_pass']) + login_operation = self._module.params['operation'] == 'mas_login' + + if have_token and have_userpass: + self.fail_module(msg='Cannot define both authentication token and username/password') + + if have_token: + self._headers['Cookie'] = "NITRO_AUTH_TOKEN=%s" % self._module.params['nitro_auth_token'] + + if have_userpass and not login_operation: + self._headers['X-NITRO-USER'] = self._module.params['nitro_user'] + self._headers['X-NITRO-PASS'] = self._module.params['nitro_pass'] + + # Do header manipulation when doing a MAS proxy call + if self._module.params['instance_ip'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_IP'] = self._module.params['instance_ip'] + elif self._module.params['instance_name'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_NAME'] = self._module.params['instance_name'] + elif self._module.params['instance_id'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_ID'] = self._module.params['instance_id'] + + def edit_response_data(self, r, info, result, success_status): + # Search for body in both http body and http data + if r is not None: + result['http_response_body'] = codecs.decode(r.read(), 'utf-8') + elif 'body' in info: + result['http_response_body'] = codecs.decode(info['body'], 'utf-8') + del info['body'] + else: + result['http_response_body'] = '' + + result['http_response_data'] = info + + # Update the nitro_* parameters according to expected success_status + # Use explicit return values from http response or deduce from http status code + + # Nitro return code in http data + result['nitro_errorcode'] = None + result['nitro_message'] = None + result['nitro_severity'] = None + + if result['http_response_body'] != '': + try: + data = self._module.from_json(result['http_response_body']) + except ValueError: + data = {} + result['nitro_errorcode'] = data.get('errorcode') + result['nitro_message'] = data.get('message') + result['nitro_severity'] = data.get('severity') + + # If we do not have the nitro errorcode from body deduce it from the http status + if result['nitro_errorcode'] is None: + # HTTP status failed + if result['http_response_data'].get('status') != success_status: + result['nitro_errorcode'] = -1 + result['nitro_message'] = result['http_response_data'].get('msg', 'HTTP status %s' % result['http_response_data']['status']) + result['nitro_severity'] = 'ERROR' + # HTTP status succeeded + else: + result['nitro_errorcode'] = 0 + result['nitro_message'] = 'Success' + result['nitro_severity'] = 'NONE' + + def handle_get_return_object(self, result): + result['nitro_object'] = [] + if result['nitro_errorcode'] == 0: + if result['http_response_body'] != '': + data = self._module.from_json(result['http_response_body']) + if self._module.params['resource'] in data: + result['nitro_object'] = data[self._module.params['resource']] + else: + del result['nitro_object'] + + def fail_module(self, msg, **kwargs): + self._module_result['failed'] = True + self._module_result['changed'] = False + self._module_result.update(kwargs) + self._module_result['msg'] = msg + self._module.fail_json(**self._module_result) + + def main(self): + if self._module.params['operation'] == 'add': + result = self.add() + + if self._module.params['operation'] == 'update': + result = self.update() + + if self._module.params['operation'] == 'delete': + result = self.delete() + + if self._module.params['operation'] == 'delete_by_args': + result = self.delete_by_args() + + if self._module.params['operation'] == 'get': + result = self.get() + + if self._module.params['operation'] == 'get_by_args': + result = self.get_by_args() + + if self._module.params['operation'] == 'get_filtered': + result = self.get_filtered() + + if self._module.params['operation'] == 'get_all': + result = self.get_all() + + if self._module.params['operation'] == 'count': + result = self.count() + + if self._module.params['operation'] == 'mas_login': + result = self.mas_login() + + if self._module.params['operation'] == 'action': + result = self.action() + + if self._module.params['operation'] == 'save_config': + result = self.save_config() + + if result['nitro_errorcode'] not in self._module.params['expected_nitro_errorcode']: + self.fail_module(msg='NITRO Failure', **result) + + self._module_result.update(result) + self._module.exit_json(**self._module_result) + + def exit_module(self): + self._module.exit_json() + + def add(self): + # Check if required attributes are present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=201) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def update(self): + # Check if required attributes are arguments present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource name is undefined.') + + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='PUT', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def get(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource name is undefined.') + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_by_args(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['args'] is None: + self.fail_module(msg='NITRO args is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + args_dict = self._module.params['args'] + args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict]) + + args = 'args=' + args + + url = '?'.join([url, args]) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + result = {} + self.edit_response_data(r, info, result, success_status=200) + + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_filtered(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['filter'] is None: + self.fail_module(msg='NITRO filter is undefined.') + + filter_str = ','.join('%s:%s' % (k, v) for k, v in self._module.params['filter'].items()) + + url = '%s://%s/nitro/v1/config/%s?filter=%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + filter_str, + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_all(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + print('headers %s' % self._headers) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def delete(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + # Deletion by name takes precedence over deletion by attributes + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='DELETE', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def delete_by_args(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['args'] is None: + self.fail_module(msg='NITRO args is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + args_dict = self._module.params['args'] + args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict]) + + args = 'args=' + args + + url = '?'.join([url, args]) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='DELETE', + ) + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def count(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + url = '%s://%s/nitro/v1/config/%s?count=yes' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result) + + if result['http_response_body'] != '': + data = self._module.from_json(result['http_response_body']) + + result['nitro_errorcode'] = data['errorcode'] + result['nitro_message'] = data['message'] + result['nitro_severity'] = data['severity'] + if self._module.params['resource'] in data: + result['nitro_count'] = data[self._module.params['resource']][0]['__count'] + + self._module_result['changed'] = False + + return result + + def action(self): + # Check if required attributes are present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + if self._module.params['action'] is None: + self.fail_module(msg='NITRO action is undefined.') + + url = '%s://%s/nitro/v1/config/%s?action=%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['action'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def mas_login(self): + url = '%s://%s/nitro/v1/config/login' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + ) + + login_credentials = { + 'login': { + + 'username': self._module.params['nitro_user'], + 'password': self._module.params['nitro_pass'], + } + } + + data = 'object=\n%s' % self._module.jsonify(login_credentials) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + print(r, info) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + body_data = self._module.from_json(result['http_response_body']) + result['nitro_auth_token'] = body_data['login'][0]['sessionid'] + + self._module_result['changed'] = False + + return result + + def save_config(self): + + url = '%s://%s/nitro/v1/config/nsconfig?action=save' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + ) + + data = self._module.jsonify( + { + 'nsconfig': {}, + } + ) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=200) + self._module_result['changed'] = False + + return result + + +def main(): + + nitro_api_caller = NitroAPICaller() + nitro_api_caller.main() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_save_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_save_config.py new file mode 100644 index 00000000..bcc43f10 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_save_config.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_save_config +short_description: Save Netscaler configuration. +description: + - This module unconditionally saves the configuration on the target netscaler node. + - This module does not support check mode. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + nsip: + description: + - The ip address of the netscaler appliance where the nitro API calls will be made. + - "The port can be specified with the colon (:). E.g. C(192.168.1.1:555)." + required: True + + nitro_user: + description: + - The username with which to authenticate to the netscaler node. + required: True + + nitro_pass: + description: + - The password with which to authenticate to the netscaler node. + required: True + + nitro_protocol: + choices: [ 'http', 'https' ] + default: http + description: + - Which protocol to use when accessing the nitro API objects. + + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. + required: false + default: 'yes' + type: bool + + nitro_timeout: + description: + - Time in seconds until a timeout error is thrown when establishing a new session with Netscaler. + default: 310 + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +--- +- name: Save netscaler configuration + delegate_to: localhost + community.network.netscaler_save_config: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + +- name: Setup server without saving configuration + delegate_to: localhost + notify: Save configuration + netscaler_server: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + save_config: no + + name: server-1 + ipaddress: 192.168.1.1 + +# Under playbook's handlers + +- name: Save configuration + delegate_to: localhost + community.network.netscaler_save_config: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +''' + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import get_nitro_client, log, loglines, netscaler_common_arguments + + +def main(): + + argument_spec = copy.deepcopy(netscaler_common_arguments) + + # Delete common arguments irrelevant to this module + del argument_spec['state'] + del argument_spec['save_config'] + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + try: + log('Saving configuration') + client.save_config() + except nitro_exception as e: + msg = "nitro exception errorcode=" + str(e.errorcode) + ",message=" + e.message + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_server.py new file mode 100644 index 00000000..e33adb20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_server.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_server +short_description: Manage server configuration +description: + - Manage server entities configuration. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + name: + description: + - "Name for the server." + - >- + Must begin with an ASCII alphabetic or underscore C(_) character, and must contain only ASCII + alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals C(=), and hyphen C(-) + characters. + - "Can be changed after the name is created." + - "Minimum length = 1" + + ipaddress: + description: + - >- + IPv4 or IPv6 address of the server. If you create an IP address based server, you can specify the + name of the server, instead of its IP address, when creating a service. Note: If you do not create a + server entry, the server IP address that you enter when you create a service becomes the name of the + server. + + domain: + description: + - "Domain name of the server. For a domain based configuration, you must create the server first." + - "Minimum length = 1" + + translationip: + description: + - "IP address used to transform the server's DNS-resolved IP address." + + translationmask: + description: + - "The netmask of the translation ip." + + domainresolveretry: + description: + - >- + Time, in seconds, for which the NetScaler appliance must wait, after DNS resolution fails, before + sending the next DNS query to resolve the domain name. + - "Minimum value = C(5)" + - "Maximum value = C(20939)" + default: 5 + + ipv6address: + description: + - >- + Support IPv6 addressing mode. If you configure a server with the IPv6 addressing mode, you cannot use + the server in the IPv4 addressing mode. + default: false + type: bool + + comment: + description: + - "Any information about the server." + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = C(0)" + - "Maximum value = C(4094)" + + graceful: + description: + - >- + Shut down gracefully, without accepting any new connections, and disabling each service when all of + its connections are closed. + - This option is meaningful only when setting the I(disabled) option to C(true) + type: bool + + delay: + description: + - Time, in seconds, after which all the services configured on the server are disabled. + - This option is meaningful only when setting the I(disabled) option to C(true) + + disabled: + description: + - When set to C(true) the server state will be set to C(disabled). + - When set to C(false) the server state will be set to C(enabled). + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup server + delegate_to: localhost + community.network.netscaler_server: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: server-1 + ipaddress: 192.168.1.1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' } +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.server import server + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, + netscaler_common_arguments, + log, loglines, + get_immutables_intersection) + + +def server_exists(client, module): + log('Checking if server exists') + if server.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def server_identical(client, module, server_proxy): + log('Checking if configured server is identical') + if server.count_filtered(client, 'name:%s' % module.params['name']) == 0: + return False + diff = diff_list(client, module, server_proxy) + + # Remove options that are not present in nitro server object + # These are special options relevant to the disabled action + for option in ['graceful', 'delay']: + if option in diff: + del diff[option] + + if diff == {}: + return True + else: + return False + + +def diff_list(client, module, server_proxy): + ret_val = server_proxy.diff_object(server.get_filtered(client, 'name:%s' % module.params['name'])[0]), + return ret_val[0] + + +def do_state_change(client, module, server_proxy): + if module.params['disabled']: + log('Disabling server') + result = server.disable(client, server_proxy.actual) + else: + log('Enabling server') + result = server.enable(client, server_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + ipaddress=dict(type='str'), + domain=dict(type='str'), + translationip=dict(type='str'), + translationmask=dict(type='str'), + domainresolveretry=dict(type='int'), + ipv6address=dict( + type='bool', + default=False + ), + comment=dict(type='str'), + td=dict(type='float'), + graceful=dict(type='bool'), + delay=dict(type='float') + ) + + hand_inserted_arguments = dict( + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + + client = get_nitro_client(module) + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate Server Config object + readwrite_attrs = [ + 'name', + 'ipaddress', + 'domain', + 'translationip', + 'translationmask', + 'domainresolveretry', + 'ipv6address', + 'graceful', + 'delay', + 'comment', + 'td', + ] + + readonly_attrs = [ + 'statechangetimesec', + 'tickssincelaststatechange', + 'autoscale', + 'customserverid', + 'monthreshold', + 'maxclient', + 'maxreq', + 'maxbandwidth', + 'usip', + 'cka', + 'tcpb', + 'cmp', + 'clttimeout', + 'svrtimeout', + 'cipheader', + 'cip', + 'cacheable', + 'sc', + 'sp', + 'downstateflush', + 'appflowlog', + 'boundtd', + '__count', + ] + + immutable_attrs = [ + 'name', + 'domain', + 'ipv6address', + 'td', + ] + + transforms = { + 'graceful': ['bool_yes_no'], + 'ipv6address': ['bool_yes_no'], + } + + server_proxy = ConfigProxy( + actual=server(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not server_exists(client, module): + if not module.check_mode: + server_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not server_identical(client, module, server_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(server_proxy, diff_list(client, module, server_proxy).keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_list(client, module, server_proxy), **module_result) + if not module.check_mode: + server_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, server_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for result + log('Sanity checks for state present') + if not module.check_mode: + if not server_exists(client, module): + module.fail_json(msg='Server does not seem to exist', **module_result) + if not server_identical(client, module, server_proxy): + module.fail_json( + msg='Server is not configured according to parameters given', + diff=diff_list(client, module, server_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if server_exists(client, module): + if not module.check_mode: + server_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state absent') + if not module.check_mode: + if server_exists(client, module): + module.fail_json(msg='Server seems to be present', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_service.py new file mode 100644 index 00000000..430d2253 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_service.py @@ -0,0 +1,959 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_service +short_description: Manage service configuration in Netscaler +description: + - Manage service configuration in Netscaler. + - This module allows the creation, deletion and modification of Netscaler services. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + - This module supports check mode. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the service. Must begin with an ASCII alphabetic or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Cannot be changed after the service has been created. + - "Minimum length = 1" + + ip: + description: + - "IP to assign to the service." + - "Minimum length = 1" + + servername: + description: + - "Name of the server that hosts the service." + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'RPCSVR' + - 'DNS' + - 'ADNS' + - 'SNMP' + - 'RTSP' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'ADNS_TCP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + - 'RADIUS' + - 'RADIUSListener' + - 'RDP' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'SMPP' + - 'PPTP' + - 'GRE' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol in which data is exchanged with the service." + + port: + description: + - "Port number of the service." + - "Range 1 - 65535" + - "* in CLI is represented as 65535 in NITRO API" + + cleartextport: + description: + - >- + Port to which clear text data must be sent after the appliance decrypts incoming SSL traffic. + Applicable to transparent SSL services. + - "Minimum value = 1" + + cachetype: + choices: + - 'TRANSPARENT' + - 'REVERSE' + - 'FORWARD' + description: + - "Cache type supported by the cache server." + + maxclient: + description: + - "Maximum number of simultaneous open connections to the service." + - "Minimum value = 0" + - "Maximum value = 4294967294" + + healthmonitor: + description: + - "Monitor the health of this service" + default: yes + type: bool + + maxreq: + description: + - "Maximum number of requests that can be sent on a persistent connection to the service." + - "Note: Connection requests beyond this value are rejected." + - "Minimum value = 0" + - "Maximum value = 65535" + + cacheable: + description: + - "Use the transparent cache redirection virtual server to forward requests to the cache server." + - "Note: Do not specify this parameter if you set the Cache Type parameter." + default: no + type: bool + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Before forwarding a request to the service, insert an HTTP header with the client's IPv4 or IPv6 + address as its value. Used if the server needs the client's IP address for security, accounting, or + other purposes, and setting the Use Source IP parameter is not a viable option. + + cipheader: + description: + - >- + Name for the HTTP header whose value must be set to the IP address of the client. Used with the + Client IP parameter. If you set the Client IP parameter, and you do not specify a name for the + header, the appliance uses the header name specified for the global Client IP Header parameter (the + cipHeader parameter in the set ns param CLI command or the Client IP Header parameter in the + Configure HTTP Parameters dialog box at System > Settings > Change HTTP parameters). If the global + Client IP Header parameter is not specified, the appliance inserts a header with the name + "client-ip.". + - "Minimum length = 1" + + usip: + description: + - >- + Use the client's IP address as the source IP address when initiating a connection to the server. When + creating a service, if you do not set this parameter, the service inherits the global Use Source IP + setting (available in the enable ns mode and disable ns mode CLI commands, or in the System > + Settings > Configure modes > Configure Modes dialog box). However, you can override this setting + after you create the service. + type: bool + + pathmonitor: + description: + - "Path monitoring for clustering." + + pathmonitorindv: + description: + - "Individual Path monitoring decisions." + + useproxyport: + description: + - >- + Use the proxy port as the source port when initiating connections with the server. With the NO + setting, the client-side connection port is used as the source port for the server-side connection. + - "Note: This parameter is available only when the Use Source IP (USIP) parameter is set to YES." + type: bool + + sp: + description: + - "Enable surge protection for the service." + type: bool + + rtspsessionidremap: + description: + - "Enable RTSP session ID mapping for the service." + default: off + type: bool + + clttimeout: + description: + - "Time, in seconds, after which to terminate an idle client connection." + - "Minimum value = 0" + - "Maximum value = 31536000" + + svrtimeout: + description: + - "Time, in seconds, after which to terminate an idle server connection." + - "Minimum value = 0" + - "Maximum value = 31536000" + + customserverid: + description: + - >- + Unique identifier for the service. Used when the persistency type for the virtual server is set to + Custom Server ID. + default: 'None' + + serverid: + description: + - "The identifier for the service. This is used when the persistency type is set to Custom Server ID." + + cka: + description: + - "Enable client keep-alive for the service." + type: bool + + tcpb: + description: + - "Enable TCP buffering for the service." + type: bool + + cmp: + description: + - "Enable compression for the service." + type: bool + + maxbandwidth: + description: + - "Maximum bandwidth, in Kbps, allocated to the service." + - "Minimum value = 0" + - "Maximum value = 4294967287" + + accessdown: + description: + - >- + Use Layer 2 mode to bridge the packets sent to this service if it is marked as DOWN. If the service + is DOWN, and this parameter is disabled, the packets are dropped. + default: no + type: bool + monthreshold: + description: + - >- + Minimum sum of weights of the monitors that are bound to this service. Used to determine whether to + mark a service as UP or DOWN. + - "Minimum value = 0" + - "Maximum value = 65535" + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a service whose state transitions from UP to DOWN. Do + not enable this option for applications that must complete their transactions. + + tcpprofilename: + description: + - "Name of the TCP profile that contains TCP configuration settings for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile that contains HTTP configuration settings for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + hashid: + description: + - >- + A numerical identifier that can be used by hash based load balancing methods. Must be unique for each + service. + - "Minimum value = 1" + + comment: + description: + - "Any information about the service." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging of AppFlow information." + + netprofile: + description: + - "Network profile to use for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = 0" + - "Maximum value = 4094" + + processlocal: + choices: + - 'enabled' + - 'disabled' + description: + - >- + By turning on this option packets destined to a service in a cluster will not under go any steering. + Turn this option for single packet request response mode or when the upstream device is performing a + proper RSS for connection based distribution. + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the service. DNS profile properties will applied to the + transactions processed by a service. This parameter is valid only for ADNS and ADNS-TCP services. + - "Minimum length = 1" + - "Maximum length = 127" + + ipaddress: + description: + - "The new IP address of the service." + + graceful: + description: + - >- + Shut down gracefully, not accepting any new connections, and disabling the service when all of its + connections are closed. + default: no + type: bool + + monitor_bindings: + description: + - A list of load balancing monitors to bind to this service. + - Each monitor entry is a dictionary which may contain the following options. + - Note that if not using the built in monitors they must first be setup. + suboptions: + monitorname: + description: + - Name of the monitor. + weight: + description: + - Weight to assign to the binding between the monitor and service. + dup_state: + choices: + - 'enabled' + - 'disabled' + description: + - State of the monitor. + - The state setting for a monitor of a given type affects all monitors of that type. + - For example, if an HTTP monitor is enabled, all HTTP monitors on the appliance are (or remain) enabled. + - If an HTTP monitor is disabled, all HTTP monitors on the appliance are disabled. + dup_weight: + description: + - Weight to assign to the binding between the monitor and service. + + disabled: + description: + - When set to C(yes) the service state will be set to DISABLED. + - When set to C(no) the service state will be set to ENABLED. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# Monitor monitor-1 must have been already setup + +- name: Setup http service + gather_facts: False + delegate_to: localhost + community.network.netscaler_service: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: service-http-1 + servicetype: HTTP + ipaddress: 10.78.0.1 + port: 80 + + monitor_bindings: + - monitor-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +diff: + description: A dictionary with a list of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' }" +''' + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service import service + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding import service_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_service_binding import lbmonitor_service_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) + + +def service_exists(client, module): + if service.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def service_identical(client, module, service_proxy): + service_list = service.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = service_proxy.diff_object(service_list[0]) + # the actual ip address is stored in the ipaddress attribute + # of the retrieved object + if 'ip' in diff_dict: + del diff_dict['ip'] + if 'graceful' in diff_dict: + del diff_dict['graceful'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff(client, module, service_proxy): + service_list = service.get_filtered(client, 'name:%s' % module.params['name']) + diff_object = service_proxy.diff_object(service_list[0]) + if 'ip' in diff_object: + del diff_object['ip'] + return diff_object + + +def get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs): + bindings = {} + if module.params['monitor_bindings'] is not None: + for binding in module.params['monitor_bindings']: + attribute_values_dict = copy.deepcopy(binding) + # attribute_values_dict['servicename'] = module.params['name'] + attribute_values_dict['servicegroupname'] = module.params['name'] + binding_proxy = ConfigProxy( + actual=lbmonitor_service_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=monitor_bindings_rw_attrs, + ) + key = binding_proxy.monitorname + bindings[key] = binding_proxy + return bindings + + +def get_actual_monitor_bindings(client, module): + bindings = {} + if service_lbmonitor_binding.count(client, module.params['name']) == 0: + return bindings + + # Fallthrough to rest of execution + for binding in service_lbmonitor_binding.get(client, module.params['name']): + # Excluding default monitors since we cannot operate on them + if binding.monitor_name in ('tcp-default', 'ping-default'): + continue + key = binding.monitor_name + actual = lbmonitor_service_binding() + actual.weight = binding.weight + actual.monitorname = binding.monitor_name + actual.dup_weight = binding.dup_weight + actual.servicename = module.params['name'] + bindings[key] = actual + + return bindings + + +def monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs) + actual_bindings = get_actual_monitor_bindings(client, module) + + configured_key_set = set(configured_proxys.keys()) + actual_key_set = set(actual_bindings.keys()) + symmetrical_diff = configured_key_set ^ actual_key_set + if len(symmetrical_diff) > 0: + return False + + # Compare key to key + for monitor_name in configured_key_set: + proxy = configured_proxys[monitor_name] + actual = actual_bindings[monitor_name] + diff_dict = proxy.diff_object(actual) + if 'servicegroupname' in diff_dict: + if proxy.servicegroupname == actual.servicename: + del diff_dict['servicegroupname'] + if len(diff_dict) > 0: + return False + + # Fallthrought to success + return True + + +def sync_monitor_bindings(client, module, monitor_bindings_rw_attrs): + configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs) + actual_bindings = get_actual_monitor_bindings(client, module) + configured_keyset = set(configured_proxys.keys()) + actual_keyset = set(actual_bindings.keys()) + + # Delete extra + delete_keys = list(actual_keyset - configured_keyset) + for monitor_name in delete_keys: + log('Deleting binding for monitor %s' % monitor_name) + lbmonitor_service_binding.delete(client, actual_bindings[monitor_name]) + + # Delete and re-add modified + common_keyset = list(configured_keyset & actual_keyset) + for monitor_name in common_keyset: + proxy = configured_proxys[monitor_name] + actual = actual_bindings[monitor_name] + if not proxy.has_equal_attributes(actual): + log('Deleting and re adding binding for monitor %s' % monitor_name) + lbmonitor_service_binding.delete(client, actual) + proxy.add() + + # Add new + new_keys = list(configured_keyset - actual_keyset) + for monitor_name in new_keys: + log('Adding binding for monitor %s' % monitor_name) + configured_proxys[monitor_name].add() + + +def all_identical(client, module, service_proxy, monitor_bindings_rw_attrs): + return service_identical(client, module, service_proxy) and monitor_bindings_identical(client, module, monitor_bindings_rw_attrs) + + +def do_state_change(client, module, service_proxy): + if module.params['disabled']: + log('Disabling service') + result = service.disable(client, service_proxy.actual) + else: + log('Enabling service') + result = service.enable(client, service_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + ip=dict(type='str'), + servername=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'RPCSVR', + 'DNS', + 'ADNS', + 'SNMP', + 'RTSP', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'ADNS_TCP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + 'RADIUS', + 'RADIUSListener', + 'RDP', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'SMPP', + 'PPTP', + 'GRE', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX' + ] + ), + port=dict(type='int'), + cleartextport=dict(type='int'), + cachetype=dict( + type='str', + choices=[ + 'TRANSPARENT', + 'REVERSE', + 'FORWARD', + ] + ), + maxclient=dict(type='float'), + healthmonitor=dict( + type='bool', + default=True, + ), + maxreq=dict(type='float'), + cacheable=dict( + type='bool', + default=False, + ), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + usip=dict(type='bool'), + useproxyport=dict(type='bool'), + sp=dict(type='bool'), + rtspsessionidremap=dict( + type='bool', + default=False, + ), + clttimeout=dict(type='float'), + svrtimeout=dict(type='float'), + customserverid=dict( + type='str', + default='None', + ), + cka=dict(type='bool'), + tcpb=dict(type='bool'), + cmp=dict(type='bool'), + maxbandwidth=dict(type='float'), + accessdown=dict( + type='bool', + default=False + ), + monthreshold=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + hashid=dict(type='float'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + netprofile=dict(type='str'), + processlocal=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + dnsprofilename=dict(type='str'), + ipaddress=dict(type='str'), + graceful=dict( + type='bool', + default=False, + ), + ) + + hand_inserted_arguments = dict( + monitor_bindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Fallthrough to rest of execution + + # Instantiate Service Config object + readwrite_attrs = [ + 'name', + 'ip', + 'servername', + 'servicetype', + 'port', + 'cleartextport', + 'cachetype', + 'maxclient', + 'healthmonitor', + 'maxreq', + 'cacheable', + 'cip', + 'cipheader', + 'usip', + 'useproxyport', + 'sp', + 'rtspsessionidremap', + 'clttimeout', + 'svrtimeout', + 'customserverid', + 'cka', + 'tcpb', + 'cmp', + 'maxbandwidth', + 'accessdown', + 'monthreshold', + 'downstateflush', + 'tcpprofilename', + 'httpprofilename', + 'hashid', + 'comment', + 'appflowlog', + 'netprofile', + 'processlocal', + 'dnsprofilename', + 'ipaddress', + 'graceful', + ] + + readonly_attrs = [ + 'numofconnections', + 'policyname', + 'serviceconftype', + 'serviceconftype2', + 'value', + 'gslb', + 'dup_state', + 'publicip', + 'publicport', + 'svrstate', + 'monitor_state', + 'monstatcode', + 'lastresponse', + 'responsetime', + 'riseapbrstatsmsgcode2', + 'monstatparam1', + 'monstatparam2', + 'monstatparam3', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'stateupdatereason', + 'clmonowner', + 'clmonview', + 'serviceipstr', + 'oracleserverversion', + ] + + immutable_attrs = [ + 'name', + 'ip', + 'servername', + 'servicetype', + 'port', + 'cleartextport', + 'cachetype', + 'cipheader', + 'serverid', + 'state', + 'td', + 'monitor_name_svc', + 'riseapbrstatsmsgcode', + 'all', + 'Internal', + 'newname', + ] + + transforms = { + 'pathmonitorindv': ['bool_yes_no'], + 'cacheable': ['bool_yes_no'], + 'cka': ['bool_yes_no'], + 'pathmonitor': ['bool_yes_no'], + 'tcpb': ['bool_yes_no'], + 'sp': ['bool_on_off'], + 'graceful': ['bool_yes_no'], + 'usip': ['bool_yes_no'], + 'healthmonitor': ['bool_yes_no'], + 'useproxyport': ['bool_yes_no'], + 'rtspsessionidremap': ['bool_on_off'], + 'accessdown': ['bool_yes_no'], + 'cmp': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + 'processlocal': [lambda v: v.upper()], + } + + monitor_bindings_rw_attrs = [ + 'servicename', + 'servicegroupname', + 'dup_state', + 'dup_weight', + 'monitorname', + 'weight', + ] + + # Translate module arguments to correspondign config object attributes + if module.params['ip'] is None: + module.params['ip'] = module.params['ipaddress'] + + service_proxy = ConfigProxy( + actual=service(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not service_exists(client, module): + if not module.check_mode: + service_proxy.add() + sync_monitor_bindings(client, module, monitor_bindings_rw_attrs) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, service_proxy, monitor_bindings_rw_attrs): + + # Check if we try to change value of immutable attributes + diff_dict = diff(client, module, service_proxy) + immutables_changed = get_immutables_intersection(service_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + # Service sync + if not service_identical(client, module, service_proxy): + if not module.check_mode: + service_proxy.update() + + # Monitor bindings sync + if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + if not module.check_mode: + sync_monitor_bindings(client, module, monitor_bindings_rw_attrs) + + module_result['changed'] = True + if not module.check_mode: + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, service_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not service_exists(client, module): + module.fail_json(msg='Service does not exist', **module_result) + + if not service_identical(client, module, service_proxy): + module.fail_json(msg='Service differs from configured', diff=diff(client, module, service_proxy), **module_result) + + if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + module.fail_json(msg='Monitor bindings are not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if service_exists(client, module): + if not module.check_mode: + service_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if service_exists(client, module): + module.fail_json(msg='Service still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_servicegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_servicegroup.py new file mode 100644 index 00000000..85e11492 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_servicegroup.py @@ -0,0 +1,1041 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_servicegroup +short_description: Manage service group configuration in Netscaler +description: + - Manage service group configuration in Netscaler. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + servicegroupname: + description: + - >- + Name of the service group. Must begin with an ASCII alphabetic or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Can be changed after the name is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'RPCSVR' + - 'DNS' + - 'ADNS' + - 'SNMP' + - 'RTSP' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'ADNS_TCP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + - 'RADIUS' + - 'RADIUSListener' + - 'RDP' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'SMPP' + - 'PPTP' + - 'GRE' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol used to exchange data with the service." + + cachetype: + choices: + - 'TRANSPARENT' + - 'REVERSE' + - 'FORWARD' + description: + - "Cache type supported by the cache server." + + maxclient: + description: + - "Maximum number of simultaneous open connections for the service group." + - "Minimum value = C(0)" + - "Maximum value = C(4294967294)" + + maxreq: + description: + - "Maximum number of requests that can be sent on a persistent connection to the service group." + - "Note: Connection requests beyond this value are rejected." + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + cacheable: + description: + - "Use the transparent cache redirection virtual server to forward the request to the cache server." + - "Note: Do not set this parameter if you set the Cache Type." + type: bool + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - "Insert the Client IP header in requests forwarded to the service." + + cipheader: + description: + - >- + Name of the HTTP header whose value must be set to the IP address of the client. Used with the Client + IP parameter. If client IP insertion is enabled, and the client IP header is not specified, the value + of Client IP Header parameter or the value set by the set ns config command is used as client's IP + header name. + - "Minimum length = 1" + + usip: + description: + - >- + Use client's IP address as the source IP address when initiating connection to the server. With the + NO setting, which is the default, a mapped IP (MIP) address or subnet IP (SNIP) address is used as + the source IP address to initiate server side connections. + type: bool + + pathmonitor: + description: + - "Path monitoring for clustering." + type: bool + + pathmonitorindv: + description: + - "Individual Path monitoring decisions." + type: bool + + useproxyport: + description: + - >- + Use the proxy port as the source port when initiating connections with the server. With the NO + setting, the client-side connection port is used as the source port for the server-side connection. + - "Note: This parameter is available only when the Use Source IP C(usip) parameter is set to C(yes)." + type: bool + + healthmonitor: + description: + - "Monitor the health of this service. Available settings function as follows:" + - "C(yes) - Send probes to check the health of the service." + - >- + C(no) - Do not send probes to check the health of the service. With the NO option, the appliance shows + the service as UP at all times. + type: bool + + sp: + description: + - "Enable surge protection for the service group." + type: bool + + rtspsessionidremap: + description: + - "Enable RTSP session ID mapping for the service group." + type: bool + + clttimeout: + description: + - "Time, in seconds, after which to terminate an idle client connection." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + svrtimeout: + description: + - "Time, in seconds, after which to terminate an idle server connection." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + cka: + description: + - "Enable client keep-alive for the service group." + type: bool + + tcpb: + description: + - "Enable TCP buffering for the service group." + type: bool + + cmp: + description: + - "Enable compression for the specified service." + type: bool + + maxbandwidth: + description: + - "Maximum bandwidth, in Kbps, allocated for all the services in the service group." + - "Minimum value = C(0)" + - "Maximum value = C(4294967287)" + + monthreshold: + description: + - >- + Minimum sum of weights of the monitors that are bound to this service. Used to determine whether to + mark a service as UP or DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with all the services in the service group whose state + transitions from UP to DOWN. Do not enable this option for applications that must complete their + transactions. + + tcpprofilename: + description: + - "Name of the TCP profile that contains TCP configuration settings for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile that contains HTTP configuration settings for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + comment: + description: + - "Any information about the service group." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging of AppFlow information for the specified service group." + + netprofile: + description: + - "Network profile for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + autoscale: + choices: + - 'DISABLED' + - 'DNS' + - 'POLICY' + description: + - "Auto scale option for a servicegroup." + + memberport: + description: + - "member port." + + graceful: + description: + - "Wait for all existing connections to the service to terminate before shutting down the service." + type: bool + + servicemembers: + description: + - A list of dictionaries describing each service member of the service group. + suboptions: + ip: + description: + - IP address of the service. Must not overlap with an existing server entity defined by name. + + port: + description: + - Server port number. + - Range C(1) - C(65535) + - "* in CLI is represented as 65535 in NITRO API" + state: + choices: + - 'enabled' + - 'disabled' + description: + - Initial state of the service after binding. + hashid: + description: + - The hash identifier for the service. + - This must be unique for each service. + - This parameter is used by hash based load balancing methods. + - Minimum value = C(1) + + serverid: + description: + - The identifier for the service. + - This is used when the persistency type is set to Custom Server ID. + + servername: + description: + - Name of the server to which to bind the service group. + - The server must already be configured as a named server. + - Minimum length = 1 + + customserverid: + description: + - The identifier for this IP:Port pair. + - Used when the persistency type is set to Custom Server ID. + + weight: + description: + - Weight to assign to the servers in the service group. + - Specifies the capacity of the servers relative to the other servers in the load balancing configuration. + - The higher the weight, the higher the percentage of requests sent to the service. + - Minimum value = C(1) + - Maximum value = C(100) + + monitorbindings: + description: + - A list of monitornames to bind to this service + - Note that the monitors must have already been setup possibly using the M(community.network.netscaler_lb_monitor) module or some other method + suboptions: + monitorname: + description: + - The monitor name to bind to this servicegroup. + weight: + description: + - Weight to assign to the binding between the monitor and servicegroup. + + disabled: + description: + - When set to C(yes) the service group state will be set to DISABLED. + - When set to C(no) the service group state will be set to ENABLED. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# The LB Monitors monitor-1 and monitor-2 must already exist +# Service members defined by C(ip) must not redefine an existing server's ip address. +# Service members defined by C(servername) must already exist. + +- name: Setup http service with ip members + delegate_to: localhost + community.network.netscaler_servicegroup: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + servicegroupname: service-group-1 + servicetype: HTTP + servicemembers: + - ip: 10.78.78.78 + port: 80 + weight: 50 + - ip: 10.79.79.79 + port: 80 + weight: 40 + - servername: server-1 + port: 80 + weight: 10 + + monitorbindings: + - monitorname: monitor-1 + weight: 50 + - monitorname: monitor-2 + weight: 50 + +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +import copy + +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup import servicegroup + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup_servicegroupmember_binding import servicegroup_servicegroupmember_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup_lbmonitor_binding import servicegroup_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_servicegroup_binding import lbmonitor_servicegroup_binding + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def servicegroup_exists(client, module): + log('Checking if service group exists') + count = servicegroup.count_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + log('count is %s' % count) + if count > 0: + return True + else: + return False + + +def servicegroup_identical(client, module, servicegroup_proxy): + log('Checking if service group is identical') + servicegroups = servicegroup.get_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + if servicegroup_proxy.has_equal_attributes(servicegroups[0]): + return True + else: + return False + + +def get_configured_service_members(client, module): + log('get_configured_service_members') + readwrite_attrs = [ + 'servicegroupname', + 'ip', + 'port', + 'state', + 'hashid', + 'serverid', + 'servername', + 'customserverid', + 'weight' + ] + readonly_attrs = [ + 'delay', + 'statechangetimesec', + 'svrstate', + 'tickssincelaststatechange', + 'graceful', + ] + + members = [] + if module.params['servicemembers'] is None: + return members + + for config in module.params['servicemembers']: + # Make a copy to update + config = copy.deepcopy(config) + config['servicegroupname'] = module.params['servicegroupname'] + member_proxy = ConfigProxy( + actual=servicegroup_servicegroupmember_binding(), + client=client, + attribute_values_dict=config, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs + ) + members.append(member_proxy) + return members + + +def get_actual_service_members(client, module): + try: + # count() raises nitro exception instead of returning 0 + count = servicegroup_servicegroupmember_binding.count(client, module.params['servicegroupname']) + if count > 0: + servicegroup_members = servicegroup_servicegroupmember_binding.get(client, module.params['servicegroupname']) + else: + servicegroup_members = [] + except nitro_exception as e: + if e.errorcode == 258: + servicegroup_members = [] + else: + raise + return servicegroup_members + + +def servicemembers_identical(client, module): + log('servicemembers_identical') + + servicegroup_members = get_actual_service_members(client, module) + log('servicemembers %s' % servicegroup_members) + module_servicegroups = get_configured_service_members(client, module) + log('Number of service group members %s' % len(servicegroup_members)) + if len(servicegroup_members) != len(module_servicegroups): + return False + + # Fallthrough to member evaluation + identical_count = 0 + for actual_member in servicegroup_members: + for member in module_servicegroups: + if member.has_equal_attributes(actual_member): + identical_count += 1 + break + if identical_count != len(servicegroup_members): + return False + + # Fallthrough to success + return True + + +def sync_service_members(client, module): + log('sync_service_members') + configured_service_members = get_configured_service_members(client, module) + actual_service_members = get_actual_service_members(client, module) + skip_add = [] + skip_delete = [] + + # Find positions of identical service members + for (configured_index, configured_service) in enumerate(configured_service_members): + for (actual_index, actual_service) in enumerate(actual_service_members): + if configured_service.has_equal_attributes(actual_service): + skip_add.append(configured_index) + skip_delete.append(actual_index) + + # Delete actual that are not identical to any configured + for (actual_index, actual_service) in enumerate(actual_service_members): + # Skip identical + if actual_index in skip_delete: + log('Skipping actual delete at index %s' % actual_index) + continue + + # Fallthrouth to deletion + if all([ + hasattr(actual_service, 'ip'), + actual_service.ip is not None, + hasattr(actual_service, 'servername'), + actual_service.servername is not None, + ]): + actual_service.ip = None + + actual_service.servicegroupname = module.params['servicegroupname'] + servicegroup_servicegroupmember_binding.delete(client, actual_service) + + # Add configured that are not already present in actual + for (configured_index, configured_service) in enumerate(configured_service_members): + + # Skip identical + if configured_index in skip_add: + log('Skipping configured add at index %s' % configured_index) + continue + + # Fallthrough to addition + configured_service.add() + + +def monitor_binding_equal(configured, actual): + if any([configured.monitorname != actual.monitor_name, + configured.servicegroupname != actual.servicegroupname, + configured.weight != float(actual.weight)]): + return False + return True + + +def get_configured_monitor_bindings(client, module): + log('Entering get_configured_monitor_bindings') + bindings = {} + if 'monitorbindings' in module.params and module.params['monitorbindings'] is not None: + for binding in module.params['monitorbindings']: + readwrite_attrs = [ + 'monitorname', + 'servicegroupname', + 'weight', + ] + readonly_attrs = [] + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['servicegroupname'] = module.params['servicegroupname'] + binding_proxy = ConfigProxy( + actual=lbmonitor_servicegroup_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + key = attribute_values_dict['monitorname'] + bindings[key] = binding_proxy + return bindings + + +def get_actual_monitor_bindings(client, module): + log('Entering get_actual_monitor_bindings') + bindings = {} + try: + # count() raises nitro exception instead of returning 0 + count = servicegroup_lbmonitor_binding.count(client, module.params['servicegroupname']) + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + if count == 0: + return bindings + + # Fallthrough to rest of execution + for binding in servicegroup_lbmonitor_binding.get(client, module.params['servicegroupname']): + log('Gettign actual monitor with name %s' % binding.monitor_name) + key = binding.monitor_name + bindings[key] = binding + + return bindings + + +def monitor_bindings_identical(client, module): + log('Entering monitor_bindings_identical') + configured_bindings = get_configured_monitor_bindings(client, module) + actual_bindings = get_actual_monitor_bindings(client, module) + + configured_key_set = set(configured_bindings.keys()) + actual_key_set = set(actual_bindings.keys()) + symmetrical_diff = configured_key_set ^ actual_key_set + for default_monitor in ('tcp-default', 'ping-default'): + if default_monitor in symmetrical_diff: + log('Excluding %s monitor from key comparison' % default_monitor) + symmetrical_diff.remove(default_monitor) + if len(symmetrical_diff) > 0: + return False + + # Compare key to key + for key in configured_key_set: + configured_proxy = configured_bindings[key] + + # Follow nscli convention for missing weight value + if not hasattr(configured_proxy, 'weight'): + configured_proxy.weight = 1 + log('configured_proxy %s' % [configured_proxy.monitorname, configured_proxy.servicegroupname, configured_proxy.weight]) + log('actual_bindings %s' % [actual_bindings[key].monitor_name, actual_bindings[key].servicegroupname, actual_bindings[key].weight]) + if not monitor_binding_equal(configured_proxy, actual_bindings[key]): + return False + + # Fallthrought to success + return True + + +def sync_monitor_bindings(client, module): + log('Entering sync_monitor_bindings') + + actual_bindings = get_actual_monitor_bindings(client, module) + + # Exclude default monitors from deletion + for monitorname in ('tcp-default', 'ping-default'): + if monitorname in actual_bindings: + del actual_bindings[monitorname] + + configured_bindings = get_configured_monitor_bindings(client, module) + + to_remove = list(set(actual_bindings.keys()) - set(configured_bindings.keys())) + to_add = list(set(configured_bindings.keys()) - set(actual_bindings.keys())) + to_modify = list(set(configured_bindings.keys()) & set(actual_bindings.keys())) + + # Delete existing and modifiable bindings + for key in to_remove + to_modify: + binding = actual_bindings[key] + b = lbmonitor_servicegroup_binding() + b.monitorname = binding.monitor_name + b.servicegroupname = module.params['servicegroupname'] + # Cannot remove default monitor bindings + if b.monitorname in ('tcp-default', 'ping-default'): + continue + lbmonitor_servicegroup_binding.delete(client, b) + + # Add new and modified bindings + for key in to_add + to_modify: + binding = configured_bindings[key] + log('Adding %s' % binding.monitorname) + binding.add() + + +def diff(client, module, servicegroup_proxy): + servicegroup_list = servicegroup.get_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + diff_object = servicegroup_proxy.diff_object(servicegroup_list[0]) + return diff_object + + +def do_state_change(client, module, servicegroup_proxy): + if module.params['disabled']: + log('Disabling service') + result = servicegroup.disable(client, servicegroup_proxy.actual) + else: + log('Enabling service') + result = servicegroup.enable(client, servicegroup_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + servicegroupname=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'RPCSVR', + 'DNS', + 'ADNS', + 'SNMP', + 'RTSP', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'ADNS_TCP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + 'RADIUS', + 'RADIUSListener', + 'RDP', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'SMPP', + 'PPTP', + 'GRE', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX', + ] + ), + cachetype=dict( + type='str', + choices=[ + 'TRANSPARENT', + 'REVERSE', + 'FORWARD', + ] + ), + maxclient=dict(type='float'), + maxreq=dict(type='float'), + cacheable=dict(type='bool'), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + usip=dict(type='bool'), + pathmonitor=dict(type='bool'), + pathmonitorindv=dict(type='bool'), + useproxyport=dict(type='bool'), + healthmonitor=dict(type='bool'), + sp=dict(type='bool'), + rtspsessionidremap=dict(type='bool'), + clttimeout=dict(type='float'), + svrtimeout=dict(type='float'), + cka=dict(type='bool'), + tcpb=dict(type='bool'), + cmp=dict(type='bool'), + maxbandwidth=dict(type='float'), + monthreshold=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + autoscale=dict( + type='str', + choices=[ + 'DISABLED', + 'DNS', + 'POLICY', + ] + ), + memberport=dict(type='int'), + graceful=dict(type='bool'), + ) + + hand_inserted_arguments = dict( + servicemembers=dict(type='list'), + monitorbindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate service group configuration object + readwrite_attrs = [ + 'servicegroupname', + 'servicetype', + 'cachetype', + 'maxclient', + 'maxreq', + 'cacheable', + 'cip', + 'cipheader', + 'usip', + 'pathmonitor', + 'pathmonitorindv', + 'useproxyport', + 'healthmonitor', + 'sp', + 'rtspsessionidremap', + 'clttimeout', + 'svrtimeout', + 'cka', + 'tcpb', + 'cmp', + 'maxbandwidth', + 'monthreshold', + 'downstateflush', + 'tcpprofilename', + 'httpprofilename', + 'comment', + 'appflowlog', + 'netprofile', + 'autoscale', + 'memberport', + 'graceful', + ] + + readonly_attrs = [ + 'numofconnections', + 'serviceconftype', + 'value', + 'svrstate', + 'ip', + 'monstatcode', + 'monstatparam1', + 'monstatparam2', + 'monstatparam3', + 'statechangetimemsec', + 'stateupdatereason', + 'clmonowner', + 'clmonview', + 'groupcount', + 'riseapbrstatsmsgcode2', + 'serviceipstr', + 'servicegroupeffectivestate' + ] + + immutable_attrs = [ + 'servicegroupname', + 'servicetype', + 'cachetype', + 'td', + 'cipheader', + 'state', + 'autoscale', + 'memberport', + 'servername', + 'port', + 'serverid', + 'monitor_name_svc', + 'dup_weight', + 'riseapbrstatsmsgcode', + 'delay', + 'graceful', + 'includemembers', + 'newname', + ] + + transforms = { + 'pathmonitorindv': ['bool_yes_no'], + 'cacheable': ['bool_yes_no'], + 'cka': ['bool_yes_no'], + 'pathmonitor': ['bool_yes_no'], + 'tcpb': ['bool_yes_no'], + 'sp': ['bool_on_off'], + 'usip': ['bool_yes_no'], + 'healthmonitor': ['bool_yes_no'], + 'useproxyport': ['bool_yes_no'], + 'rtspsessionidremap': ['bool_on_off'], + 'graceful': ['bool_yes_no'], + 'cmp': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + servicegroup_proxy = ConfigProxy( + actual=servicegroup(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + if module.params['state'] == 'present': + log('Applying actions for state present') + if not servicegroup_exists(client, module): + if not module.check_mode: + log('Adding service group') + servicegroup_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not servicegroup_identical(client, module, servicegroup_proxy): + + # Check if we try to change value of immutable attributes + diff_dict = diff(client, module, servicegroup_proxy) + immutables_changed = get_immutables_intersection(servicegroup_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + if not module.check_mode: + servicegroup_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Check bindings + if not monitor_bindings_identical(client, module): + if not module.check_mode: + sync_monitor_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not servicemembers_identical(client, module): + if not module.check_mode: + sync_service_members(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, servicegroup_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not servicegroup_exists(client, module): + module.fail_json(msg='Service group is not present', **module_result) + if not servicegroup_identical(client, module, servicegroup_proxy): + module.fail_json( + msg='Service group is not identical to configuration', + diff=diff(client, module, servicegroup_proxy), + **module_result + ) + if not servicemembers_identical(client, module): + module.fail_json(msg='Service group members differ from configuration', **module_result) + if not monitor_bindings_identical(client, module): + module.fail_json(msg='Monitor bindings are not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if servicegroup_exists(client, module): + if not module.check_mode: + servicegroup_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if servicegroup_exists(client, module): + module.fail_json(msg='Service group is present', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=" + str(e.errorcode) + ",message=" + e.message + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_ssl_certkey.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_ssl_certkey.py new file mode 100644 index 00000000..6fe589c4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/netscaler_ssl_certkey.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_ssl_certkey +short_description: Manage ssl certificate keys. +description: + - Manage ssl certificate keys. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + certkey: + description: + - >- + Name for the certificate and private-key pair. Must begin with an ASCII alphanumeric or underscore + C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), + colon C(:), at C(@), equals C(=), and hyphen C(-) characters. Cannot be changed after the certificate-key + pair is created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, "my cert" or 'my cert'). + - "Minimum length = 1" + + cert: + description: + - >- + Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key + pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + key: + description: + - >- + Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. + The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + password: + description: + - >- + Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys + in PEM format. + + inform: + choices: + - 'DER' + - 'PEM' + - 'PFX' + description: + - >- + Input format of the certificate and the private-key files. The three formats supported by the + appliance are: + - "PEM - Privacy Enhanced Mail" + - "DER - Distinguished Encoding Rule" + - "PFX - Personal Information Exchange." + + passplain: + description: + - >- + Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM + format. + - "Minimum length = 1" + + expirymonitor: + choices: + - 'enabled' + - 'disabled' + description: + - "Issue an alert when the certificate is about to expire." + + notificationperiod: + description: + - >- + Time, in number of days, before certificate expiration, at which to generate an alert that the + certificate is about to expire. + - "Minimum value = C(10)" + - "Maximum value = C(100)" + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' + +- name: Setup ssl certkey + delegate_to: localhost + community.network.netscaler_ssl_certkey: + nitro_user: nsroot + nitro_pass: nsroot + nsip: 172.18.0.2 + + certkey: certirificate_1 + cert: server.crt + key: server.key + expirymonitor: enabled + notificationperiod: 30 + inform: PEM + password: False + passplain: somesecret +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey import sslcertkey + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) + + +def key_exists(client, module): + log('Checking if key exists') + log('certkey is %s' % module.params['certkey']) + all_certificates = sslcertkey.get(client) + certkeys = [item.certkey for item in all_certificates] + if module.params['certkey'] in certkeys: + return True + else: + return False + + +def key_identical(client, module, sslcertkey_proxy): + log('Checking if configured key is identical') + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + diff_dict = sslcertkey_proxy.diff_object(sslcertkey_list[0]) + if 'password' in diff_dict: + del diff_dict['password'] + if 'passplain' in diff_dict: + del diff_dict['passplain'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, sslcertkey_proxy): + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + return sslcertkey_proxy.diff_object(sslcertkey_list[0]) + + +def main(): + + module_specific_arguments = dict( + certkey=dict(type='str'), + cert=dict(type='str'), + key=dict(type='str'), + password=dict(type='bool'), + inform=dict( + type='str', + choices=[ + 'DER', + 'PEM', + 'PFX', + ] + ), + passplain=dict( + type='str', + no_log=True, + ), + expirymonitor=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + notificationperiod=dict(type='float'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + 'expirymonitor', + 'notificationperiod', + ] + + readonly_attrs = [ + 'signaturealg', + 'certificatetype', + 'serial', + 'issuer', + 'clientcertnotbefore', + 'clientcertnotafter', + 'daystoexpiration', + 'subject', + 'publickey', + 'publickeysize', + 'version', + 'priority', + 'status', + 'passcrypt', + 'data', + 'servicename', + ] + + immutable_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + ] + + transforms = { + 'expirymonitor': [lambda v: v.upper()], + } + + # Instantiate config proxy + sslcertkey_proxy = ConfigProxy( + actual=sslcertkey(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + if module.params['state'] == 'present': + log('Applying actions for state present') + if not key_exists(client, module): + if not module.check_mode: + log('Adding certificate key') + sslcertkey_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not key_identical(client, module, sslcertkey_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(sslcertkey_proxy, diff_list(client, module, sslcertkey_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, sslcertkey_proxy), + **module_result + ) + + if not module.check_mode: + sslcertkey_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not key_exists(client, module): + module.fail_json(msg='SSL certkey does not exist') + if not key_identical(client, module, sslcertkey_proxy): + module.fail_json(msg='SSL certkey differs from configured', diff=diff_list(client, module, sslcertkey_proxy)) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if key_exists(client, module): + if not module.check_mode: + sslcertkey_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if key_exists(client, module): + module.fail_json(msg='SSL certkey still exists') + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server.py new file mode 100644 index 00000000..d9bdcc80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# (c) 2016, Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_server +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' server object. +description: + - Manage SLB (Server Load Balancer) server objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + partition: + description: + - set active-partition + server_name: + description: + - The SLB (Server Load Balancer) server name. + required: true + aliases: ['server'] + server_ip: + description: + - The SLB server IPv4 address. + aliases: ['ip', 'address'] + server_status: + description: + - The SLB virtual server status. + default: enabled + aliases: ['status'] + choices: ['enabled', 'disabled'] + server_ports: + description: + - A list of ports to create for the server. Each list item should be a + dictionary which specifies the C(port:) and C(protocol:), but can also optionally + specify the C(status:). See the examples below for details. This parameter is + required when C(state) is C(present). + aliases: ['port'] + state: + description: + - This is to specify the operation to create, update or remove SLB server. + default: present + choices: ['present', 'absent'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +EXAMPLES = ''' +- name: Create a new server + community.network.a10_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + server: test + server_ip: 1.1.1.100 + server_ports: + - port_num: 8080 + protocol: tcp + - port_num: 8443 + protocol: TCP +''' + +RETURN = ''' +content: + description: the full info regarding the slb_server + returned: success + type: str + sample: "mynewserver" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, axapi_failure, + axapi_get_port_protocol, axapi_enabled_disabled, AXAPI_PORT_PROTOCOLS) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port_num', 'protocol', 'status'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port_num' in item: + try: + item['port_num'] = int(item['port_num']) + except Exception: + module.fail_json(msg="port_num entries in the port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port_num field") + + # validate the port protocol is present, and convert it to + # the internal API integer value (and validate it) + if 'protocol' in item: + protocol = axapi_get_port_protocol(item['protocol']) + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS)) + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + server_name=dict(type='str', aliases=['server'], required=True), + server_ip=dict(type='str', aliases=['ip', 'address']), + server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']), + server_ports=dict(type='list', aliases=['port'], default=[]), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + partition = module.params['partition'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_server = module.params['server_name'] + slb_server_ip = module.params['server_ip'] + slb_server_status = module.params['server_status'] + slb_server_ports = module.params['server_ports'] + + if slb_server is None: + module.fail_json(msg='server_name is required') + + axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host + session_url = axapi_authenticate(module, axapi_base_url, username, password) + + # validate the ports data structure + validate_ports(module, slb_server_ports) + + json_post = { + 'server': { + 'name': slb_server, + } + } + + # add optional module parameters + if slb_server_ip: + json_post['server']['host'] = slb_server_ip + + if slb_server_ports: + json_post['server']['port_list'] = slb_server_ports + + if slb_server_status: + json_post['server']['status'] = axapi_enabled_disabled(slb_server_status) + + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + + slb_server_data = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server})) + slb_server_exists = not axapi_failure(slb_server_data) + + changed = False + if state == 'present': + if not slb_server_exists: + if not slb_server_ip: + module.fail_json(msg='you must specify an IP address when creating a server') + + result = axapi_call(module, session_url + '&method=slb.server.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg']) + changed = True + else: + def port_needs_update(src_ports, dst_ports): + ''' + Checks to determine if the port definitions of the src_ports + array are in or different from those in dst_ports. If there is + a difference, this function returns true, otherwise false. + ''' + for src_port in src_ports: + found = False + different = False + for dst_port in dst_ports: + if src_port['port_num'] == dst_port['port_num']: + found = True + for valid_field in VALID_PORT_FIELDS: + if src_port[valid_field] != dst_port[valid_field]: + different = True + break + if found or different: + break + if not found or different: + return True + # every port from the src exists in the dst, and none of them were different + return False + + def status_needs_update(current_status, new_status): + ''' + Check to determine if we want to change the status of a server. + If there is a difference between the current status of the server and + the desired status, return true, otherwise false. + ''' + if current_status != new_status: + return True + return False + + defined_ports = slb_server_data.get('server', {}).get('port_list', []) + current_status = slb_server_data.get('server', {}).get('status') + + # we check for a needed update several ways + # - in case ports are missing from the ones specified by the user + # - in case ports are missing from those on the device + # - in case we are change the status of a server + if (port_needs_update(defined_ports, slb_server_ports) or + port_needs_update(slb_server_ports, defined_ports) or + status_needs_update(current_status, axapi_enabled_disabled(slb_server_status))): + result = axapi_call(module, session_url + '&method=slb.server.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to update the server: %s" % result['response']['err']['msg']) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server})) + else: + result = slb_server_data + elif state == 'absent': + if slb_server_exists: + result = axapi_call(module, session_url + '&method=slb.server.delete', json.dumps({'name': slb_server})) + changed = True + else: + result = dict(msg="the server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server_axapi3.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server_axapi3.py new file mode 100644 index 00000000..b5ba9a5f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_server_axapi3.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2014, Mischa Peters +# Copyright: (c) 2016, Eric Chou +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_server_axapi3 +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices +description: + - Manage SLB (Server Load Balancer) server objects on A10 Networks devices via aXAPIv3. +author: + - Eric Chou (@ericchou1) +extends_documentation_fragment: +- community.network.a10 +- url + +options: + server_name: + description: + - The SLB (Server Load Balancer) server name. + required: true + aliases: ['server'] + server_ip: + description: + - The SLB (Server Load Balancer) server IPv4 address. + required: true + aliases: ['ip', 'address'] + server_status: + description: + - The SLB (Server Load Balancer) virtual server status. + default: enable + aliases: ['action'] + choices: ['enable', 'disable'] + server_ports: + description: + - A list of ports to create for the server. Each list item should be a dictionary which specifies the C(port:) + and C(protocol:). + aliases: ['port'] + operation: + description: + - Create, Update or Remove SLB server. For create and update operation, we use the IP address and server + name specified in the POST message. For delete operation, we use the server name in the request URI. + default: create + choices: ['create', 'update', 'remove'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +RETURN = ''' +# +''' + +EXAMPLES = ''' +- name: Create a new server + a10_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + server: test + server_ip: 1.1.1.100 + validate_certs: false + server_status: enable + write_config: yes + operation: create + server_ports: + - port-number: 8080 + protocol: tcp + action: enable + - port-number: 8443 + protocol: TCP +''' + +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import axapi_call_v3, a10_argument_spec, axapi_authenticate_v3, axapi_failure +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import AXAPI_PORT_PROTOCOLS +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port-number', 'protocol', 'action'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port-number' in item: + try: + item['port-number'] = int(item['port-number']) + except Exception: + module.fail_json(msg="port-number entries in the port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port-number field") + + # validate the port protocol is present, no need to convert to the internal API integer value in v3 + if 'protocol' in item: + protocol = item['protocol'] + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS)) + + # 'status' is 'action' in AXAPIv3 + # no need to convert the status, a.k.a action, to the internal API integer value in v3 + # action is either enabled or disabled + if 'action' in item: + action = item['action'] + if action not in ['enable', 'disable']: + module.fail_json(msg="server action must be enable or disable") + else: + item['action'] = 'enable' + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + operation=dict(type='str', default='create', choices=['create', 'update', 'delete']), + server_name=dict(type='str', aliases=['server'], required=True), + server_ip=dict(type='str', aliases=['ip', 'address'], required=True), + server_status=dict(type='str', default='enable', aliases=['action'], choices=['enable', 'disable']), + server_ports=dict(type='list', aliases=['port'], default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + operation = module.params['operation'] + write_config = module.params['write_config'] + slb_server = module.params['server_name'] + slb_server_ip = module.params['server_ip'] + slb_server_status = module.params['server_status'] + slb_server_ports = module.params['server_ports'] + + axapi_base_url = 'https://{0}/axapi/v3/'.format(host) + axapi_auth_url = axapi_base_url + 'auth/' + signature = axapi_authenticate_v3(module, axapi_auth_url, username, password) + + # validate the ports data structure + validate_ports(module, slb_server_ports) + + json_post = { + "server-list": [ + { + "name": slb_server, + "host": slb_server_ip + } + ] + } + + # add optional module parameters + if slb_server_ports: + json_post['server-list'][0]['port-list'] = slb_server_ports + + if slb_server_status: + json_post['server-list'][0]['action'] = slb_server_status + + slb_server_data = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='GET', body='', signature=signature) + + # for empty slb server list + if axapi_failure(slb_server_data): + slb_server_exists = False + else: + slb_server_list = [server['name'] for server in slb_server_data['server-list']] + if slb_server in slb_server_list: + slb_server_exists = True + else: + slb_server_exists = False + + changed = False + if operation == 'create': + if slb_server_exists is False: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='POST', body=json.dumps(json_post), signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg']) + changed = True + else: + module.fail_json(msg="server already exists, use state='update' instead") + changed = False + # if we changed things, get the full info regarding result + if changed: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/' + slb_server, method='GET', body='', signature=signature) + else: + result = slb_server_data + elif operation == 'delete': + if slb_server_exists: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/' + slb_server, method='DELETE', body='', signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to delete server: %s" % result['response']['err']['msg']) + changed = True + else: + result = dict(msg="the server was not present") + elif operation == 'update': + if slb_server_exists: + result = axapi_call_v3(module, axapi_base_url + 'slb/server/', method='PUT', body=json.dumps(json_post), signature=signature) + if axapi_failure(result): + module.fail_json(msg="failed to update server: %s" % result['response']['err']['msg']) + changed = True + else: + result = dict(msg="the server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call_v3(module, axapi_base_url + 'write/memory/', method='POST', body='', signature=signature) + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out gracefully and exit + axapi_call_v3(module, axapi_base_url + 'logoff/', method='POST', body='', signature=signature) + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_service_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_service_group.py new file mode 100644 index 00000000..2a85b487 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_service_group.py @@ -0,0 +1,332 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_service_group +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' service groups. +description: + - Manage SLB (Server Load Balancing) service-group objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. + - When a server doesn't exist and is added to the service-group the server will be created. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + state: + description: + - If the specified service group should exists. + default: present + choices: ['present', 'absent'] + partition: + description: + - set active-partition + service_group: + description: + - The SLB (Server Load Balancing) service-group name + required: true + aliases: ['service', 'pool', 'group'] + service_group_protocol: + description: + - The SLB service-group protocol of TCP or UDP. + default: tcp + aliases: ['proto', 'protocol'] + choices: ['tcp', 'udp'] + service_group_method: + description: + - The SLB service-group load balancing method, such as round-robin or weighted-rr. + default: round-robin + aliases: ['method'] + choices: + - 'round-robin' + - 'weighted-rr' + - 'least-connection' + - 'weighted-least-connection' + - 'service-least-connection' + - 'service-weighted-least-connection' + - 'fastest-response' + - 'least-request' + - 'round-robin-strict' + - 'src-ip-only-hash' + - 'src-ip-hash' + servers: + description: + - A list of servers to add to the service group. Each list item should be a + dictionary which specifies the C(server:) and C(port:), but can also optionally + specify the C(status:). See the examples below for details. + aliases: ['server', 'member'] + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + +EXAMPLES = ''' +- name: Create a new service-group + community.network.a10_service_group: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + service_group: sg-80-tcp + servers: + - server: foo1.mydomain.com + port: 8080 + - server: foo2.mydomain.com + port: 8080 + - server: foo3.mydomain.com + port: 8080 + - server: foo4.mydomain.com + port: 8080 + status: disabled +''' + +RETURN = ''' +content: + description: the full info regarding the slb_service_group + returned: success + type: str + sample: "mynewservicegroup" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, + axapi_failure, axapi_enabled_disabled) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_SERVICE_GROUP_FIELDS = ['name', 'protocol', 'lb_method'] +VALID_SERVER_FIELDS = ['server', 'port', 'status'] + + +def validate_servers(module, servers): + for item in servers: + for key in item: + if key not in VALID_SERVER_FIELDS: + module.fail_json(msg="invalid server field (%s), must be one of: %s" % (key, ','.join(VALID_SERVER_FIELDS))) + + # validate the server name is present + if 'server' not in item: + module.fail_json(msg="server definitions must define the server field") + + # validate the port number is present and an integer + if 'port' in item: + try: + item['port'] = int(item['port']) + except Exception: + module.fail_json(msg="server port definitions must be integers") + else: + module.fail_json(msg="server definitions must define the port field") + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + service_group=dict(type='str', aliases=['service', 'pool', 'group'], required=True), + service_group_protocol=dict(type='str', default='tcp', aliases=['proto', 'protocol'], choices=['tcp', 'udp']), + service_group_method=dict(type='str', default='round-robin', + aliases=['method'], + choices=['round-robin', + 'weighted-rr', + 'least-connection', + 'weighted-least-connection', + 'service-least-connection', + 'service-weighted-least-connection', + 'fastest-response', + 'least-request', + 'round-robin-strict', + 'src-ip-only-hash', + 'src-ip-hash']), + servers=dict(type='list', aliases=['server', 'member'], default=[]), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + partition = module.params['partition'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_service_group = module.params['service_group'] + slb_service_group_proto = module.params['service_group_protocol'] + slb_service_group_method = module.params['service_group_method'] + slb_servers = module.params['servers'] + + if slb_service_group is None: + module.fail_json(msg='service_group is required') + + axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json' + load_balancing_methods = {'round-robin': 0, + 'weighted-rr': 1, + 'least-connection': 2, + 'weighted-least-connection': 3, + 'service-least-connection': 4, + 'service-weighted-least-connection': 5, + 'fastest-response': 6, + 'least-request': 7, + 'round-robin-strict': 8, + 'src-ip-only-hash': 14, + 'src-ip-hash': 15} + + if not slb_service_group_proto or slb_service_group_proto.lower() == 'tcp': + protocol = 2 + else: + protocol = 3 + + # validate the server data list structure + validate_servers(module, slb_servers) + + json_post = { + 'service_group': { + 'name': slb_service_group, + 'protocol': protocol, + 'lb_method': load_balancing_methods[slb_service_group_method], + } + } + + # first we authenticate to get a session id + session_url = axapi_authenticate(module, axapi_base_url, username, password) + # then we select the active-partition + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + # then we check to see if the specified group exists + slb_result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group})) + slb_service_group_exist = not axapi_failure(slb_result) + + changed = False + if state == 'present': + # before creating/updating we need to validate that servers + # defined in the servers list exist to prevent errors + checked_servers = [] + for server in slb_servers: + result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': server['server']})) + if axapi_failure(result): + module.fail_json(msg="the server %s specified in the servers list does not exist" % server['server']) + checked_servers.append(server['server']) + + if not slb_service_group_exist: + result = axapi_call(module, session_url + '&method=slb.service_group.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg=result['response']['err']['msg']) + changed = True + else: + # check to see if the service group definition without the + # server members is different, and update that individually + # if it needs it + do_update = False + for field in VALID_SERVICE_GROUP_FIELDS: + if json_post['service_group'][field] != slb_result['service_group'][field]: + do_update = True + break + + if do_update: + result = axapi_call(module, session_url + '&method=slb.service_group.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg=result['response']['err']['msg']) + changed = True + + # next we pull the defined list of servers out of the returned + # results to make it a bit easier to iterate over + defined_servers = slb_result.get('service_group', {}).get('member_list', []) + + # next we add/update new member servers from the user-specified + # list if they're different or not on the target device + for server in slb_servers: + found = False + different = False + for def_server in defined_servers: + if server['server'] == def_server['server']: + found = True + for valid_field in VALID_SERVER_FIELDS: + if server[valid_field] != def_server[valid_field]: + different = True + break + if found or different: + break + # add or update as required + server_data = { + "name": slb_service_group, + "member": server, + } + if not found: + result = axapi_call(module, session_url + '&method=slb.service_group.member.create', json.dumps(server_data)) + changed = True + elif different: + result = axapi_call(module, session_url + '&method=slb.service_group.member.update', json.dumps(server_data)) + changed = True + + # finally, remove any servers that are on the target + # device but were not specified in the list given + for server in defined_servers: + found = False + for slb_server in slb_servers: + if server['server'] == slb_server['server']: + found = True + break + # remove if not found + server_data = { + "name": slb_service_group, + "member": server, + } + if not found: + result = axapi_call(module, session_url + '&method=slb.service_group.member.delete', json.dumps(server_data)) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group})) + else: + result = slb_result + elif state == 'absent': + if slb_service_group_exist: + result = axapi_call(module, session_url + '&method=slb.service_group.delete', json.dumps({'name': slb_service_group})) + changed = True + else: + result = dict(msg="the service group was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_virtual_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_virtual_server.py new file mode 100644 index 00000000..77343f80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/a10/a10_virtual_server.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Mischa Peters , +# Eric Chou +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: a10_virtual_server +short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' virtual servers. +description: + - Manage SLB (Server Load Balancing) virtual server objects on A10 Networks devices via aXAPIv2. +author: + - Eric Chou (@ericchou1) + - Mischa Peters (@mischapeters) +notes: + - Requires A10 Networks aXAPI 2.1. +extends_documentation_fragment: +- community.network.a10 +- url + +options: + state: + description: + - If the specified virtual server should exist. + choices: ['present', 'absent'] + default: present + partition: + description: + - set active-partition + virtual_server: + description: + - The SLB (Server Load Balancing) virtual server name. + required: true + aliases: ['vip', 'virtual'] + virtual_server_ip: + description: + - The SLB virtual server IPv4 address. + aliases: ['ip', 'address'] + virtual_server_status: + description: + - The SLB virtual server status, such as enabled or disabled. + default: enable + aliases: ['status'] + choices: ['enabled', 'disabled'] + virtual_server_ports: + description: + - A list of ports to create for the virtual server. Each list item should be a + dictionary which specifies the C(port:) and C(type:), but can also optionally + specify the C(service_group:) as well as the C(status:). See the examples + below for details. This parameter is required when C(state) is C(present). + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + type: bool + default: 'yes' + +''' + + +EXAMPLES = ''' +- name: Create a new virtual server + community.network.a10_virtual_server: + host: a10.mydomain.com + username: myadmin + password: mypassword + partition: mypartition + virtual_server: vserver1 + virtual_server_ip: 1.1.1.1 + virtual_server_ports: + - port: 80 + protocol: TCP + service_group: sg-80-tcp + - port: 443 + protocol: HTTPS + service_group: sg-443-https + - port: 8080 + protocol: http + status: disabled +''' + +RETURN = ''' +content: + description: the full info regarding the slb_virtual + returned: success + type: str + sample: "mynewvirtualserver" +''' +import json + +from ansible_collections.community.network.plugins.module_utils.network.a10.a10 import (axapi_call, a10_argument_spec, axapi_authenticate, axapi_failure, + axapi_enabled_disabled, axapi_get_vport_protocol, + AXAPI_VPORT_PROTOCOLS) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import url_argument_spec + + +VALID_PORT_FIELDS = ['port', 'protocol', 'service_group', 'status'] + + +def validate_ports(module, ports): + for item in ports: + for key in item: + if key not in VALID_PORT_FIELDS: + module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS))) + + # validate the port number is present and an integer + if 'port' in item: + try: + item['port'] = int(item['port']) + except Exception: + module.fail_json(msg="port definitions must be integers") + else: + module.fail_json(msg="port definitions must define the port field") + + # validate the port protocol is present, and convert it to + # the internal API integer value (and validate it) + if 'protocol' in item: + protocol = axapi_get_vport_protocol(item['protocol']) + if not protocol: + module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_VPORT_PROTOCOLS)) + else: + item['protocol'] = protocol + else: + module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_VPORT_PROTOCOLS)) + + # convert the status to the internal API integer value + if 'status' in item: + item['status'] = axapi_enabled_disabled(item['status']) + else: + item['status'] = 1 + + # ensure the service_group field is at least present + if 'service_group' not in item: + item['service_group'] = '' + + +def main(): + argument_spec = a10_argument_spec() + argument_spec.update(url_argument_spec()) + argument_spec.update( + dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + virtual_server=dict(type='str', aliases=['vip', 'virtual'], required=True), + virtual_server_ip=dict(type='str', aliases=['ip', 'address'], required=True), + virtual_server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']), + virtual_server_ports=dict(type='list', required=True), + partition=dict(type='str', default=[]), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + host = module.params['host'] + username = module.params['username'] + password = module.params['password'] + partition = module.params['partition'] + state = module.params['state'] + write_config = module.params['write_config'] + slb_virtual = module.params['virtual_server'] + slb_virtual_ip = module.params['virtual_server_ip'] + slb_virtual_status = module.params['virtual_server_status'] + slb_virtual_ports = module.params['virtual_server_ports'] + + if slb_virtual is None: + module.fail_json(msg='virtual_server is required') + + validate_ports(module, slb_virtual_ports) + + axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host + session_url = axapi_authenticate(module, axapi_base_url, username, password) + + axapi_call(module, session_url + '&method=system.partition.active', json.dumps({'name': partition})) + slb_virtual_data = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual})) + slb_virtual_exists = not axapi_failure(slb_virtual_data) + + changed = False + if state == 'present': + json_post = { + 'virtual_server': { + 'name': slb_virtual, + 'address': slb_virtual_ip, + 'status': axapi_enabled_disabled(slb_virtual_status), + 'vport_list': slb_virtual_ports, + } + } + + # before creating/updating we need to validate that any + # service groups defined in the ports list exist since + # since the API will still create port definitions for + # them while indicating a failure occurred + checked_service_groups = [] + for port in slb_virtual_ports: + if 'service_group' in port and port['service_group'] not in checked_service_groups: + # skip blank service group entries + if port['service_group'] == '': + continue + result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': port['service_group']})) + if axapi_failure(result): + module.fail_json(msg="the service group %s specified in the ports list does not exist" % port['service_group']) + checked_service_groups.append(port['service_group']) + + if not slb_virtual_exists: + result = axapi_call(module, session_url + '&method=slb.virtual_server.create', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg']) + changed = True + else: + def needs_update(src_ports, dst_ports): + ''' + Checks to determine if the port definitions of the src_ports + array are in or different from those in dst_ports. If there is + a difference, this function returns true, otherwise false. + ''' + for src_port in src_ports: + found = False + different = False + for dst_port in dst_ports: + if src_port['port'] == dst_port['port']: + found = True + for valid_field in VALID_PORT_FIELDS: + if src_port[valid_field] != dst_port[valid_field]: + different = True + break + if found or different: + break + if not found or different: + return True + # every port from the src exists in the dst, and none of them were different + return False + + defined_ports = slb_virtual_data.get('virtual_server', {}).get('vport_list', []) + + # we check for a needed update both ways, in case ports + # are missing from either the ones specified by the user + # or from those on the device + if needs_update(defined_ports, slb_virtual_ports) or needs_update(slb_virtual_ports, defined_ports): + result = axapi_call(module, session_url + '&method=slb.virtual_server.update', json.dumps(json_post)) + if axapi_failure(result): + module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg']) + changed = True + + # if we changed things, get the full info regarding + # the service group for the return data below + if changed: + result = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual})) + else: + result = slb_virtual_data + elif state == 'absent': + if slb_virtual_exists: + result = axapi_call(module, session_url + '&method=slb.virtual_server.delete', json.dumps({'name': slb_virtual})) + changed = True + else: + result = dict(msg="the virtual server was not present") + + # if the config has changed, save the config unless otherwise requested + if changed and write_config: + write_result = axapi_call(module, session_url + '&method=system.action.write_memory') + if axapi_failure(write_result): + module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg']) + + # log out of the session nicely and exit + axapi_call(module, session_url + '&method=session.close') + module.exit_json(changed=changed, content=result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_command.py new file mode 100644 index 00000000..58312516 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_command.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aireos_command +author: "James Mighion (@jmighion)" +short_description: Run commands on remote devices running Cisco WLC +description: + - Sends arbitrary commands to an aireos node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - Commands run in configuration mode with this module are not + idempotent. Please use M(community.network.aireos_config) to configure WLC devices. +extends_documentation_fragment: +- community.network.aireos + +options: + commands: + description: + - List of commands to send to the remote aireos device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show sysinfo on remote devices + community.network.aireos_command: + commands: show sysinfo + + - name: Run show sysinfo and check to see if output contains Cisco Controller + community.network.aireos_command: + commands: show sysinfo + wait_for: result[0] contains 'Cisco Controller' + + - name: Run multiple commands on remote nodes + community.network.aireos_command: + commands: + - show sysinfo + - show interface summary + + - name: Run multiple commands and evaluate the output + community.network.aireos_command: + commands: + - show sysinfo + - show interface summary + wait_for: + - result[0] contains Cisco Controller + - result[1] contains Loopback0 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import run_commands +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import aireos_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_text + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = to_text(item, errors='surrogate_then_replace').split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + warnings.append( + 'commands run in config mode with aireos_command are not ' + 'idempotent. Please use aireos_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(aireos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_config.py new file mode 100644 index 00000000..06fecec9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aireos/aireos_config.py @@ -0,0 +1,354 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aireos_config +author: "James Mighion (@jmighion)" +short_description: Manage Cisco WLC configurations +description: + - AireOS does not use a block indent file syntax, so there are no sections or parents. + This module provides an implementation for working with AireOS configurations in + a deterministic way. +extends_documentation_fragment: +- community.network.aireos + +options: + lines: + description: + - The ordered set of commands that should be configured. + The commands must be the exact same commands as found + in the device run-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. + If match is set to I(none), the module will not attempt to + compare the source configuration with the running + configuration on the remote device. + default: line + choices: ['line', 'none'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + save: + description: + - The C(save) argument instructs the module to save the + running-config to startup-config. This operation is performed + after any changes are made to the current running config. If + no changes are made, the configuration is still saved to the + startup config. This option will always cause the module to + return changed. This argument is mutually exclusive with I(save_when). + - This option is deprecated as of Ansible 2.7, use C(save_when) + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the module will always return as changed. + If the argument is set to I(never), the running-config will never + be copied to the startup-config. If the argument is set to I(changed), + then the running-config will only be copied to the startup-config if + the task has made a change. + default: never + choices: ['always', 'never', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['intended', 'running'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure configuration + community.network.aireos_config: + lines: sysname testDevice + +- name: Diff the running-config against a provided config + community.network.aireos_config: + diff_against: intended + intended: "{{ lookup('file', 'master.cfg') }}" + +- name: Load new acl into device + community.network.aireos_config: + lines: + - acl create testACL + - acl rule protocol testACL 1 any + - acl rule direction testACL 3 in + before: acl delete testACL + +- name: Configurable backup path + community.network.aireos_config: + backup: yes + lines: sysname testDevice + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/aireos_config.2016-07-16@22:28:34 +""" +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import run_commands, get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import aireos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.aireos.aireos import check_args as aireos_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_running_config(module, config=None): + contents = module.params['running_config'] + if not contents: + if config: + contents = config + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + candidate.add(module.params['lines']) + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "save config", "prompt": "Are you sure you want to save", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `save config` due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + # save is deprecated as of Ansible 2.7, use save_when instead + save=dict(type='bool', default=False, removed_in_version='2.0.0', + removed_from_collection='community.network'), # was Ansible 2.11 + save_when=dict(choices=['always', 'never', 'changed'], default='never'), + + diff_against=dict(choices=['running', 'intended']), + diff_ignore_lines=dict(type='list') + ) + + argument_spec.update(aireos_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('save', 'save_when')] + + required_if = [('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + aireos_check_args(module, warnings) + result = {'changed': False, 'warnings': warnings} + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['src'], module.params['lines'])): + match = module.params['match'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + configobjs = candidate.difference(config, match=match) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always' or module.params['save']: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + output = run_commands(module, 'show run-config commands') + contents = output[0] + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + result.update({ + 'changed': True, + 'diff': {'before': str(base_config), 'after': str(running_config)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/apconos/apconos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/apconos/apconos_command.py new file mode 100644 index 00000000..18156a4c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/apconos/apconos_command.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# +# Copyright (C) 2019 APCON. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute apconos Commands on Apcon Switches. +# Apcon Networking + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: apconos_command +version_added: '0.2.0' +author: "David Lee (@davidlee-ap)" +short_description: Run arbitrary commands on APCON devices +description: + - Sends arbitrary commands to an apcon device and returns the results + read from the device. The module includes an argument that will + cause the module to wait for a specific condition before returning + or timing out if the condition is not met. +notes: + - Tested against apcon iis+ii +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + type: list + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + type: str + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + type: int + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 + type: int +''' + +EXAMPLES = """ +- name: Basic Configuration + community.network.apconos_command: + commands: + - show version + - enable ssh + register: result + +- name: Get output from single command + community.network.apconos_command: + commands: ['show version'] + register: result +""" + +RETURN = """ +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_lines +from ansible_collections.community.network.plugins.module_utils.network.apconos.apconos import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional + + +def parse_commands(module, warnings): + + commands = module.params['commands'] + + if module.check_mode: + for item in list(commands): + if not item.startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not ' + 'executing %s' % item + ) + commands.remove(item) + + return commands + + +def main(): + spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=False) + warnings = list() + result = {'changed': False, 'warnings': warnings} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = parse_commands(module, warnings) + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + for item in responses: + if len(item) == 0: + if module.check_mode: + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + else: + result.update({ + 'changed': True, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + elif 'ERROR' in item: + result.update({ + 'failed': True, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + else: + result.update({ + 'stdout': item, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_command.py new file mode 100644 index 00000000..33d9f2c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_command.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aruba_command +author: "James Mighion (@jmighion)" +short_description: Run commands on remote devices running Aruba Mobility Controller +description: + - Sends arbitrary commands to an aruba node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.aruba_config) to configure Aruba devices. +extends_documentation_fragment: +- community.network.aruba + +options: + commands: + description: + - List of commands to send to the remote aruba device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.aruba_command: + commands: show version + + - name: Run show version and check to see if output contains Aruba + community.network.aruba_command: + commands: show version + wait_for: result[0] contains Aruba + + - name: Run multiple commands on remote nodes + community.network.aruba_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.aruba_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains Aruba + - result[1] contains Loopback0 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import run_commands +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import aruba_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + module.fail_json( + msg='aruba_command does not support running config mode ' + 'commands. Please use aruba_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(aruba_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_config.py new file mode 100644 index 00000000..b21098b2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/aruba/aruba_config.py @@ -0,0 +1,420 @@ +#!/usr/bin/python +# +# Copyright: Ansible Team +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: aruba_config +author: "James Mighion (@jmighion)" +short_description: Manage Aruba configuration sections +description: + - Aruba configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with Aruba configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.aruba + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup configuration and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup configuration if it has changed since + the last save to startup configuration. If the argument is set to + I(never), the running-config will never be copied to the + startup configuration. If the argument is set to I(changed), then the running-config + will only be copied to the startup configuration if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup configuration. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['startup', 'intended', 'running'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + encrypt: + description: + - This allows an Aruba controller's passwords and keys to be displayed in plain + text when set to I(false) or encrypted when set to I(true). + If set to I(false), the setting will re-encrypt at the end of the module run. + Backups are still encrypted even when set to I(false). + type: bool + default: 'yes' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.aruba_config: + lines: hostname {{ inventory_hostname }} + +- name: Diff the running-config against a provided config + community.network.aruba_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Configure interface settings + community.network.aruba_config: + lines: + - description test interface + - ip access-group 1 in + parents: interface gigabitethernet 0/0/0 + +- name: Load new acl into device + community.network.aruba_config: + lines: + - permit host 10.10.10.10 + - ipv6 permit host fda9:97d6:32a3:3e59::3333 + parents: ip access-list standard 1 + before: no ip access-list standard 1 + match: exact + +- name: Configurable backup path + community.network.aruba_config: + backup: yes + lines: hostname {{ inventory_hostname }} + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/aruba_config.2016-07-16@22:28:34 +""" + + +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import run_commands, get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import aruba_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.aruba.aruba import check_args as aruba_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_running_config(module, config=None): + contents = module.params['running_config'] + if not contents: + if config: + contents = config + else: + contents = get_config(module) + return NetworkConfig(contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig() + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'write memory') + else: + module.warn('Skipping command `write memory` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['running', 'startup', 'intended']), + diff_ignore_lines=dict(type='list'), + + encrypt=dict(type='bool', default=True), + ) + + argument_spec.update(aruba_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + aruba_check_args(module, warnings) + result = {'changed': False, 'warnings': warnings} + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if not module.params['encrypt']: + run_commands(module, 'encrypt disable') + + if any((module.params['src'], module.params['lines'])): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + path = module.params['parents'] + configobjs = candidate.difference(config, match=match, replace=replace, path=path) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show configuration']) + + running_config = NetworkConfig(contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed': + if result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show configuration') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + result.update({ + 'changed': True, + 'diff': {'before': str(base_config), 'after': str(running_config)} + }) + + # make sure 'encrypt enable' is applied if it was ever disabled + if not module.params['encrypt']: + run_commands(module, 'encrypt enable') + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_actiongroupconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_actiongroupconfig.py new file mode 100644 index 00000000..bfd07703 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_actiongroupconfig.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_actiongroupconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ActionGroupConfig Avi RESTful Object +description: + - This module is used to configure ActionGroupConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_script_config_ref: + description: + - Reference of the action script configuration to be used. + - It is a reference to an object of type alertscriptconfig. + autoscale_trigger_notification: + description: + - Trigger notification to autoscale manager. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + email_config_ref: + description: + - Select the email notification configuration to use when sending alerts via email. + - It is a reference to an object of type alertemailconfig. + external_only: + description: + - Generate alert only to external destinations. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + required: true + type: bool + level: + description: + - When an alert is generated, mark its priority via the alert level. + - Enum options - ALERT_LOW, ALERT_MEDIUM, ALERT_HIGH. + - Default value when not specified in API or module is interpreted by Avi Controller as ALERT_LOW. + required: true + name: + description: + - Name of the object. + required: true + snmp_trap_profile_ref: + description: + - Select the snmp trap notification to use when sending alerts via snmp trap. + - It is a reference to an object of type snmptrapprofile. + syslog_config_ref: + description: + - Select the syslog notification configuration to use when sending alerts via syslog. + - It is a reference to an object of type alertsyslogconfig. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ActionGroupConfig object + community.network.avi_actiongroupconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_actiongroupconfig +""" + +RETURN = ''' +obj: + description: ActionGroupConfig (api/actiongroupconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_script_config_ref=dict(type='str',), + autoscale_trigger_notification=dict(type='bool',), + description=dict(type='str',), + email_config_ref=dict(type='str',), + external_only=dict(type='bool', required=True), + level=dict(type='str', required=True), + name=dict(type='str', required=True), + snmp_trap_profile_ref=dict(type='str',), + syslog_config_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'actiongroupconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertconfig.py new file mode 100644 index 00000000..80a5f443 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertconfig.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertConfig Avi RESTful Object +description: + - This module is used to configure AlertConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_group_ref: + description: + - The alert config will trigger the selected alert action, which can send notifications and execute a controlscript. + - It is a reference to an object of type actiongroupconfig. + alert_rule: + description: + - List of filters matching on events or client logs used for triggering alerts. + required: true + autoscale_alert: + description: + - This alert config applies to auto scale alerts. + type: bool + category: + description: + - Determines whether an alert is raised immediately when event occurs (realtime) or after specified number of events occurs within rolling time + - window. + - Enum options - REALTIME, ROLLINGWINDOW, WATERMARK. + - Default value when not specified in API or module is interpreted by Avi Controller as REALTIME. + required: true + description: + description: + - A custom description field. + enabled: + description: + - Enable or disable this alert config from generating new alerts. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + expiry_time: + description: + - An alert is expired and deleted after the expiry time has elapsed. + - The original event triggering the alert remains in the event's log. + - Allowed values are 1-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 86400. + name: + description: + - Name of the alert configuration. + required: true + obj_uuid: + description: + - Uuid of the resource for which alert was raised. + object_type: + description: + - The object type to which the alert config is associated with. + - Valid object types are - virtual service, pool, service engine. + - Enum options - VIRTUALSERVICE, POOL, HEALTHMONITOR, NETWORKPROFILE, APPLICATIONPROFILE, HTTPPOLICYSET, DNSPOLICY, SECURITYPOLICY, IPADDRGROUP, + - STRINGGROUP, SSLPROFILE, SSLKEYANDCERTIFICATE, NETWORKSECURITYPOLICY, APPLICATIONPERSISTENCEPROFILE, ANALYTICSPROFILE, VSDATASCRIPTSET, TENANT, + - PKIPROFILE, AUTHPROFILE, CLOUD, SERVERAUTOSCALEPOLICY, AUTOSCALELAUNCHCONFIG, MICROSERVICEGROUP, IPAMPROFILE, HARDWARESECURITYMODULEGROUP, + - POOLGROUP, PRIORITYLABELS, POOLGROUPDEPLOYMENTPOLICY, GSLBSERVICE, GSLBSERVICERUNTIME, SCHEDULER, GSLBGEODBPROFILE, + - GSLBAPPLICATIONPERSISTENCEPROFILE, TRAFFICCLONEPROFILE, VSVIP, WAFPOLICY, WAFPROFILE, ERRORPAGEPROFILE, ERRORPAGEBODY, L4POLICYSET, + - GSLBSERVICERUNTIMEBATCH, WAFPOLICYPSMGROUP, PINGACCESSAGENT, SERVICEENGINEPOLICY, NATPOLICY, SSOPOLICY, PROTOCOLPARSER, SERVICEENGINE, + - DEBUGSERVICEENGINE, DEBUGCONTROLLER, DEBUGVIRTUALSERVICE, SERVICEENGINEGROUP, SEPROPERTIES, NETWORK, CONTROLLERNODE, CONTROLLERPROPERTIES, + - SYSTEMCONFIGURATION, VRFCONTEXT, USER, ALERTCONFIG, ALERTSYSLOGCONFIG, ALERTEMAILCONFIG, ALERTTYPECONFIG, APPLICATION, ROLE, CLOUDPROPERTIES, + - SNMPTRAPPROFILE, ACTIONGROUPPROFILE, MICROSERVICE, ALERTPARAMS, ACTIONGROUPCONFIG, CLOUDCONNECTORUSER, GSLB, GSLBDNSUPDATE, GSLBSITEOPS, + - GLBMGRWARMSTART, IPAMDNSRECORD, GSLBDNSGSSTATUS, GSLBDNSGEOFILEOPS, GSLBDNSGEOUPDATE, GSLBDNSGEOCLUSTEROPS, GSLBDNSCLEANUP, GSLBSITEOPSRESYNC, + - IPAMDNSPROVIDERPROFILE, TCPSTATRUNTIME, UDPSTATRUNTIME, IPSTATRUNTIME, ARPSTATRUNTIME, MBSTATRUNTIME, IPSTKQSTATSRUNTIME, MALLOCSTATRUNTIME, + - SHMALLOCSTATRUNTIME, CPUUSAGERUNTIME, L7GLOBALSTATSRUNTIME, L7VIRTUALSERVICESTATSRUNTIME, SEAGENTVNICDBRUNTIME, SEAGENTGRAPHDBRUNTIME, + - SEAGENTSTATERUNTIME, INTERFACERUNTIME, ARPTABLERUNTIME, DISPATCHERSTATRUNTIME, DISPATCHERSTATCLEARRUNTIME, DISPATCHERTABLEDUMPRUNTIME, + - DISPATCHERREMOTETIMERLISTDUMPRUNTIME, METRICSAGENTMESSAGE, HEALTHMONITORSTATRUNTIME, METRICSENTITYRUNTIME, PERSISTENCEINTERNAL, + - HTTPPOLICYSETINTERNAL, DNSPOLICYINTERNAL, CONNECTIONDUMPRUNTIME, SHAREDDBSTATS, SHAREDDBSTATSCLEAR, ICMPSTATRUNTIME, ROUTETABLERUNTIME, + - VIRTUALMACHINE, POOLSERVER, SEVSLIST, MEMINFORUNTIME, RTERINGSTATRUNTIME, ALGOSTATRUNTIME, HEALTHMONITORRUNTIME, CPUSTATRUNTIME, SEVM, HOST, + - PORTGROUP, CLUSTER, DATACENTER, VCENTER, HTTPPOLICYSETSTATS, DNSPOLICYSTATS, METRICSSESTATS, RATELIMITERSTATRUNTIME, NETWORKSECURITYPOLICYSTATS, + - TCPCONNRUNTIME, POOLSTATS, CONNPOOLINTERNAL, CONNPOOLSTATS, VSHASHSHOWRUNTIME, SELOGSTATSRUNTIME, NETWORKSECURITYPOLICYDETAIL, LICENSERUNTIME, + - SERVERRUNTIME, METRICSRUNTIMESUMMARY, METRICSRUNTIMEDETAIL, DISPATCHERSEHMPROBETEMPDISABLERUNTIME, POOLDEBUG, VSLOGMGRMAP, SERUMINSERTIONSTATS, + - HTTPCACHE, HTTPCACHESTATS, SEDOSSTATRUNTIME, VSDOSSTATRUNTIME, SERVERUPDATEREQ, VSSCALEOUTLIST, SEMEMDISTRUNTIME, TCPCONNRUNTIMEDETAIL, + - SEUPGRADESTATUS, SEUPGRADEPREVIEW, SEFAULTINJECTEXHAUSTM, SEFAULTINJECTEXHAUSTMCL, SEFAULTINJECTEXHAUSTMCLSMALL, SEFAULTINJECTEXHAUSTCONN, + - SEHEADLESSONLINEREQ, SEUPGRADE, SEUPGRADESTATUSDETAIL, SERESERVEDVS, SERESERVEDVSCLEAR, VSCANDIDATESEHOSTLIST, SEGROUPUPGRADE, REBALANCE, + - SEGROUPREBALANCE, SEAUTHSTATSRUNTIME, AUTOSCALESTATE, VIRTUALSERVICEAUTHSTATS, NETWORKSECURITYPOLICYDOS, KEYVALINTERNAL, KEYVALSUMMARYINTERNAL, + - SERVERSTATEUPDATEINFO, CLTRACKINTERNAL, CLTRACKSUMMARYINTERNAL, MICROSERVICERUNTIME, SEMICROSERVICE, VIRTUALSERVICEANALYSIS, CLIENTINTERNAL, + - CLIENTSUMMARYINTERNAL, MICROSERVICEGROUPRUNTIME, BGPRUNTIME, REQUESTQUEUERUNTIME, MIGRATEALL, MIGRATEALLSTATUSSUMMARY, MIGRATEALLSTATUSDETAIL, + - INTERFACESUMMARYRUNTIME, INTERFACELACPRUNTIME, DNSTABLE, GSLBSERVICEDETAIL, GSLBSERVICEINTERNAL, GSLBSERVICEHMONSTAT, SETROLESREQUEST, + - TRAFFICCLONERUNTIME, GEOLOCATIONINFO, SEVSHBSTATRUNTIME, GEODBINTERNAL, GSLBSITEINTERNAL, WAFSTATS, USERDEFINEDDATASCRIPTCOUNTERS, LLDPRUNTIME, + - VSESSHARINGPOOL, NDTABLERUNTIME, IP6STATRUNTIME, ICMP6STATRUNTIME, SEVSSPLACEMENT, L4POLICYSETSTATS, L4POLICYSETINTERNAL, BGPDEBUGINFO, SHARD, + - CPUSTATRUNTIMEDETAIL, SEASSERTSTATRUNTIME, SEFAULTINJECTINFRA, SEAGENTASSERTSTATRUNTIME, SEDATASTORESTATUS, DIFFQUEUESTATUS, IP6ROUTETABLERUNTIME, + - SECURITYMGRSTATE, VIRTUALSERVICESESCALEOUTSTATUS, SHARDSERVERSTATUS, SEAGENTSHARDCLIENTRESOURCEMAP, SEAGENTCONSISTENTHASH, SEAGENTVNICDBHISTORY, + - SEAGENTSHARDCLIENTAPPMAP, SEAGENTSHARDCLIENTEVENTHISTORY, SENATSTATRUNTIME, SENATFLOWRUNTIME, SERESOURCEPROTO, SECONSUMERPROTO, + - SECREATEPENDINGPROTO, PLACEMENTSTATS, SEVIPPROTO, RMVRFPROTO, VCENTERMAP, VIMGRVCENTERRUNTIME, INTERESTEDVMS, INTERESTEDHOSTS, + - VCENTERSUPPORTEDCOUNTERS, ENTITYCOUNTERS, TRANSACTIONSTATS, SEVMCREATEPROGRESS, PLACEMENTSTATUS, VISUBFOLDERS, VIDATASTORE, VIHOSTRESOURCES, + - CLOUDCONNECTOR, VINETWORKSUBNETVMS, VIDATASTORECONTENTS, VIMGRVCENTERCLOUDRUNTIME, VIVCENTERPORTGROUPS, VIVCENTERDATACENTERS, VIMGRHOSTRUNTIME, + - PLACEMENTGLOBALS, APICCONFIGURATION, CIFTABLE, APICTRANSACTION, VIRTUALSERVICESTATEDBCACHESUMMARY, POOLSTATEDBCACHESUMMARY, + - SERVERSTATEDBCACHESUMMARY, APICAGENTINTERNAL, APICTRANSACTIONFLAP, APICGRAPHINSTANCES, APICEPGS, APICEPGEPS, APICDEVICEPKGVER, APICTENANTS, + - APICVMMDOMAINS, NSXCONFIGURATION, NSXSGTABLE, NSXAGENTINTERNAL, NSXSGINFO, NSXSGIPS, NSXAGENTINTERNALCLI, MAXOBJECTS. + recommendation: + description: + - Recommendation of alertconfig. + rolling_window: + description: + - Only if the number of events is reached or exceeded within the time window will an alert be generated. + - Allowed values are 1-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + source: + description: + - Signifies system events or the type of client logsused in this alert configuration. + - Enum options - CONN_LOGS, APP_LOGS, EVENT_LOGS, METRICS. + required: true + summary: + description: + - Summary of reason why alert is generated. + tenant_ref: + description: + - It is a reference to an object of type tenant. + threshold: + description: + - An alert is created only when the number of events meets or exceeds this number within the chosen time frame. + - Allowed values are 1-65536. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + throttle: + description: + - Alerts are suppressed (throttled) for this duration of time since the last alert was raised for this alert config. + - Allowed values are 0-31536000. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create AlertConfig object + community.network.avi_alertconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_alertconfig +""" + +RETURN = ''' +obj: + description: AlertConfig (api/alertconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_group_ref=dict(type='str',), + alert_rule=dict(type='dict', required=True), + autoscale_alert=dict(type='bool',), + category=dict(type='str', required=True), + description=dict(type='str',), + enabled=dict(type='bool',), + expiry_time=dict(type='int',), + name=dict(type='str', required=True), + obj_uuid=dict(type='str',), + object_type=dict(type='str',), + recommendation=dict(type='str',), + rolling_window=dict(type='int',), + source=dict(type='str', required=True), + summary=dict(type='str',), + tenant_ref=dict(type='str',), + threshold=dict(type='int',), + throttle=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertemailconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertemailconfig.py new file mode 100644 index 00000000..b460a76e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertemailconfig.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertemailconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertEmailConfig Avi RESTful Object +description: + - This module is used to configure AlertEmailConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cc_emails: + description: + - Alerts are copied to the comma separated list of email recipients. + description: + description: + - User defined description for the object. + name: + description: + - A user-friendly name of the email notification service. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + to_emails: + description: + - Alerts are sent to the comma separated list of email recipients. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create AlertEmailConfig object + community.network.avi_alertemailconfig: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_alertemailconfig +""" + +RETURN = ''' +obj: + description: AlertEmailConfig (api/alertemailconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cc_emails=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + to_emails=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertemailconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertscriptconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertscriptconfig.py new file mode 100644 index 00000000..95868c89 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertscriptconfig.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertscriptconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertScriptConfig Avi RESTful Object +description: + - This module is used to configure AlertScriptConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + action_script: + description: + - User defined alert action script. + - Please refer to kb.avinetworks.com for more information. + name: + description: + - A user-friendly name of the script. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Alert Script to perform AWS server autoscaling + community.network.avi_alertscriptconfig: + username: '{{ username }}' + controller: '{{ controller }}' + password: '{{ password }}' + action_script: "echo Hello" + name: AWS-Launch-Script + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: AlertScriptConfig (api/alertscriptconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + action_script=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertscriptconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertsyslogconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertsyslogconfig.py new file mode 100644 index 00000000..d6bd4a0a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_alertsyslogconfig.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_alertsyslogconfig +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AlertSyslogConfig Avi RESTful Object +description: + - This module is used to configure AlertSyslogConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for alert syslog config. + name: + description: + - A user-friendly name of the syslog notification. + required: true + syslog_servers: + description: + - The list of syslog servers. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Alert Syslog object to forward all events to external syslog server + community.network.avi_alertsyslogconfig: + controller: '{{ controller }}' + name: Roberts-syslog + password: '{{ password }}' + syslog_servers: + - syslog_server: 10.10.0.100 + syslog_server_port: 514 + udp: true + tenant_ref: admin + username: '{{ username }}' +""" + +RETURN = ''' +obj: + description: AlertSyslogConfig (api/alertsyslogconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + name=dict(type='str', required=True), + syslog_servers=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'alertsyslogconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_analyticsprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_analyticsprofile.py new file mode 100644 index 00000000..079ec4c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_analyticsprofile.py @@ -0,0 +1,610 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_analyticsprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AnalyticsProfile Avi RESTful Object +description: + - This module is used to configure AnalyticsProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + apdex_response_threshold: + description: + - If a client receives an http response in less than the satisfactory latency threshold, the request is considered satisfied. + - It is considered tolerated if it is not satisfied and less than tolerated latency factor multiplied by the satisfactory latency threshold. + - Greater than this number and the client's request is considered frustrated. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 500. + apdex_response_tolerated_factor: + description: + - Client tolerated response latency factor. + - Client must receive a response within this factor times the satisfactory threshold (apdex_response_threshold) to be considered tolerated. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_rtt_threshold: + description: + - Satisfactory client to avi round trip time(rtt). + - Allowed values are 1-2000. + - Default value when not specified in API or module is interpreted by Avi Controller as 250. + apdex_rtt_tolerated_factor: + description: + - Tolerated client to avi round trip time(rtt) factor. + - It is a multiple of apdex_rtt_tolerated_factor. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_rum_threshold: + description: + - If a client is able to load a page in less than the satisfactory latency threshold, the pageload is considered satisfied. + - It is considered tolerated if it is greater than satisfied but less than the tolerated latency multiplied by satisfied latency. + - Greater than this number and the client's request is considered frustrated. + - A pageload includes the time for dns lookup, download of all http objects, and page render time. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 5000. + apdex_rum_tolerated_factor: + description: + - Virtual service threshold factor for tolerated page load time (plt) as multiple of apdex_rum_threshold. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_server_response_threshold: + description: + - A server http response is considered satisfied if latency is less than the satisfactory latency threshold. + - The response is considered tolerated when it is greater than satisfied but less than the tolerated latency factor * s_latency. + - Greater than this number and the server response is considered frustrated. + - Allowed values are 1-30000. + - Default value when not specified in API or module is interpreted by Avi Controller as 400. + apdex_server_response_tolerated_factor: + description: + - Server tolerated response latency factor. + - Servermust response within this factor times the satisfactory threshold (apdex_server_response_threshold) to be considered tolerated. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + apdex_server_rtt_threshold: + description: + - Satisfactory client to avi round trip time(rtt). + - Allowed values are 1-2000. + - Default value when not specified in API or module is interpreted by Avi Controller as 125. + apdex_server_rtt_tolerated_factor: + description: + - Tolerated client to avi round trip time(rtt) factor. + - It is a multiple of apdex_rtt_tolerated_factor. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + client_log_config: + description: + - Configure which logs are sent to the avi controller from ses and how they are processed. + client_log_streaming_config: + description: + - Configure to stream logs to an external server. + - Field introduced in 17.1.1. + conn_lossy_ooo_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of out of order packets are received. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_lossy_timeo_rexmt_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of packets are retransmitted due to timeout. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + conn_lossy_total_rexmt_threshold: + description: + - A connection between client and avi is considered lossy when more than this percentage of packets are retransmitted. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_lossy_zero_win_size_event_threshold: + description: + - A client connection is considered lossy when percentage of times a packet could not be transmitted due to tcp zero window is above this threshold. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + conn_server_lossy_ooo_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of out of order packets are received. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_server_lossy_timeo_rexmt_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of packets are retransmitted due to timeout. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + conn_server_lossy_total_rexmt_threshold: + description: + - A connection between avi and server is considered lossy when more than this percentage of packets are retransmitted. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + conn_server_lossy_zero_win_size_event_threshold: + description: + - A server connection is considered lossy when percentage of times a packet could not be transmitted due to tcp zero window is above this threshold. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + description: + description: + - User defined description for the object. + disable_ondemand_metrics: + description: + - Virtual service (vs) metrics are processed only when there is live data traffic on the vs. + - In case, vs is idle for a period of time as specified by ondemand_metrics_idle_timeout then metrics processing is suspended for that vs. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_se_analytics: + description: + - Disable node (service engine) level analytics forvs metrics. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_server_analytics: + description: + - Disable analytics on backend servers. + - This may be desired in container environment when there are large number of ephemeral servers. + - Additionally, no healthscore of servers is computed when server analytics is disabled. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_vs_analytics: + description: + - Disable virtualservice (frontend) analytics. + - This flag disables metrics and healthscore for virtualservice. + - Field introduced in 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_advanced_analytics: + description: + - Enables advanced analytics features like anomaly detection. + - If set to false, anomaly computation (and associated rules/events) for vs, pool and server metrics will be disabled. + - However, setting it to false reduces cpu and memory requirements for analytics subsystem. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + exclude_client_close_before_request_as_error: + description: + - Exclude client closed connection before an http request could be completed from being classified as an error. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_dns_policy_drop_as_significant: + description: + - Exclude dns policy drops from the list of errors. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_gs_down_as_error: + description: + - Exclude queries to gslb services that are operationally down from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_http_error_codes: + description: + - List of http status codes to be excluded from being classified as an error. + - Error connections or responses impacts health score, are included as significant logs, and may be classified as part of a dos attack. + exclude_invalid_dns_domain_as_error: + description: + - Exclude dns queries to domains outside the domains configured in the dns application profile from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_invalid_dns_query_as_error: + description: + - Exclude invalid dns queries from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_no_dns_record_as_error: + description: + - Exclude queries to domains that did not have configured services/records from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_no_valid_gs_member_as_error: + description: + - Exclude queries to gslb services that have no available members from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_persistence_change_as_error: + description: + - Exclude persistence server changed while load balancing' from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_server_dns_error_as_error: + description: + - Exclude server dns error response from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_server_tcp_reset_as_error: + description: + - Exclude server tcp reset from errors. + - It is common for applications like ms exchange. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_sip_error_codes: + description: + - List of sip status codes to be excluded from being classified as an error. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + exclude_syn_retransmit_as_error: + description: + - Exclude 'server unanswered syns' from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_tcp_reset_as_error: + description: + - Exclude tcp resets by client from the list of potential errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + exclude_unsupported_dns_query_as_error: + description: + - Exclude unsupported dns queries from the list of errors. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + healthscore_max_server_limit: + description: + - Skips health score computation of pool servers when number of servers in a pool is more than this setting. + - Allowed values are 0-5000. + - Special values are 0- 'server health score is disabled'. + - Field introduced in 17.2.13, 18.1.4. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + hs_event_throttle_window: + description: + - Time window (in secs) within which only unique health change events should occur. + - Default value when not specified in API or module is interpreted by Avi Controller as 1209600. + hs_max_anomaly_penalty: + description: + - Maximum penalty that may be deducted from health score for anomalies. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + hs_max_resources_penalty: + description: + - Maximum penalty that may be deducted from health score for high resource utilization. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 25. + hs_max_security_penalty: + description: + - Maximum penalty that may be deducted from health score based on security assessment. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + hs_min_dos_rate: + description: + - Dos connection rate below which the dos security assessment will not kick in. + - Default value when not specified in API or module is interpreted by Avi Controller as 1000. + hs_performance_boost: + description: + - Adds free performance score credits to health score. + - It can be used for compensating health score for known slow applications. + - Allowed values are 0-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + hs_pscore_traffic_threshold_l4_client: + description: + - Threshold number of connections in 5min, below which apdexr, apdexc, rum_apdex, and other network quality metrics are not computed. + - Default value when not specified in API or module is interpreted by Avi Controller as 10.0. + hs_pscore_traffic_threshold_l4_server: + description: + - Threshold number of connections in 5min, below which apdexr, apdexc, rum_apdex, and other network quality metrics are not computed. + - Default value when not specified in API or module is interpreted by Avi Controller as 10.0. + hs_security_certscore_expired: + description: + - Score assigned when the certificate has expired. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_certscore_gt30d: + description: + - Score assigned when the certificate expires in more than 30 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_certscore_le07d: + description: + - Score assigned when the certificate expires in less than or equal to 7 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 2.0. + hs_security_certscore_le30d: + description: + - Score assigned when the certificate expires in less than or equal to 30 days. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 4.0. + hs_security_chain_invalidity_penalty: + description: + - Penalty for allowing certificates with invalid chain. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_cipherscore_eq000b: + description: + - Score assigned when the minimum cipher strength is 0 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_cipherscore_ge128b: + description: + - Score assigned when the minimum cipher strength is greater than equal to 128 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_cipherscore_lt128b: + description: + - Score assigned when the minimum cipher strength is less than 128 bits. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 3.5. + hs_security_encalgo_score_none: + description: + - Score assigned when no algorithm is used for encryption. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + hs_security_encalgo_score_rc4: + description: + - Score assigned when rc4 algorithm is used for encryption. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 2.5. + hs_security_hsts_penalty: + description: + - Penalty for not enabling hsts. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_nonpfs_penalty: + description: + - Penalty for allowing non-pfs handshakes. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_selfsignedcert_penalty: + description: + - Deprecated. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + hs_security_ssl30_score: + description: + - Score assigned when supporting ssl3.0 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 3.5. + hs_security_tls10_score: + description: + - Score assigned when supporting tls1.0 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_tls11_score: + description: + - Score assigned when supporting tls1.1 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_tls12_score: + description: + - Score assigned when supporting tls1.2 encryption protocol. + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 5.0. + hs_security_weak_signature_algo_penalty: + description: + - Penalty for allowing weak signature algorithm(s). + - Allowed values are 0-5. + - Default value when not specified in API or module is interpreted by Avi Controller as 1.0. + name: + description: + - The name of the analytics profile. + required: true + ondemand_metrics_idle_timeout: + description: + - This flag sets the time duration of no live data traffic after which virtual service metrics processing is suspended. + - It is applicable only when disable_ondemand_metrics is set to false. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1800. + ranges: + description: + - List of http status code ranges to be excluded from being classified as an error. + resp_code_block: + description: + - Block of http response codes to be excluded from being classified as an error. + - Enum options - AP_HTTP_RSP_4XX, AP_HTTP_RSP_5XX. + sensitive_log_profile: + description: + - Rules applied to the http application log for filtering sensitive information. + - Field introduced in 17.2.10, 18.1.2. + sip_log_depth: + description: + - Maximum number of sip messages added in logs for a sip transaction. + - By default, this value is 20. + - Allowed values are 1-1000. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the analytics profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a custom Analytics profile object + community.network.avi_analyticsprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + apdex_response_threshold: 500 + apdex_response_tolerated_factor: 4.0 + apdex_rtt_threshold: 250 + apdex_rtt_tolerated_factor: 4.0 + apdex_rum_threshold: 5000 + apdex_rum_tolerated_factor: 4.0 + apdex_server_response_threshold: 400 + apdex_server_response_tolerated_factor: 4.0 + apdex_server_rtt_threshold: 125 + apdex_server_rtt_tolerated_factor: 4.0 + conn_lossy_ooo_threshold: 50 + conn_lossy_timeo_rexmt_threshold: 20 + conn_lossy_total_rexmt_threshold: 50 + conn_lossy_zero_win_size_event_threshold: 2 + conn_server_lossy_ooo_threshold: 50 + conn_server_lossy_timeo_rexmt_threshold: 20 + conn_server_lossy_total_rexmt_threshold: 50 + conn_server_lossy_zero_win_size_event_threshold: 2 + disable_se_analytics: false + disable_server_analytics: false + exclude_client_close_before_request_as_error: false + exclude_persistence_change_as_error: false + exclude_server_tcp_reset_as_error: false + exclude_syn_retransmit_as_error: false + exclude_tcp_reset_as_error: false + hs_event_throttle_window: 1209600 + hs_max_anomaly_penalty: 10 + hs_max_resources_penalty: 25 + hs_max_security_penalty: 100 + hs_min_dos_rate: 1000 + hs_performance_boost: 20 + hs_pscore_traffic_threshold_l4_client: 10.0 + hs_pscore_traffic_threshold_l4_server: 10.0 + hs_security_certscore_expired: 0.0 + hs_security_certscore_gt30d: 5.0 + hs_security_certscore_le07d: 2.0 + hs_security_certscore_le30d: 4.0 + hs_security_chain_invalidity_penalty: 1.0 + hs_security_cipherscore_eq000b: 0.0 + hs_security_cipherscore_ge128b: 5.0 + hs_security_cipherscore_lt128b: 3.5 + hs_security_encalgo_score_none: 0.0 + hs_security_encalgo_score_rc4: 2.5 + hs_security_hsts_penalty: 0.0 + hs_security_nonpfs_penalty: 1.0 + hs_security_selfsignedcert_penalty: 1.0 + hs_security_ssl30_score: 3.5 + hs_security_tls10_score: 5.0 + hs_security_tls11_score: 5.0 + hs_security_tls12_score: 5.0 + hs_security_weak_signature_algo_penalty: 1.0 + name: jason-analytics-profile + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: AnalyticsProfile (api/analyticsprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + apdex_response_threshold=dict(type='int',), + apdex_response_tolerated_factor=dict(type='float',), + apdex_rtt_threshold=dict(type='int',), + apdex_rtt_tolerated_factor=dict(type='float',), + apdex_rum_threshold=dict(type='int',), + apdex_rum_tolerated_factor=dict(type='float',), + apdex_server_response_threshold=dict(type='int',), + apdex_server_response_tolerated_factor=dict(type='float',), + apdex_server_rtt_threshold=dict(type='int',), + apdex_server_rtt_tolerated_factor=dict(type='float',), + client_log_config=dict(type='dict',), + client_log_streaming_config=dict(type='dict',), + conn_lossy_ooo_threshold=dict(type='int',), + conn_lossy_timeo_rexmt_threshold=dict(type='int',), + conn_lossy_total_rexmt_threshold=dict(type='int',), + conn_lossy_zero_win_size_event_threshold=dict(type='int',), + conn_server_lossy_ooo_threshold=dict(type='int',), + conn_server_lossy_timeo_rexmt_threshold=dict(type='int',), + conn_server_lossy_total_rexmt_threshold=dict(type='int',), + conn_server_lossy_zero_win_size_event_threshold=dict(type='int',), + description=dict(type='str',), + disable_ondemand_metrics=dict(type='bool',), + disable_se_analytics=dict(type='bool',), + disable_server_analytics=dict(type='bool',), + disable_vs_analytics=dict(type='bool',), + enable_advanced_analytics=dict(type='bool',), + exclude_client_close_before_request_as_error=dict(type='bool',), + exclude_dns_policy_drop_as_significant=dict(type='bool',), + exclude_gs_down_as_error=dict(type='bool',), + exclude_http_error_codes=dict(type='list',), + exclude_invalid_dns_domain_as_error=dict(type='bool',), + exclude_invalid_dns_query_as_error=dict(type='bool',), + exclude_no_dns_record_as_error=dict(type='bool',), + exclude_no_valid_gs_member_as_error=dict(type='bool',), + exclude_persistence_change_as_error=dict(type='bool',), + exclude_server_dns_error_as_error=dict(type='bool',), + exclude_server_tcp_reset_as_error=dict(type='bool',), + exclude_sip_error_codes=dict(type='list',), + exclude_syn_retransmit_as_error=dict(type='bool',), + exclude_tcp_reset_as_error=dict(type='bool',), + exclude_unsupported_dns_query_as_error=dict(type='bool',), + healthscore_max_server_limit=dict(type='int',), + hs_event_throttle_window=dict(type='int',), + hs_max_anomaly_penalty=dict(type='int',), + hs_max_resources_penalty=dict(type='int',), + hs_max_security_penalty=dict(type='int',), + hs_min_dos_rate=dict(type='int',), + hs_performance_boost=dict(type='int',), + hs_pscore_traffic_threshold_l4_client=dict(type='float',), + hs_pscore_traffic_threshold_l4_server=dict(type='float',), + hs_security_certscore_expired=dict(type='float',), + hs_security_certscore_gt30d=dict(type='float',), + hs_security_certscore_le07d=dict(type='float',), + hs_security_certscore_le30d=dict(type='float',), + hs_security_chain_invalidity_penalty=dict(type='float',), + hs_security_cipherscore_eq000b=dict(type='float',), + hs_security_cipherscore_ge128b=dict(type='float',), + hs_security_cipherscore_lt128b=dict(type='float',), + hs_security_encalgo_score_none=dict(type='float',), + hs_security_encalgo_score_rc4=dict(type='float',), + hs_security_hsts_penalty=dict(type='float',), + hs_security_nonpfs_penalty=dict(type='float',), + hs_security_selfsignedcert_penalty=dict(type='float',), + hs_security_ssl30_score=dict(type='float',), + hs_security_tls10_score=dict(type='float',), + hs_security_tls11_score=dict(type='float',), + hs_security_tls12_score=dict(type='float',), + hs_security_weak_signature_algo_penalty=dict(type='float',), + name=dict(type='str', required=True), + ondemand_metrics_idle_timeout=dict(type='int',), + ranges=dict(type='list',), + resp_code_block=dict(type='list',), + sensitive_log_profile=dict(type='dict',), + sip_log_depth=dict(type='int',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'analyticsprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_session.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_session.py new file mode 100644 index 00000000..147e7c07 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_session.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: not supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_api_session +author: Gaurav Rastogi (@grastogi23) + +short_description: Avi API Module +description: + - This module can be used for calling any resources defined in Avi REST API. U(https://avinetworks.com/) + - This module is useful for invoking HTTP Patch methods and accessing resources that do not have an REST object associated with them. +requirements: [ avisdk ] +options: + http_method: + description: + - Allowed HTTP methods for RESTful services and are supported by Avi Controller. + choices: ["get", "put", "post", "patch", "delete"] + required: true + data: + description: + - HTTP body in YAML or JSON format. + params: + description: + - Query parameters passed to the HTTP API. + path: + description: + - 'Path for Avi API resource. For example, C(path: virtualservice) will translate to C(api/virtualserivce).' + timeout: + description: + - Timeout (in seconds) for Avi API calls. + default: 60 +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + + - name: Get Pool Information using avi_api_session + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: get + path: pool + params: + name: "{{ pool_name }}" + api_version: 16.4 + register: pool_results + + - name: Patch Pool with list of servers + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: patch + path: "{{ pool_path }}" + api_version: 16.4 + data: + add: + servers: + - ip: + addr: 10.10.10.10 + type: V4 + - ip: + addr: 20.20.20.20 + type: V4 + register: updated_pool + + - name: Fetch Pool metrics bandwidth and connections rate + community.network.avi_api_session: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + http_method: get + path: analytics/metrics/pool + api_version: 16.4 + params: + name: "{{ pool_name }}" + metric_id: l4_server.avg_bandwidth,l4_server.avg_complete_conns + step: 300 + limit: 10 + register: pool_metrics + +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, avi_obj_cmp, + cleanup_absent_fields, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + http_method=dict(required=True, + choices=['get', 'put', 'post', 'patch', + 'delete']), + path=dict(type='str', required=True), + params=dict(type='dict'), + data=dict(type='jsonarg'), + timeout=dict(type='int', default=60) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + tenant_uuid = api_creds.tenant_uuid + tenant = api_creds.tenant + timeout = int(module.params.get('timeout')) + # path is a required argument + path = module.params.get('path', '') + params = module.params.get('params', None) + data = module.params.get('data', None) + # Get the api_version from module. + api_version = api_creds.api_version + if data is not None: + data = json.loads(data) + method = module.params['http_method'] + + existing_obj = None + changed = method != 'get' + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + + # API methods not allowed + api_get_not_allowed = ["cluster", "gslbsiteops"] + api_post_not_allowed = ["alert", "fileservice"] + api_put_not_allowed = ["backup"] + + if method == 'post' and not any(path.startswith(uri) for uri in api_post_not_allowed): + # TODO: Above condition should be updated after AV-38981 is fixed + # need to check if object already exists. In that case + # change the method to be put + try: + using_collection = False + if not any(path.startswith(uri) for uri in api_get_not_allowed): + if 'name' in data: + gparams['name'] = data['name'] + using_collection = True + if not any(path.startswith(uri) for uri in api_get_not_allowed): + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + existing_obj = rsp.json() + if using_collection: + existing_obj = existing_obj['results'][0] + except (IndexError, KeyError): + # object is not found + pass + else: + if not any(path.startswith(uri) for uri in api_get_not_allowed): + # object is present + method = 'put' + path += '/' + existing_obj['uuid'] + + if method == 'put' and not any(path.startswith(uri) for uri in api_put_not_allowed): + # put can happen with when full path is specified or it is put + post + if existing_obj is None: + using_collection = False + if ((len(path.split('/')) == 1) and ('name' in data) and + (not any(path.startswith(uri) for uri in api_get_not_allowed))): + gparams['name'] = data['name'] + using_collection = True + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + rsp_data = rsp.json() + if using_collection: + if rsp_data['results']: + existing_obj = rsp_data['results'][0] + path += '/' + existing_obj['uuid'] + else: + method = 'post' + else: + if rsp.status_code == 404: + method = 'post' + else: + existing_obj = rsp_data + if existing_obj: + changed = not avi_obj_cmp(data, existing_obj) + cleanup_absent_fields(data) + if method == 'patch': + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + existing_obj = rsp.json() + + if (method == 'put' and changed) or (method != 'put'): + fn = getattr(api, method) + rsp = fn(path, tenant=tenant, tenant_uuid=tenant, timeout=timeout, + params=params, data=data, api_version=api_version) + else: + rsp = None + if method == 'delete' and rsp.status_code == 404: + changed = False + rsp.status_code = 200 + if method == 'patch' and existing_obj and rsp.status_code < 299: + # Ideally the comparison should happen with the return values + # from the patch API call. However, currently Avi API are + # returning different hostname when GET is used vs Patch. + # tracked as AV-12561 + if path.startswith('pool'): + time.sleep(1) + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid, + params=gparams, api_version=api_version) + new_obj = rsp.json() + changed = not avi_obj_cmp(new_obj, existing_obj) + if rsp is None: + return module.exit_json(changed=changed, obj=existing_obj) + return ansible_return(module, rsp, changed, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_version.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_version.py new file mode 100644 index 00000000..5b5ea5f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_api_version.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +""" +# Created on July 24, 2017 +# +# @author: Vilian Atmadzhov (vilian.atmadzhov@paddypowerbetfair.com) GitHub ID: vivobg +# +# module_check: not supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# Vilian Atmadzhov, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_api_version +author: Vilian Atmadzhov (@vivobg) + +short_description: Avi API Version Module +description: + - This module can be used to obtain the version of the Avi REST API. U(https://avinetworks.com/) +requirements: [ avisdk ] +options: {} +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Get AVI API version + community.network.avi_api_version: + controller: "" + username: "" + password: "" + tenant: "" + register: avi_controller_version +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict() + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + try: + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, + password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + remote_api_version = api.remote_api_version + remote = {} + for key in remote_api_version.keys(): + remote[key.lower()] = remote_api_version[key] + api.close() + module.exit_json(changed=False, obj=remote) + except Exception as e: + module.fail_json(msg=("Unable to get an AVI session. %s" % e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationpersistenceprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationpersistenceprofile.py new file mode 100644 index 00000000..e43967d9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationpersistenceprofile.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_applicationpersistenceprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ApplicationPersistenceProfile Avi RESTful Object +description: + - This module is used to configure ApplicationPersistenceProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + app_cookie_persistence_profile: + description: + - Specifies the application cookie persistence profile parameters. + description: + description: + - User defined description for the object. + hdr_persistence_profile: + description: + - Specifies the custom http header persistence profile parameters. + http_cookie_persistence_profile: + description: + - Specifies the http cookie persistence profile parameters. + ip_persistence_profile: + description: + - Specifies the client ip persistence profile parameters. + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - A user-friendly name for the persistence profile. + required: true + persistence_type: + description: + - Method used to persist clients to the same server for a duration of time or a session. + - Enum options - PERSISTENCE_TYPE_CLIENT_IP_ADDRESS, PERSISTENCE_TYPE_HTTP_COOKIE, PERSISTENCE_TYPE_TLS, PERSISTENCE_TYPE_CLIENT_IPV6_ADDRESS, + - PERSISTENCE_TYPE_CUSTOM_HTTP_HEADER, PERSISTENCE_TYPE_APP_COOKIE, PERSISTENCE_TYPE_GSLB_SITE. + - Default value when not specified in API or module is interpreted by Avi Controller as PERSISTENCE_TYPE_CLIENT_IP_ADDRESS. + required: true + server_hm_down_recovery: + description: + - Specifies behavior when a persistent server has been marked down by a health monitor. + - Enum options - HM_DOWN_PICK_NEW_SERVER, HM_DOWN_ABORT_CONNECTION, HM_DOWN_CONTINUE_PERSISTENT_SERVER. + - Default value when not specified in API or module is interpreted by Avi Controller as HM_DOWN_PICK_NEW_SERVER. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the persistence profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Application Persistence setting using http cookie. + community.network.avi_applicationpersistenceprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + http_cookie_persistence_profile: + always_send_cookie: false + cookie_name: My-HTTP + key: + - aes_key: ShYGZdMks8j6Bpvm2sCvaXWzvXms2Z9ob+TTjRy46lQ= + name: c1276819-550c-4adf-912d-59efa5fd7269 + - aes_key: OGsyVk84VCtyMENFOW0rMnRXVnNrb0RzdG5mT29oamJRb0dlbHZVSjR1az0= + name: a080de57-77c3-4580-a3ea-e7a6493c14fd + - aes_key: UVN0cU9HWmFUM2xOUzBVcmVXaHFXbnBLVUUxMU1VSktSVU5HWjJOWmVFMTBUMUV4UmxsNk4xQmFZejA9 + name: 60478846-33c6-484d-868d-bbc324fce4a5 + timeout: 15 + name: My-HTTP-Cookie + persistence_type: PERSISTENCE_TYPE_HTTP_COOKIE + server_hm_down_recovery: HM_DOWN_PICK_NEW_SERVER + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: ApplicationPersistenceProfile (api/applicationpersistenceprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + app_cookie_persistence_profile=dict(type='dict',), + description=dict(type='str',), + hdr_persistence_profile=dict(type='dict',), + http_cookie_persistence_profile=dict(type='dict',), + ip_persistence_profile=dict(type='dict',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + persistence_type=dict(type='str', required=True), + server_hm_down_recovery=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'applicationpersistenceprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationprofile.py new file mode 100644 index 00000000..456c66a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_applicationprofile.py @@ -0,0 +1,217 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_applicationprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ApplicationProfile Avi RESTful Object +description: + - This module is used to configure ApplicationProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of application profiles. + - Internally set by cloud connector. + - Field introduced in 17.2.14, 18.1.5, 18.2.1. + created_by: + description: + - Name of the application profile creator. + - Field introduced in 17.2.14, 18.1.5, 18.2.1. + description: + description: + - User defined description for the object. + dns_service_profile: + description: + - Specifies various dns service related controls for virtual service. + dos_rl_profile: + description: + - Specifies various security related controls for virtual service. + http_profile: + description: + - Specifies the http application proxy profile parameters. + name: + description: + - The name of the application profile. + required: true + preserve_client_ip: + description: + - Specifies if client ip needs to be preserved for backend connection. + - Not compatible with connection multiplexing. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + preserve_client_port: + description: + - Specifies if we need to preserve client port while preserving client ip for backend connections. + - Field introduced in 17.2.7. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + sip_service_profile: + description: + - Specifies various sip service related controls for virtual service. + - Field introduced in 17.2.8, 18.1.3, 18.2.1. + tcp_app_profile: + description: + - Specifies the tcp application proxy profile parameters. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Specifies which application layer proxy is enabled for the virtual service. + - Enum options - APPLICATION_PROFILE_TYPE_L4, APPLICATION_PROFILE_TYPE_HTTP, APPLICATION_PROFILE_TYPE_SYSLOG, APPLICATION_PROFILE_TYPE_DNS, + - APPLICATION_PROFILE_TYPE_SSL, APPLICATION_PROFILE_TYPE_SIP. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the application profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Application Profile for HTTP application enabled for SSL traffic + community.network.avi_applicationprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + http_profile: + cache_config: + age_header: true + aggressive: false + date_header: true + default_expire: 600 + enabled: false + heuristic_expire: false + max_cache_size: 0 + max_object_size: 4194304 + mime_types_group_refs: + - admin:System-Cacheable-Resource-Types + min_object_size: 100 + query_cacheable: false + xcache_header: true + client_body_timeout: 0 + client_header_timeout: 10000 + client_max_body_size: 0 + client_max_header_size: 12 + client_max_request_size: 48 + compression_profile: + compressible_content_ref: admin:System-Compressible-Content-Types + compression: false + remove_accept_encoding_header: true + type: AUTO_COMPRESSION + connection_multiplexing_enabled: true + hsts_enabled: false + hsts_max_age: 365 + http_to_https: false + httponly_enabled: false + keepalive_header: false + keepalive_timeout: 30000 + max_bad_rps_cip: 0 + max_bad_rps_cip_uri: 0 + max_bad_rps_uri: 0 + max_rps_cip: 0 + max_rps_cip_uri: 0 + max_rps_unknown_cip: 0 + max_rps_unknown_uri: 0 + max_rps_uri: 0 + post_accept_timeout: 30000 + secure_cookie_enabled: false + server_side_redirect_to_https: false + spdy_enabled: false + spdy_fwd_proxy_mode: false + ssl_client_certificate_mode: SSL_CLIENT_CERTIFICATE_NONE + ssl_everywhere_enabled: false + websockets_enabled: true + x_forwarded_proto_enabled: false + xff_alternate_name: X-Forwarded-For + xff_enabled: true + name: System-HTTP + tenant_ref: admin + type: APPLICATION_PROFILE_TYPE_HTTP +""" + +RETURN = ''' +obj: + description: ApplicationProfile (api/applicationprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + dns_service_profile=dict(type='dict',), + dos_rl_profile=dict(type='dict',), + http_profile=dict(type='dict',), + name=dict(type='str', required=True), + preserve_client_ip=dict(type='bool',), + preserve_client_port=dict(type='bool',), + sip_service_profile=dict(type='dict',), + tcp_app_profile=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'applicationprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_authprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_authprofile.py new file mode 100644 index 00000000..88667db1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_authprofile.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_authprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of AuthProfile Avi RESTful Object +description: + - This module is used to configure AuthProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + http: + description: + - Http user authentication params. + ldap: + description: + - Ldap server and directory settings. + name: + description: + - Name of the auth profile. + required: true + pa_agent_ref: + description: + - Pingaccessagent uuid. + - It is a reference to an object of type pingaccessagent. + - Field introduced in 18.2.3. + saml: + description: + - Saml settings. + - Field introduced in 17.2.3. + tacacs_plus: + description: + - Tacacs+ settings. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of the auth profile. + - Enum options - AUTH_PROFILE_LDAP, AUTH_PROFILE_TACACS_PLUS, AUTH_PROFILE_SAML, AUTH_PROFILE_PINGACCESS. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the auth profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create user authorization profile based on the LDAP + community.network.avi_authprofile: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + http: + cache_expiration_time: 5 + group_member_is_full_dn: false + ldap: + base_dn: dc=avi,dc=local + bind_as_administrator: true + port: 389 + security_mode: AUTH_LDAP_SECURE_NONE + server: + - 10.10.0.100 + settings: + admin_bind_dn: user@avi.local + group_filter: (objectClass=*) + group_member_attribute: member + group_member_is_full_dn: true + group_search_dn: dc=avi,dc=local + group_search_scope: AUTH_LDAP_SCOPE_SUBTREE + ignore_referrals: true + password: password + user_id_attribute: samAccountname + user_search_dn: dc=avi,dc=local + user_search_scope: AUTH_LDAP_SCOPE_ONE + name: ProdAuth + tenant_ref: admin + type: AUTH_PROFILE_LDAP +""" + +RETURN = ''' +obj: + description: AuthProfile (api/authprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + http=dict(type='dict',), + ldap=dict(type='dict',), + name=dict(type='str', required=True), + pa_agent_ref=dict(type='str',), + saml=dict(type='dict',), + tacacs_plus=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'authprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_autoscalelaunchconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_autoscalelaunchconfig.py new file mode 100644 index 00000000..39e93f83 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_autoscalelaunchconfig.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_autoscalelaunchconfig +author: Chaitanya Deshpande (@chaitanyaavi) + +short_description: Module for setup of AutoScaleLaunchConfig Avi RESTful Object +description: + - This module is used to configure AutoScaleLaunchConfig object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + image_id: + description: + - Unique id of the amazon machine image (ami) or openstack vm id. + mesos: + description: + - Autoscalemesossettings settings for autoscalelaunchconfig. + name: + description: + - Name of the object. + required: true + openstack: + description: + - Autoscaleopenstacksettings settings for autoscalelaunchconfig. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_external_asg: + description: + - If set to true, serverautoscalepolicy will use the autoscaling group (external_autoscaling_groups) from pool to perform scale up and scale down. + - Pool should have single autoscaling group configured. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an Autoscale Launch configuration. + community.network.avi_autoscalelaunchconfig: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + image_id: default + name: default-autoscalelaunchconfig + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: AutoScaleLaunchConfig (api/autoscalelaunchconfig) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + image_id=dict(type='str',), + mesos=dict(type='dict',), + name=dict(type='str', required=True), + openstack=dict(type='dict',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_external_asg=dict(type='bool',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'autoscalelaunchconfig', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backup.py new file mode 100644 index 00000000..f052d0a4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backup.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_backup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Backup Avi RESTful Object +description: + - This module is used to configure Backup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + backup_config_ref: + description: + - Backupconfiguration information. + - It is a reference to an object of type backupconfiguration. + file_name: + description: + - The file name of backup. + required: true + local_file_url: + description: + - Url to download the backup file. + remote_file_url: + description: + - Url to download the backup file. + scheduler_ref: + description: + - Scheduler information. + - It is a reference to an object of type scheduler. + tenant_ref: + description: + - It is a reference to an object of type tenant. + timestamp: + description: + - Unix timestamp of when the backup file is created. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Backup object + community.network.avi_backup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_backup +""" + +RETURN = ''' +obj: + description: Backup (api/backup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + backup_config_ref=dict(type='str',), + file_name=dict(type='str', required=True), + local_file_url=dict(type='str',), + remote_file_url=dict(type='str',), + scheduler_ref=dict(type='str',), + tenant_ref=dict(type='str',), + timestamp=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'backup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backupconfiguration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backupconfiguration.py new file mode 100644 index 00000000..7ba546a0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_backupconfiguration.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_backupconfiguration +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of BackupConfiguration Avi RESTful Object +description: + - This module is used to configure BackupConfiguration object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + aws_access_key: + description: + - Aws access key id. + - Field introduced in 18.2.3. + aws_bucket_id: + description: + - Aws bucket. + - Field introduced in 18.2.3. + aws_secret_access: + description: + - Aws secret access key. + - Field introduced in 18.2.3. + backup_file_prefix: + description: + - Prefix of the exported configuration file. + - Field introduced in 17.1.1. + backup_passphrase: + description: + - Passphrase of backup configuration. + maximum_backups_stored: + description: + - Rotate the backup files based on this count. + - Allowed values are 1-20. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + name: + description: + - Name of backup configuration. + required: true + remote_directory: + description: + - Directory at remote destination with write permission for ssh user. + remote_hostname: + description: + - Remote destination. + save_local: + description: + - Local backup. + type: bool + ssh_user_ref: + description: + - Access credentials for remote destination. + - It is a reference to an object of type cloudconnectoruser. + tenant_ref: + description: + - It is a reference to an object of type tenant. + upload_to_remote_host: + description: + - Remote backup. + type: bool + upload_to_s3: + description: + - Cloud backup. + - Field introduced in 18.2.3. + type: bool + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create BackupConfiguration object + community.network.avi_backupconfiguration: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_backupconfiguration +""" + +RETURN = ''' +obj: + description: BackupConfiguration (api/backupconfiguration) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + aws_access_key=dict(type='str', no_log=True,), + aws_bucket_id=dict(type='str',), + aws_secret_access=dict(type='str', no_log=True,), + backup_file_prefix=dict(type='str',), + backup_passphrase=dict(type='str', no_log=True,), + maximum_backups_stored=dict(type='int',), + name=dict(type='str', required=True), + remote_directory=dict(type='str',), + remote_hostname=dict(type='str',), + save_local=dict(type='bool',), + ssh_user_ref=dict(type='str',), + tenant_ref=dict(type='str',), + upload_to_remote_host=dict(type='bool',), + upload_to_s3=dict(type='bool',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'backupconfiguration', + set(['backup_passphrase', 'aws_access_key', 'aws_secret_access'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_certificatemanagementprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_certificatemanagementprofile.py new file mode 100644 index 00000000..aaac46e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_certificatemanagementprofile.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_certificatemanagementprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CertificateManagementProfile Avi RESTful Object +description: + - This module is used to configure CertificateManagementProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the pki profile. + required: true + script_params: + description: + - List of customparams. + script_path: + description: + - Script_path of certificatemanagementprofile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CertificateManagementProfile object + community.network.avi_certificatemanagementprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_certificatemanagementprofile +""" + +RETURN = ''' +obj: + description: CertificateManagementProfile (api/certificatemanagementprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + script_params=dict(type='list',), + script_path=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'certificatemanagementprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloud.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloud.py new file mode 100644 index 00000000..568d68ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloud.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloud +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Cloud Avi RESTful Object +description: + - This module is used to configure Cloud object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + apic_configuration: + description: + - Apicconfiguration settings for cloud. + apic_mode: + description: + - Boolean flag to set apic_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + autoscale_polling_interval: + description: + - Cloudconnector polling interval for external autoscale groups. + - Field introduced in 18.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + aws_configuration: + description: + - Awsconfiguration settings for cloud. + azure_configuration: + description: + - Field introduced in 17.2.1. + cloudstack_configuration: + description: + - Cloudstackconfiguration settings for cloud. + custom_tags: + description: + - Custom tags for all avi created resources in the cloud infrastructure. + - Field introduced in 17.1.5. + dhcp_enabled: + description: + - Select the ip address management scheme. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + dns_provider_ref: + description: + - Dns profile for the cloud. + - It is a reference to an object of type ipamdnsproviderprofile. + docker_configuration: + description: + - Dockerconfiguration settings for cloud. + east_west_dns_provider_ref: + description: + - Dns profile for east-west services. + - It is a reference to an object of type ipamdnsproviderprofile. + east_west_ipam_provider_ref: + description: + - Ipam profile for east-west services. + - Warning - please use virtual subnets in this ipam profile that do not conflict with the underlay networks or any overlay networks in the cluster. + - For example in aws and gcp, 169.254.0.0/16 is used for storing instance metadata. + - Hence, it should not be used in this profile. + - It is a reference to an object of type ipamdnsproviderprofile. + enable_vip_static_routes: + description: + - Use static routes for vip side network resolution during virtualservice placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + gcp_configuration: + description: + - Google cloud platform configuration. + - Field introduced in 18.2.1. + ip6_autocfg_enabled: + description: + - Enable ipv6 auto configuration. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ipam_provider_ref: + description: + - Ipam profile for the cloud. + - It is a reference to an object of type ipamdnsproviderprofile. + license_tier: + description: + - Specifies the default license tier which would be used by new se groups. + - This field by default inherits the value from system configuration. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + license_type: + description: + - If no license type is specified then default license enforcement for the cloud type is chosen. + - The default mappings are container cloud is max ses, openstack and vmware is cores and linux it is sockets. + - Enum options - LIC_BACKEND_SERVERS, LIC_SOCKETS, LIC_CORES, LIC_HOSTS, LIC_SE_BANDWIDTH, LIC_METERED_SE_BANDWIDTH. + linuxserver_configuration: + description: + - Linuxserverconfiguration settings for cloud. + mesos_configuration: + description: + - Field deprecated in 18.2.2. + mtu: + description: + - Mtu setting for the cloud. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + name: + description: + - Name of the object. + required: true + nsx_configuration: + description: + - Configuration parameters for nsx manager. + - Field introduced in 17.1.1. + obj_name_prefix: + description: + - Default prefix for all automatically created objects in this cloud. + - This prefix can be overridden by the se-group template. + openstack_configuration: + description: + - Openstackconfiguration settings for cloud. + oshiftk8s_configuration: + description: + - Oshiftk8sconfiguration settings for cloud. + prefer_static_routes: + description: + - Prefer static routes over interface routes during virtualservice placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + proxy_configuration: + description: + - Proxyconfiguration settings for cloud. + rancher_configuration: + description: + - Rancherconfiguration settings for cloud. + state_based_dns_registration: + description: + - Dns records for vips are added/deleted based on the operational state of the vips. + - Field introduced in 17.1.12. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vca_configuration: + description: + - Vcloudairconfiguration settings for cloud. + vcenter_configuration: + description: + - Vcenterconfiguration settings for cloud. + vtype: + description: + - Cloud type. + - Enum options - CLOUD_NONE, CLOUD_VCENTER, CLOUD_OPENSTACK, CLOUD_AWS, CLOUD_VCA, CLOUD_APIC, CLOUD_MESOS, CLOUD_LINUXSERVER, CLOUD_DOCKER_UCP, + - CLOUD_RANCHER, CLOUD_OSHIFT_K8S, CLOUD_AZURE, CLOUD_GCP. + - Default value when not specified in API or module is interpreted by Avi Controller as CLOUD_NONE. + required: true +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a VMware cloud with write access mode + community.network.avi_cloud: + username: '{{ username }}' + controller: '{{ controller }}' + password: '{{ password }}' + apic_mode: false + dhcp_enabled: true + enable_vip_static_routes: false + license_type: LIC_CORES + mtu: 1500 + name: vCenter Cloud + prefer_static_routes: false + tenant_ref: admin + vcenter_configuration: + datacenter_ref: /api/vimgrdcruntime/datacenter-2-10.10.20.100 + management_network: /api/vimgrnwruntime/dvportgroup-103-10.10.20.100 + password: password + privilege: WRITE_ACCESS + username: user + vcenter_url: 10.10.20.100 + vtype: CLOUD_VCENTER +""" + +RETURN = ''' +obj: + description: Cloud (api/cloud) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + apic_configuration=dict(type='dict',), + apic_mode=dict(type='bool',), + autoscale_polling_interval=dict(type='int',), + aws_configuration=dict(type='dict',), + azure_configuration=dict(type='dict',), + cloudstack_configuration=dict(type='dict',), + custom_tags=dict(type='list',), + dhcp_enabled=dict(type='bool',), + dns_provider_ref=dict(type='str',), + docker_configuration=dict(type='dict',), + east_west_dns_provider_ref=dict(type='str',), + east_west_ipam_provider_ref=dict(type='str',), + enable_vip_static_routes=dict(type='bool',), + gcp_configuration=dict(type='dict',), + ip6_autocfg_enabled=dict(type='bool',), + ipam_provider_ref=dict(type='str',), + license_tier=dict(type='str',), + license_type=dict(type='str',), + linuxserver_configuration=dict(type='dict',), + mesos_configuration=dict(type='dict',), + mtu=dict(type='int',), + name=dict(type='str', required=True), + nsx_configuration=dict(type='dict',), + obj_name_prefix=dict(type='str',), + openstack_configuration=dict(type='dict',), + oshiftk8s_configuration=dict(type='dict',), + prefer_static_routes=dict(type='bool',), + proxy_configuration=dict(type='dict',), + rancher_configuration=dict(type='dict',), + state_based_dns_registration=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + vca_configuration=dict(type='dict',), + vcenter_configuration=dict(type='dict',), + vtype=dict(type='str', required=True), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloud', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudconnectoruser.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudconnectoruser.py new file mode 100644 index 00000000..8e40269c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudconnectoruser.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloudconnectoruser +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CloudConnectorUser Avi RESTful Object +description: + - This module is used to configure CloudConnectorUser object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + azure_serviceprincipal: + description: + - Field introduced in 17.2.1. + azure_userpass: + description: + - Field introduced in 17.2.1. + gcp_credentials: + description: + - Credentials for google cloud platform. + - Field introduced in 18.2.1. + name: + description: + - Name of the object. + required: true + oci_credentials: + description: + - Credentials for oracle cloud infrastructure. + - Field introduced in 18.2.1,18.1.3. + private_key: + description: + - Private_key of cloudconnectoruser. + public_key: + description: + - Public_key of cloudconnectoruser. + tenant_ref: + description: + - It is a reference to an object of type tenant. + tencent_credentials: + description: + - Credentials for tencent cloud. + - Field introduced in 18.2.3. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a Cloud connector user that is used for integration into cloud platforms + community.network.avi_cloudconnectoruser: + controller: '{{ controller }}' + name: root + password: '{{ password }}' + private_key: | + -----BEGIN RSA PRIVATE KEY----- + -----END RSA PRIVATE KEY-----' + public_key: 'ssh-rsa ...' + tenant_ref: admin + username: '{{ username }}' +""" + +RETURN = ''' +obj: + description: CloudConnectorUser (api/cloudconnectoruser) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + azure_serviceprincipal=dict(type='dict',), + azure_userpass=dict(type='dict',), + gcp_credentials=dict(type='dict',), + name=dict(type='str', required=True), + oci_credentials=dict(type='dict',), + private_key=dict(type='str', no_log=True,), + public_key=dict(type='str',), + tenant_ref=dict(type='str',), + tencent_credentials=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloudconnectoruser', + set(['private_key'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudproperties.py new file mode 100644 index 00000000..d054a9c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cloudproperties.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cloudproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CloudProperties Avi RESTful Object +description: + - This module is used to configure CloudProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cc_props: + description: + - Cloudconnector properties. + cc_vtypes: + description: + - Cloud types supported by cloudconnector. + - Enum options - CLOUD_NONE, CLOUD_VCENTER, CLOUD_OPENSTACK, CLOUD_AWS, CLOUD_VCA, CLOUD_APIC, CLOUD_MESOS, CLOUD_LINUXSERVER, CLOUD_DOCKER_UCP, + - CLOUD_RANCHER, CLOUD_OSHIFT_K8S, CLOUD_AZURE, CLOUD_GCP. + hyp_props: + description: + - Hypervisor properties. + info: + description: + - Properties specific to a cloud type. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CloudProperties object + community.network.avi_cloudproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_cloudproperties +""" + +RETURN = ''' +obj: + description: CloudProperties (api/cloudproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cc_props=dict(type='dict',), + cc_vtypes=dict(type='list',), + hyp_props=dict(type='list',), + info=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cloudproperties', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cluster.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cluster.py new file mode 100644 index 00000000..e31feae3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_cluster.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_cluster +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Cluster Avi RESTful Object +description: + - This module is used to configure Cluster object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the object. + required: true + nodes: + description: + - List of clusternode. + rejoin_nodes_automatically: + description: + - Re-join cluster nodes automatically in the event one of the node is reset to factory. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + virtual_ip: + description: + - A virtual ip address. + - This ip address will be dynamically reconfigured so that it always is the ip of the cluster leader. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Cluster object + community.network.avi_cluster: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_cluster +""" + +RETURN = ''' +obj: + description: Cluster (api/cluster) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + nodes=dict(type='list',), + rejoin_nodes_automatically=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + virtual_ip=dict(type='dict',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'cluster', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_clusterclouddetails.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_clusterclouddetails.py new file mode 100644 index 00000000..938f3515 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_clusterclouddetails.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_clusterclouddetails +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ClusterCloudDetails Avi RESTful Object +description: + - This module is used to configure ClusterCloudDetails object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + azure_info: + description: + - Azure info to configure cluster_vip on the controller. + - Field introduced in 17.2.5. + name: + description: + - Field introduced in 17.2.5. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.5. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.5. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ClusterCloudDetails object + community.network.avi_clusterclouddetails: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_clusterclouddetails +""" + +RETURN = ''' +obj: + description: ClusterCloudDetails (api/clusterclouddetails) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + azure_info=dict(type='dict',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'clusterclouddetails', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_controllerproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_controllerproperties.py new file mode 100644 index 00000000..4b68d379 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_controllerproperties.py @@ -0,0 +1,420 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_controllerproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ControllerProperties Avi RESTful Object +description: + - This module is used to configure ControllerProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + allow_ip_forwarding: + description: + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + allow_unauthenticated_apis: + description: + - Allow unauthenticated access for special apis. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + allow_unauthenticated_nodes: + description: + - Boolean flag to set allow_unauthenticated_nodes. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + api_idle_timeout: + description: + - Allowed values are 0-1440. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + api_perf_logging_threshold: + description: + - Threshold to log request timing in portal_performance.log and server-timing response header. + - Any stage taking longer than 1% of the threshold will be included in the server-timing header. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 10000. + appviewx_compat_mode: + description: + - Export configuration in appviewx compatibility mode. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + attach_ip_retry_interval: + description: + - Number of attach_ip_retry_interval. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + attach_ip_retry_limit: + description: + - Number of attach_ip_retry_limit. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + bm_use_ansible: + description: + - Use ansible for se creation in baremetal. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + cleanup_expired_authtoken_timeout_period: + description: + - Period for auth token cleanup job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + cleanup_sessions_timeout_period: + description: + - Period for sessions cleanup job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + cloud_reconcile: + description: + - Enable/disable periodic reconcile for all the clouds. + - Field introduced in 17.2.14,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + cluster_ip_gratuitous_arp_period: + description: + - Period for cluster ip gratuitous arp job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + consistency_check_timeout_period: + description: + - Period for consistency check job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + crashed_se_reboot: + description: + - Number of crashed_se_reboot. + - Default value when not specified in API or module is interpreted by Avi Controller as 900. + dead_se_detection_timer: + description: + - Number of dead_se_detection_timer. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + dns_refresh_period: + description: + - Period for refresh pool and gslb dns job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + dummy: + description: + - Number of dummy. + enable_api_sharding: + description: + - This setting enables the controller leader to shard api requests to the followers (if any). + - Field introduced in 18.1.5, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_memory_balancer: + description: + - Enable/disable memory balancer. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + fatal_error_lease_time: + description: + - Number of fatal_error_lease_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + max_dead_se_in_grp: + description: + - Number of max_dead_se_in_grp. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_pcap_per_tenant: + description: + - Maximum number of pcap files stored per tenant. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + max_seq_attach_ip_failures: + description: + - Maximum number of consecutive attach ip failures that halts vs placement. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + max_seq_vnic_failures: + description: + - Number of max_seq_vnic_failures. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + persistence_key_rotate_period: + description: + - Period for rotate app persistence keys job. + - Allowed values are 1-1051200. + - Special values are 0 - 'disabled'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + portal_token: + description: + - Token used for uploading tech-support to portal. + - Field introduced in 16.4.6,17.1.2. + process_locked_useraccounts_timeout_period: + description: + - Period for process locked user accounts job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + process_pki_profile_timeout_period: + description: + - Period for process pki profile job. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1440. + query_host_fail: + description: + - Number of query_host_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + safenet_hsm_version: + description: + - Version of the safenet package installed on the controller. + - Field introduced in 16.5.2,17.2.3. + se_create_timeout: + description: + - Number of se_create_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 900. + se_failover_attempt_interval: + description: + - Interval between attempting failovers to an se. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + se_from_marketplace: + description: + - This setting decides whether se is to be deployed from the cloud marketplace or to be created by the controller. + - The setting is applicable only when byol license is selected. + - Enum options - MARKETPLACE, IMAGE. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as IMAGE. + se_offline_del: + description: + - Number of se_offline_del. + - Default value when not specified in API or module is interpreted by Avi Controller as 172000. + se_vnic_cooldown: + description: + - Number of se_vnic_cooldown. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + secure_channel_cleanup_timeout: + description: + - Period for secure channel cleanup job. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + secure_channel_controller_token_timeout: + description: + - Number of secure_channel_controller_token_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + secure_channel_se_token_timeout: + description: + - Number of secure_channel_se_token_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + seupgrade_fabric_pool_size: + description: + - Pool size used for all fabric commands during se upgrade. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + seupgrade_segroup_min_dead_timeout: + description: + - Time to wait before marking segroup upgrade as stuck. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + ssl_certificate_expiry_warning_days: + description: + - Number of days for ssl certificate expiry warning. + unresponsive_se_reboot: + description: + - Number of unresponsive_se_reboot. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + upgrade_dns_ttl: + description: + - Time to account for dns ttl during upgrade. + - This is in addition to vs_scalein_timeout_for_upgrade in se_group. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 5. + upgrade_lease_time: + description: + - Number of upgrade_lease_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vnic_op_fail_time: + description: + - Number of vnic_op_fail_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + vs_apic_scaleout_timeout: + description: + - Time to wait for the scaled out se to become ready before marking the scaleout done, applies to apic configuration only. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + vs_awaiting_se_timeout: + description: + - Number of vs_awaiting_se_timeout. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_key_rotate_period: + description: + - Period for rotate vs keys job. + - Allowed values are 1-1051200. + - Special values are 0 - 'disabled'. + - Default value when not specified in API or module is interpreted by Avi Controller as 360. + vs_scaleout_ready_check_interval: + description: + - Interval for checking scaleout_ready status while controller is waiting for scaleoutready rpc from the service engine. + - Field introduced in 18.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_se_attach_ip_fail: + description: + - Time to wait before marking attach ip operation on an se as failed. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + vs_se_bootup_fail: + description: + - Number of vs_se_bootup_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 480. + vs_se_create_fail: + description: + - Number of vs_se_create_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + vs_se_ping_fail: + description: + - Number of vs_se_ping_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 60. + vs_se_vnic_fail: + description: + - Number of vs_se_vnic_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + vs_se_vnic_ip_fail: + description: + - Number of vs_se_vnic_ip_fail. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + warmstart_se_reconnect_wait_time: + description: + - Number of warmstart_se_reconnect_wait_time. + - Default value when not specified in API or module is interpreted by Avi Controller as 480. + warmstart_vs_resync_wait_time: + description: + - Timeout for warmstart vs resync. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ControllerProperties object + community.network.avi_controllerproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_controllerproperties +""" + +RETURN = ''' +obj: + description: ControllerProperties (api/controllerproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + allow_ip_forwarding=dict(type='bool',), + allow_unauthenticated_apis=dict(type='bool',), + allow_unauthenticated_nodes=dict(type='bool',), + api_idle_timeout=dict(type='int',), + api_perf_logging_threshold=dict(type='int',), + appviewx_compat_mode=dict(type='bool',), + attach_ip_retry_interval=dict(type='int',), + attach_ip_retry_limit=dict(type='int',), + bm_use_ansible=dict(type='bool',), + cleanup_expired_authtoken_timeout_period=dict(type='int',), + cleanup_sessions_timeout_period=dict(type='int',), + cloud_reconcile=dict(type='bool',), + cluster_ip_gratuitous_arp_period=dict(type='int',), + consistency_check_timeout_period=dict(type='int',), + crashed_se_reboot=dict(type='int',), + dead_se_detection_timer=dict(type='int',), + dns_refresh_period=dict(type='int',), + dummy=dict(type='int',), + enable_api_sharding=dict(type='bool',), + enable_memory_balancer=dict(type='bool',), + fatal_error_lease_time=dict(type='int',), + max_dead_se_in_grp=dict(type='int',), + max_pcap_per_tenant=dict(type='int',), + max_seq_attach_ip_failures=dict(type='int',), + max_seq_vnic_failures=dict(type='int',), + persistence_key_rotate_period=dict(type='int',), + portal_token=dict(type='str', no_log=True,), + process_locked_useraccounts_timeout_period=dict(type='int',), + process_pki_profile_timeout_period=dict(type='int',), + query_host_fail=dict(type='int',), + safenet_hsm_version=dict(type='str',), + se_create_timeout=dict(type='int',), + se_failover_attempt_interval=dict(type='int',), + se_from_marketplace=dict(type='str',), + se_offline_del=dict(type='int',), + se_vnic_cooldown=dict(type='int',), + secure_channel_cleanup_timeout=dict(type='int',), + secure_channel_controller_token_timeout=dict(type='int',), + secure_channel_se_token_timeout=dict(type='int',), + seupgrade_fabric_pool_size=dict(type='int',), + seupgrade_segroup_min_dead_timeout=dict(type='int',), + ssl_certificate_expiry_warning_days=dict(type='list',), + unresponsive_se_reboot=dict(type='int',), + upgrade_dns_ttl=dict(type='int',), + upgrade_lease_time=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + vnic_op_fail_time=dict(type='int',), + vs_apic_scaleout_timeout=dict(type='int',), + vs_awaiting_se_timeout=dict(type='int',), + vs_key_rotate_period=dict(type='int',), + vs_scaleout_ready_check_interval=dict(type='int',), + vs_se_attach_ip_fail=dict(type='int',), + vs_se_bootup_fail=dict(type='int',), + vs_se_create_fail=dict(type='int',), + vs_se_ping_fail=dict(type='int',), + vs_se_vnic_fail=dict(type='int',), + vs_se_vnic_ip_fail=dict(type='int',), + warmstart_se_reconnect_wait_time=dict(type='int',), + warmstart_vs_resync_wait_time=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'controllerproperties', + set(['portal_token'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_customipamdnsprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_customipamdnsprofile.py new file mode 100644 index 00000000..074bda7b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_customipamdnsprofile.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_customipamdnsprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of CustomIpamDnsProfile Avi RESTful Object +description: + - This module is used to configure CustomIpamDnsProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the custom ipam dns profile. + - Field introduced in 17.1.1. + required: true + script_params: + description: + - Parameters that are always passed to the ipam/dns script. + - Field introduced in 17.1.1. + script_uri: + description: + - Script uri of form controller //ipamdnsscripts/. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create CustomIpamDnsProfile object + community.network.avi_customipamdnsprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_customipamdnsprofile +""" + +RETURN = ''' +obj: + description: CustomIpamDnsProfile (api/customipamdnsprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + script_params=dict(type='list',), + script_uri=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'customipamdnsprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_dnspolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_dnspolicy.py new file mode 100644 index 00000000..be23d5ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_dnspolicy.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_dnspolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of DnsPolicy Avi RESTful Object +description: + - This module is used to configure DnsPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.1.1. + description: + description: + - Field introduced in 17.1.1. + name: + description: + - Name of the dns policy. + - Field introduced in 17.1.1. + required: true + rule: + description: + - Dns rules. + - Field introduced in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the dns policy. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create DnsPolicy object + community.network.avi_dnspolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_dnspolicy +""" + +RETURN = ''' +obj: + description: DnsPolicy (api/dnspolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + rule=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'dnspolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpagebody.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpagebody.py new file mode 100644 index 00000000..709835a9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpagebody.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_errorpagebody +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ErrorPageBody Avi RESTful Object +description: + - This module is used to configure ErrorPageBody object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + error_page_body: + description: + - Error page body sent to client when match. + - Field introduced in 17.2.4. + format: + description: + - Format of an error page body html or json. + - Enum options - ERROR_PAGE_FORMAT_HTML, ERROR_PAGE_FORMAT_JSON. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as ERROR_PAGE_FORMAT_HTML. + name: + description: + - Field introduced in 17.2.4. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.4. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.4. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ErrorPageBody object + community.network.avi_errorpagebody: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_errorpagebody +""" + +RETURN = ''' +obj: + description: ErrorPageBody (api/errorpagebody) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + error_page_body=dict(type='str',), + format=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'errorpagebody', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpageprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpageprofile.py new file mode 100644 index 00000000..e594d15f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_errorpageprofile.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_errorpageprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ErrorPageProfile Avi RESTful Object +description: + - This module is used to configure ErrorPageProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + app_name: + description: + - Name of the virtual service which generated the error page. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as VS Name. + company_name: + description: + - Name of the company to show in error page. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as Avi Networks. + error_pages: + description: + - Defined error pages for http status codes. + - Field introduced in 17.2.4. + host_name: + description: + - Fully qualified domain name for which the error page is generated. + - Field deprecated in 18.1.1. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as Host Header. + name: + description: + - Field introduced in 17.2.4. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.4. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Field introduced in 17.2.4. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ErrorPageProfile object + community.network.avi_errorpageprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_errorpageprofile +""" + +RETURN = ''' +obj: + description: ErrorPageProfile (api/errorpageprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + app_name=dict(type='str',), + company_name=dict(type='str',), + error_pages=dict(type='list',), + host_name=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'errorpageprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslb.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslb.py new file mode 100644 index 00000000..ac6de5a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslb.py @@ -0,0 +1,353 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslb +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Gslb Avi RESTful Object +description: + - This module is used to configure Gslb object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + async_interval: + description: + - Frequency with which messages are propagated to vs mgr. + - Value of 0 disables async behavior and rpc are sent inline. + - Allowed values are 0-5. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + clear_on_max_retries: + description: + - Max retries after which the remote site is treated as a fresh start. + - In fresh start all the configs are downloaded. + - Allowed values are 1-1024. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + client_ip_addr_group: + description: + - Group to specify if the client ip addresses are public or private. + - Field introduced in 17.1.2. + description: + description: + - User defined description for the object. + dns_configs: + description: + - Sub domain configuration for the gslb. + - Gslb service's fqdn must be a match one of these subdomains. + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + leader_cluster_uuid: + description: + - Mark this site as leader of gslb configuration. + - This site is the one among the avi sites. + required: true + maintenance_mode: + description: + - This field disables the configuration operations on the leader for all federated objects. + - Cud operations on gslb, gslbservice, gslbgeodbprofile and other federated objects will be rejected. + - The rest-api disabling helps in upgrade scenarios where we don't want configuration sync operations to the gslb member when the member is being + - upgraded. + - This configuration programmatically blocks the leader from accepting new gslb configuration when member sites are undergoing upgrade. + - Field introduced in 17.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name for the gslb object. + required: true + send_interval: + description: + - Frequency with which group members communicate. + - Allowed values are 1-3600. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + send_interval_prior_to_maintenance_mode: + description: + - The user can specify a send-interval while entering maintenance mode. + - The validity of this 'maintenance send-interval' is only during maintenance mode. + - When the user leaves maintenance mode, the original send-interval is reinstated. + - This internal variable is used to store the original send-interval. + - Field introduced in 18.2.3. + sites: + description: + - Select avi site member belonging to this gslb. + tenant_ref: + description: + - It is a reference to an object of type tenant. + third_party_sites: + description: + - Third party site member belonging to this gslb. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the gslb object. + view_id: + description: + - The view-id is used in change-leader mode to differentiate partitioned groups while they have the same gslb namespace. + - Each partitioned group will be able to operate independently by using the view-id. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Gslb object + community.network.avi_gslb: + name: "test-gslb" + avi_credentials: + username: '{{ username }}' + password: '{{ password }}' + controller: '{{ controller }}' + sites: + - name: "test-site1" + username: "gslb_username" + password: "gslb_password" + ip_addresses: + - type: "V4" + addr: "10.10.28.83" + enabled: True + member_type: "GSLB_ACTIVE_MEMBER" + port: 443 + cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + - name: "test-site2" + username: "gslb_username" + password: "gslb_password" + ip_addresses: + - type: "V4" + addr: "10.10.28.86" + enabled: True + member_type: "GSLB_ACTIVE_MEMBER" + port: 443 + cluster_uuid: "cluster-0c37ae8d-ab62-410c-ad3e-06fa831950b1" + dns_configs: + - domain_name: "test1.com" + - domain_name: "test2.com" + leader_cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + +- name: Update Gslb site's configurations (Patch Add Operation) + community.network.avi_gslb: + avi_credentials: + username: '{{ username }}' + password: '{{ password }}' + controller: '{{ controller }}' + avi_api_update_method: patch + avi_api_patch_op: add + leader_cluster_uuid: "cluster-d4ee5fcc-3e0a-4d4f-9ae6-4182bc605829" + name: "test-gslb" + dns_configs: + - domain_name: "temp1.com" + - domain_name: "temp2.com" + gslb_sites_config: + - ip_addr: "10.10.28.83" + dns_vses: + - dns_vs_uuid: "virtualservice-f2a711cd-5e78-473f-8f47-d12de660fd62" + domain_names: + - "test1.com" + - "test2.com" + - ip_addr: "10.10.28.86" + dns_vses: + - dns_vs_uuid: "virtualservice-c1a63a16-f2a1-4f41-aab4-1e90f92a5e49" + domain_names: + - "temp1.com" + - "temp2.com" + +- name: Update Gslb site's configurations (Patch Replace Operation) + community.network.avi_gslb: + avi_credentials: + username: "{{ username }}" + password: "{{ password }}" + controller: "{{ controller }}" + # On basis of cluster leader uuid dns_configs is set for that particular leader cluster + leader_cluster_uuid: "cluster-84aa795f-8f09-42bb-97a4-5103f4a53da9" + name: "test-gslb" + avi_api_update_method: patch + avi_api_patch_op: replace + dns_configs: + - domain_name: "test3.com" + - domain_name: "temp3.com" + gslb_sites_config: + # Ip address is mapping key for dns_vses field update. For the given IP address, + # dns_vses is updated. + - ip_addr: "10.10.28.83" + dns_vses: + - dns_vs_uuid: "virtualservice-7c947ed4-77f3-4a52-909c-4f12afaf5bb0" + domain_names: + - "test3.com" + - ip_addr: "10.10.28.86" + dns_vses: + - dns_vs_uuid: "virtualservice-799b2c6d-7f2d-4c3f-94c6-6e813b20b674" + domain_names: + - "temp3.com" + +- name: Update Gslb site's configurations (Patch Delete Operation) + community.network.avi_gslb: + avi_credentials: + username: "{{ username }}" + password: "{{ password }}" + controller: "{{ controller }}" + # On basis of cluster leader uuid dns_configs is set for that particular leader cluster + leader_cluster_uuid: "cluster-84aa795f-8f09-42bb-97a4-5103f4a53da9" + name: "test-gslb" + avi_api_update_method: patch + avi_api_patch_op: delete + dns_configs: + gslb_sites_config: + - ip_addr: "10.10.28.83" + - ip_addr: "10.10.28.86" +""" + +RETURN = ''' +obj: + description: Gslb (api/gslb) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ApiSession, AviCredentials +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + async_interval=dict(type='int',), + clear_on_max_retries=dict(type='int',), + client_ip_addr_group=dict(type='dict',), + description=dict(type='str',), + dns_configs=dict(type='list',), + is_federated=dict(type='bool',), + leader_cluster_uuid=dict(type='str', required=True), + maintenance_mode=dict(type='bool',), + name=dict(type='str', required=True), + send_interval=dict(type='int',), + send_interval_prior_to_maintenance_mode=dict(type='int',), + sites=dict(type='list',), + tenant_ref=dict(type='str',), + third_party_sites=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + view_id=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_method = module.params['avi_api_update_method'] + if str(api_method).lower() == 'patch': + patch_op = module.params['avi_api_patch_op'] + # Create controller session + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + # Get existing gslb objects + rsp = api.get('gslb', api_version=api_creds.api_version) + existing_gslb = rsp.json() + gslb = existing_gslb['results'] + sites = module.params['gslb_sites_config'] + for gslb_obj in gslb: + # Update/Delete domain names in dns_configs fields in gslb object. + if 'dns_configs' in module.params: + if gslb_obj['leader_cluster_uuid'] == module.params['leader_cluster_uuid']: + if str(patch_op).lower() == 'delete': + gslb_obj['dns_configs'] = [] + elif str(patch_op).lower() == 'add': + if module.params['dns_configs'] not in gslb_obj['dns_configs']: + gslb_obj['dns_configs'].extend(module.params['dns_configs']) + else: + gslb_obj['dns_configs'] = module.params['dns_configs'] + # Update/Delete sites configuration + if sites: + for site_obj in gslb_obj['sites']: + dns_vses = site_obj.get('dns_vses', []) + for obj in sites: + config_for = obj.get('ip_addr', None) + if not config_for: + return module.fail_json(msg=( + "ip_addr of site in a configuration is mandatory. " + "Please provide ip_addr i.e. gslb site's ip.")) + if config_for == site_obj['ip_addresses'][0]['addr']: + if str(patch_op).lower() == 'delete': + site_obj['dns_vses'] = [] + else: + # Modify existing gslb sites object + for key, val in obj.items(): + if key == 'dns_vses' and str(patch_op).lower() == 'add': + found = False + # Check dns_vses field already exists on the controller + for v in dns_vses: + if val[0]['dns_vs_uuid'] != v['dns_vs_uuid']: + found = True + break + if not found: + dns_vses.extend(val) + else: + site_obj[key] = val + if str(patch_op).lower() == 'add': + site_obj['dns_vses'] = dns_vses + uni_dns_configs = [dict(tupleized) for tupleized in set(tuple(item.items()) + for item in gslb_obj['dns_configs'])] + gslb_obj['dns_configs'] = uni_dns_configs + module.params.update(gslb_obj) + module.params.update( + { + 'avi_api_update_method': 'put', + 'state': 'present' + } + ) + return avi_ansible_api(module, 'gslb', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbgeodbprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbgeodbprofile.py new file mode 100644 index 00000000..4a1ddf13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbgeodbprofile.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbgeodbprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of GslbGeoDbProfile Avi RESTful Object +description: + - This module is used to configure GslbGeoDbProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - Field introduced in 17.1.1. + entries: + description: + - List of geodb entries. + - An entry can either be a geodb file or an ip address group with geo properties. + - Field introduced in 17.1.1. + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - A user-friendly name for the geodb profile. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the geodb profile. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create GslbGeoDbProfile object + community.network.avi_gslbgeodbprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_gslbgeodbprofile +""" + +RETURN = ''' +obj: + description: GslbGeoDbProfile (api/gslbgeodbprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + entries=dict(type='list',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'gslbgeodbprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice.py new file mode 100644 index 00000000..4d63ea87 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbservice +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of GslbService Avi RESTful Object +description: + - This module is used to configure GslbService object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + application_persistence_profile_ref: + description: + - The federated application persistence associated with gslbservice site persistence functionality. + - It is a reference to an object of type applicationpersistenceprofile. + - Field introduced in 17.2.1. + controller_health_status_enabled: + description: + - Gs member's overall health status is derived based on a combination of controller and datapath health-status inputs. + - Note that the datapath status is determined by the association of health monitor profiles. + - Only the controller provided status is determined through this configuration. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + created_by: + description: + - Creator name. + - Field introduced in 17.1.2. + description: + description: + - User defined description for the object. + domain_names: + description: + - Fully qualified domain name of the gslb service. + down_response: + description: + - Response to the client query when the gslb service is down. + enabled: + description: + - Enable or disable the gslb service. + - If the gslb service is enabled, then the vips are sent in the dns responses based on reachability and configured algorithm. + - If the gslb service is disabled, then the vips are no longer available in the dns response. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + groups: + description: + - Select list of pools belonging to this gslb service. + health_monitor_refs: + description: + - Verify vs health by applying one or more health monitors. + - Active monitors generate synthetic traffic from dns service engine and to mark a vs up or down based on the response. + - It is a reference to an object of type healthmonitor. + health_monitor_scope: + description: + - Health monitor probe can be executed for all the members or it can be executed only for third-party members. + - This operational mode is useful to reduce the number of health monitor probes in case of a hybrid scenario. + - In such a case, avi members can have controller derived status while non-avi members can be probed by via health monitor probes in dataplane. + - Enum options - GSLB_SERVICE_HEALTH_MONITOR_ALL_MEMBERS, GSLB_SERVICE_HEALTH_MONITOR_ONLY_NON_AVI_MEMBERS. + - Default value when not specified in API or module is interpreted by Avi Controller as GSLB_SERVICE_HEALTH_MONITOR_ALL_MEMBERS. + hm_off: + description: + - This field is an internal field and is used in se. + - Field introduced in 18.2.2. + type: bool + is_federated: + description: + - This field indicates that this object is replicated across gslb federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + min_members: + description: + - The minimum number of members to distribute traffic to. + - Allowed values are 1-65535. + - Special values are 0 - 'disable'. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + name: + description: + - Name for the gslb service. + required: true + num_dns_ip: + description: + - Number of ip addresses of this gslb service to be returned by the dns service. + - Enter 0 to return all ip addresses. + - Allowed values are 1-20. + - Special values are 0- 'return all ip addresses'. + pool_algorithm: + description: + - The load balancing algorithm will pick a gslb pool within the gslb service list of available pools. + - Enum options - GSLB_SERVICE_ALGORITHM_PRIORITY, GSLB_SERVICE_ALGORITHM_GEO. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as GSLB_SERVICE_ALGORITHM_PRIORITY. + site_persistence_enabled: + description: + - Enable site-persistence for the gslbservice. + - Field introduced in 17.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + ttl: + description: + - Ttl value (in seconds) for records served for this gslb service by the dns service. + - Allowed values are 0-86400. + url: + description: + - Avi controller URL of the object. + use_edns_client_subnet: + description: + - Use the client ip subnet from the edns option as source ipaddress for client geo-location and consistent hash algorithm. + - Default is true. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + uuid: + description: + - Uuid of the gslb service. + wildcard_match: + description: + - Enable wild-card match of fqdn if an exact match is not found in the dns table, the longest match is chosen by wild-carding the fqdn in the dns + - request. + - Default is false. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create GslbService object + community.network.avi_gslbservice: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_gslbservice +""" + +RETURN = ''' +obj: + description: GslbService (api/gslbservice) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + application_persistence_profile_ref=dict(type='str',), + controller_health_status_enabled=dict(type='bool',), + created_by=dict(type='str',), + description=dict(type='str',), + domain_names=dict(type='list',), + down_response=dict(type='dict',), + enabled=dict(type='bool',), + groups=dict(type='list',), + health_monitor_refs=dict(type='list',), + health_monitor_scope=dict(type='str',), + hm_off=dict(type='bool',), + is_federated=dict(type='bool',), + min_members=dict(type='int',), + name=dict(type='str', required=True), + num_dns_ip=dict(type='int',), + pool_algorithm=dict(type='str',), + site_persistence_enabled=dict(type='bool',), + tenant_ref=dict(type='str',), + ttl=dict(type='int',), + url=dict(type='str',), + use_edns_client_subnet=dict(type='bool',), + uuid=dict(type='str',), + wildcard_match=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'gslbservice', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice_patch_member.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice_patch_member.py new file mode 100644 index 00000000..62467d54 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_gslbservice_patch_member.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: supported +# +# Copyright: (c) 2016 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_gslbservice_patch_member +author: Gaurav Rastogi (@grastogi23) + +short_description: Avi API Module +description: + - This module can be used for calling any resources defined in Avi REST API. U(https://avinetworks.com/) + - This module is useful for invoking HTTP Patch methods and accessing resources that do not have an REST object associated with them. +requirements: [ avisdk ] +options: + data: + description: + - HTTP body of GSLB Service Member in YAML or JSON format. + params: + description: + - Query parameters passed to the HTTP API. + name: + description: + - Name of the GSLB Service + required: true + state: + description: + - The state that should be applied to the member. Member is + - identified using field member.ip.addr. + default: present + choices: ["absent","present"] +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Patch GSLB Service to add a new member and group + community.network.avi_gslbservice_patch_member: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + name: gs-3 + api_version: 17.2.1 + data: + group: + name: newfoo + priority: 60 + members: + - enabled: true + ip: + addr: 10.30.10.66 + type: V4 + ratio: 3 + - name: Patch GSLB Service to delete an existing member + community.network.avi_gslbservice_patch_member: + controller: "{{ controller }}" + username: "{{ username }}" + password: "{{ password }}" + name: gs-3 + state: absent + api_version: 17.2.1 + data: + group: + name: newfoo + members: + - enabled: true + ip: + addr: 10.30.10.68 + type: V4 + ratio: 3 + - name: Update priority of GSLB Service Pool + community.network.avi_gslbservice_patch_member: + controller: "" + username: "" + password: "" + name: gs-3 + state: present + api_version: 17.2.1 + data: + group: + name: newfoo + priority: 42 +''' + + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_obj_cmp, cleanup_absent_fields, + ansible_return, AviCheckModeResponse, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def delete_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, api_version): + members = data.get('group', {}).get('members', []) + patched_member_ids = set([m['ip']['addr'] for m in members if 'fqdn' not in m]) + patched_member_fqdns = set([m['fqdn'] for m in members if 'fqdn' in m]) + + changed = False + rsp = None + + if existing_obj and (patched_member_ids or patched_member_fqdns): + groups = [group for group in existing_obj.get('groups', []) + if group['name'] == data['group']['name']] + if groups: + changed = any( + [(lambda g: g['ip']['addr'] in patched_member_ids)(m) + for m in groups[0].get('members', []) if 'fqdn' not in m]) + changed = changed or any( + [(lambda g: g['fqdn'] in patched_member_fqdns)(m) + for m in groups[0].get('members', []) if 'fqdn' in m]) + if check_mode or not changed: + return changed, rsp + # should not come here if not found + group = groups[0] + new_members = [] + for m in group.get('members', []): + if 'fqdn' in m: + if m['fqdn'] not in patched_member_fqdns: + new_members.append(m) + elif 'ip' in m: + if m['ip']['addr'] not in patched_member_ids: + new_members.append(m) + group['members'] = new_members + if not group['members']: + # Delete this group from the existing objects if it is empty. + # Controller also does not allow empty group. + existing_obj['groups'] = [ + grp for grp in existing_obj.get('groups', []) if + grp['name'] != data['group']['name']] + # remove the members that are part of the list + # update the object + # added api version for AVI api call. + rsp = api.put('gslbservice/%s' % existing_obj['uuid'], data=existing_obj, + tenant=tenant, tenant_uuid=tenant_uuid, api_version=api_version) + return changed, rsp + + +def add_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, name, api_version): + rsp = None + if not existing_obj: + # create the object + changed = True + if check_mode: + rsp = AviCheckModeResponse(obj=None) + else: + # creates group with single member + req = {'name': name, + 'groups': [data['group']] + } + # added api version for AVI api call. + rsp = api.post('gslbservice', data=req, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + else: + # found GSLB object + req = deepcopy(existing_obj) + if 'groups' not in req: + req['groups'] = [] + groups = [group for group in req['groups'] + if group['name'] == data['group']['name']] + if not groups: + # did not find the group + req['groups'].append(data['group']) + else: + # just update the existing group with members + group = groups[0] + group_info_wo_members = deepcopy(data['group']) + group_info_wo_members.pop('members', None) + group.update(group_info_wo_members) + if 'members' not in group: + group['members'] = [] + new_members = [] + for patch_member in data['group'].get('members', []): + found = False + for m in group['members']: + if 'fqdn' in patch_member and m.get('fqdn', '') == patch_member['fqdn']: + found = True + break + elif m['ip']['addr'] == patch_member['ip']['addr']: + found = True + break + if not found: + new_members.append(patch_member) + else: + m.update(patch_member) + # add any new members + group['members'].extend(new_members) + cleanup_absent_fields(req) + changed = not avi_obj_cmp(req, existing_obj) + if changed and not check_mode: + obj_path = '%s/%s' % ('gslbservice', existing_obj['uuid']) + # added api version for AVI api call. + rsp = api.put(obj_path, data=req, tenant=tenant, + tenant_uuid=tenant_uuid, api_version=api_version) + return changed, rsp + + +def main(): + argument_specs = dict( + params=dict(type='dict'), + data=dict(type='dict'), + name=dict(type='str', required=True), + state=dict(default='present', + choices=['absent', 'present']) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or ansible>=2.8 is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=api_creds.password, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + + tenant = api_creds.tenant + tenant_uuid = api_creds.tenant_uuid + params = module.params.get('params', None) + data = module.params.get('data', None) + gparams = deepcopy(params) if params else {} + gparams.update({'include_refs': '', 'include_name': ''}) + name = module.params.get('name', '') + state = module.params['state'] + # Get the api version from module. + api_version = api_creds.api_version + """ + state: present + 1. Check if the GSLB service is present + 2. If not then create the GSLB service with the member + 3. Check if the group exists + 4. if not then create the group with the member + 5. Check if the member is present + if not then add the member + state: absent + 1. check if GSLB service is present if not then exit + 2. check if group is present. if not then exit + 3. check if member is present. if present then remove it. + """ + obj_type = 'gslbservice' + # Added api version to call + existing_obj = api.get_object_by_name( + obj_type, name, tenant=tenant, tenant_uuid=tenant_uuid, + params={'include_refs': '', 'include_name': ''}, api_version=api_version) + check_mode = module.check_mode + if state == 'absent': + # Added api version to call + changed, rsp = delete_member(module, check_mode, api, tenant, + tenant_uuid, existing_obj, data, api_version) + else: + # Added api version to call + changed, rsp = add_member(module, check_mode, api, tenant, tenant_uuid, + existing_obj, data, name, api_version) + if check_mode or not changed: + return module.exit_json(changed=changed, obj=existing_obj) + return ansible_return(module, rsp, changed, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_hardwaresecuritymodulegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_hardwaresecuritymodulegroup.py new file mode 100644 index 00000000..23ab69cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_hardwaresecuritymodulegroup.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_hardwaresecuritymodulegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HardwareSecurityModuleGroup Avi RESTful Object +description: + - This module is used to configure HardwareSecurityModuleGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + hsm: + description: + - Hardware security module configuration. + required: true + name: + description: + - Name of the hsm group configuration object. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the hsm group configuration object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create HardwareSecurityModuleGroup object + community.network.avi_hardwaresecuritymodulegroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_hardwaresecuritymodulegroup +""" + +RETURN = ''' +obj: + description: HardwareSecurityModuleGroup (api/hardwaresecuritymodulegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + hsm=dict(type='dict', required=True), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'hardwaresecuritymodulegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_healthmonitor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_healthmonitor.py new file mode 100644 index 00000000..a58d7b2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_healthmonitor.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_healthmonitor +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HealthMonitor Avi RESTful Object +description: + - This module is used to configure HealthMonitor object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + dns_monitor: + description: + - Healthmonitordns settings for healthmonitor. + external_monitor: + description: + - Healthmonitorexternal settings for healthmonitor. + failed_checks: + description: + - Number of continuous failed health checks before the server is marked down. + - Allowed values are 1-50. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + http_monitor: + description: + - Healthmonitorhttp settings for healthmonitor. + https_monitor: + description: + - Healthmonitorhttp settings for healthmonitor. + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + monitor_port: + description: + - Use this port instead of the port defined for the server in the pool. + - If the monitor succeeds to this port, the load balanced traffic will still be sent to the port of the server defined within the pool. + - Allowed values are 1-65535. + - Special values are 0 - 'use server port'. + name: + description: + - A user friendly name for this health monitor. + required: true + radius_monitor: + description: + - Health monitor for radius. + - Field introduced in 18.2.3. + receive_timeout: + description: + - A valid response from the server is expected within the receive timeout window. + - This timeout must be less than the send interval. + - If server status is regularly flapping up and down, consider increasing this value. + - Allowed values are 1-2400. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + send_interval: + description: + - Frequency, in seconds, that monitors are sent to a server. + - Allowed values are 1-3600. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + sip_monitor: + description: + - Health monitor for sip. + - Field introduced in 17.2.8, 18.1.3, 18.2.1. + successful_checks: + description: + - Number of continuous successful health checks before server is marked up. + - Allowed values are 1-50. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + tcp_monitor: + description: + - Healthmonitortcp settings for healthmonitor. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of the health monitor. + - Enum options - HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP, HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS, HEALTH_MONITOR_EXTERNAL, HEALTH_MONITOR_UDP, + - HEALTH_MONITOR_DNS, HEALTH_MONITOR_GSLB, HEALTH_MONITOR_SIP, HEALTH_MONITOR_RADIUS. + required: true + udp_monitor: + description: + - Healthmonitorudp settings for healthmonitor. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the health monitor. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a HTTPS health monitor + community.network.avi_healthmonitor: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + https_monitor: + http_request: HEAD / HTTP/1.0 + http_response_code: + - HTTP_2XX + - HTTP_3XX + receive_timeout: 4 + failed_checks: 3 + send_interval: 10 + successful_checks: 3 + type: HEALTH_MONITOR_HTTPS + name: MyWebsite-HTTPS +""" + +RETURN = ''' +obj: + description: HealthMonitor (api/healthmonitor) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + dns_monitor=dict(type='dict',), + external_monitor=dict(type='dict',), + failed_checks=dict(type='int',), + http_monitor=dict(type='dict',), + https_monitor=dict(type='dict',), + is_federated=dict(type='bool',), + monitor_port=dict(type='int',), + name=dict(type='str', required=True), + radius_monitor=dict(type='dict',), + receive_timeout=dict(type='int',), + send_interval=dict(type='int',), + sip_monitor=dict(type='dict',), + successful_checks=dict(type='int',), + tcp_monitor=dict(type='dict',), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + udp_monitor=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'healthmonitor', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_httppolicyset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_httppolicyset.py new file mode 100644 index 00000000..ac04244c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_httppolicyset.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_httppolicyset +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of HTTPPolicySet Avi RESTful Object +description: + - This module is used to configure HTTPPolicySet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for pool. + - Internally set by cloud connector. + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + http_request_policy: + description: + - Http request policy for the virtual service. + http_response_policy: + description: + - Http response policy for the virtual service. + http_security_policy: + description: + - Http security policy for the virtual service. + is_internal_policy: + description: + - Boolean flag to set is_internal_policy. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name of the http policy set. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the http policy set. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a HTTP Policy set two switch between testpool1 and testpool2 + community.network.avi_httppolicyset: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + name: test-HTTP-Policy-Set + tenant_ref: admin + http_request_policy: + rules: + - index: 1 + enable: true + name: test-test1 + match: + path: + match_case: INSENSITIVE + match_str: + - /test1 + match_criteria: EQUALS + switching_action: + action: HTTP_SWITCHING_SELECT_POOL + status_code: HTTP_LOCAL_RESPONSE_STATUS_CODE_200 + pool_ref: "/api/pool?name=testpool1" + - index: 2 + enable: true + name: test-test2 + match: + path: + match_case: INSENSITIVE + match_str: + - /test2 + match_criteria: CONTAINS + switching_action: + action: HTTP_SWITCHING_SELECT_POOL + status_code: HTTP_LOCAL_RESPONSE_STATUS_CODE_200 + pool_ref: "/api/pool?name=testpool2" + is_internal_policy: false +""" + +RETURN = ''' +obj: + description: HTTPPolicySet (api/httppolicyset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + http_request_policy=dict(type='dict',), + http_response_policy=dict(type='dict',), + http_security_policy=dict(type='dict',), + is_internal_policy=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'httppolicyset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipaddrgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipaddrgroup.py new file mode 100644 index 00000000..52ae9405 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipaddrgroup.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_ipaddrgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of IpAddrGroup Avi RESTful Object +description: + - This module is used to configure IpAddrGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + addrs: + description: + - Configure ip address(es). + apic_epg_name: + description: + - Populate ip addresses from members of this cisco apic epg. + country_codes: + description: + - Populate the ip address ranges from the geo database for this country. + description: + description: + - User defined description for the object. + ip_ports: + description: + - Configure (ip address, port) tuple(s). + marathon_app_name: + description: + - Populate ip addresses from tasks of this marathon app. + marathon_service_port: + description: + - Task port associated with marathon service port. + - If marathon app has multiple service ports, this is required. + - Else, the first task port is used. + name: + description: + - Name of the ip address group. + required: true + prefixes: + description: + - Configure ip address prefix(es). + ranges: + description: + - Configure ip address range(s). + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the ip address group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create an IP Address Group configuration + community.network.avi_ipaddrgroup: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: Client-Source-Block + prefixes: + - ip_addr: + addr: 10.0.0.0 + type: V4 + mask: 8 + - ip_addr: + addr: 172.16.0.0 + type: V4 + mask: 12 + - ip_addr: + addr: 192.168.0.0 + type: V4 + mask: 16 +""" + +RETURN = ''' +obj: + description: IpAddrGroup (api/ipaddrgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + addrs=dict(type='list',), + apic_epg_name=dict(type='str',), + country_codes=dict(type='list',), + description=dict(type='str',), + ip_ports=dict(type='list',), + marathon_app_name=dict(type='str',), + marathon_service_port=dict(type='int',), + name=dict(type='str', required=True), + prefixes=dict(type='list',), + ranges=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'ipaddrgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipamdnsproviderprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipamdnsproviderprofile.py new file mode 100644 index 00000000..3e50bd8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_ipamdnsproviderprofile.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_ipamdnsproviderprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of IpamDnsProviderProfile Avi RESTful Object +description: + - This module is used to configure IpamDnsProviderProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + allocate_ip_in_vrf: + description: + - If this flag is set, only allocate ip from networks in the virtual service vrf. + - Applicable for avi vantage ipam only. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + aws_profile: + description: + - Provider details if type is aws. + azure_profile: + description: + - Provider details if type is microsoft azure. + - Field introduced in 17.2.1. + custom_profile: + description: + - Provider details if type is custom. + - Field introduced in 17.1.1. + gcp_profile: + description: + - Provider details if type is google cloud. + infoblox_profile: + description: + - Provider details if type is infoblox. + internal_profile: + description: + - Provider details if type is avi. + name: + description: + - Name for the ipam/dns provider profile. + required: true + oci_profile: + description: + - Provider details for oracle cloud. + - Field introduced in 18.2.1,18.1.3. + openstack_profile: + description: + - Provider details if type is openstack. + proxy_configuration: + description: + - Field introduced in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + tencent_profile: + description: + - Provider details for tencent cloud. + - Field introduced in 18.2.3. + type: + description: + - Provider type for the ipam/dns provider profile. + - Enum options - IPAMDNS_TYPE_INFOBLOX, IPAMDNS_TYPE_AWS, IPAMDNS_TYPE_OPENSTACK, IPAMDNS_TYPE_GCP, IPAMDNS_TYPE_INFOBLOX_DNS, IPAMDNS_TYPE_CUSTOM, + - IPAMDNS_TYPE_CUSTOM_DNS, IPAMDNS_TYPE_AZURE, IPAMDNS_TYPE_OCI, IPAMDNS_TYPE_TENCENT, IPAMDNS_TYPE_INTERNAL, IPAMDNS_TYPE_INTERNAL_DNS, + - IPAMDNS_TYPE_AWS_DNS, IPAMDNS_TYPE_AZURE_DNS. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the ipam/dns provider profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create IPAM DNS provider setting + community.network.avi_ipamdnsproviderprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + internal_profile: + dns_service_domain: + - domain_name: ashish.local + num_dns_ip: 1 + pass_through: true + record_ttl: 100 + - domain_name: guru.local + num_dns_ip: 1 + pass_through: true + record_ttl: 200 + ttl: 300 + name: Ashish-DNS + tenant_ref: Demo + type: IPAMDNS_TYPE_INTERNAL +""" + +RETURN = ''' +obj: + description: IpamDnsProviderProfile (api/ipamdnsproviderprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + allocate_ip_in_vrf=dict(type='bool',), + aws_profile=dict(type='dict',), + azure_profile=dict(type='dict',), + custom_profile=dict(type='dict',), + gcp_profile=dict(type='dict',), + infoblox_profile=dict(type='dict',), + internal_profile=dict(type='dict',), + name=dict(type='str', required=True), + oci_profile=dict(type='dict',), + openstack_profile=dict(type='dict',), + proxy_configuration=dict(type='dict',), + tenant_ref=dict(type='str',), + tencent_profile=dict(type='dict',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'ipamdnsproviderprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_l4policyset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_l4policyset.py new file mode 100644 index 00000000..d9ef7aa9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_l4policyset.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_l4policyset +author: Chaitanya Deshpande (@chaitanyaavi) + +short_description: Module for setup of L4PolicySet Avi RESTful Object +description: + - This module is used to configure L4PolicySet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.2.7. + description: + description: + - Field introduced in 17.2.7. + is_internal_policy: + description: + - Field introduced in 17.2.7. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + l4_connection_policy: + description: + - Policy to apply when a new transport connection is setup. + - Field introduced in 17.2.7. + name: + description: + - Name of the l4 policy set. + - Field introduced in 17.2.7. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.2.7. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Id of the l4 policy set. + - Field introduced in 17.2.7. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create L4PolicySet object + community.network.avi_l4policyset: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_l4policyset +""" + +RETURN = ''' +obj: + description: L4PolicySet (api/l4policyset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + is_internal_policy=dict(type='bool',), + l4_connection_policy=dict(type='dict',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'l4policyset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_microservicegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_microservicegroup.py new file mode 100644 index 00000000..d9bdf00a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_microservicegroup.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_microservicegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of MicroServiceGroup Avi RESTful Object +description: + - This module is used to configure MicroServiceGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + name: + description: + - Name of the microservice group. + required: true + service_refs: + description: + - Configure microservice(es). + - It is a reference to an object of type microservice. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the microservice group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a Microservice Group that can be used for setting up Network security policy + community.network.avi_microservicegroup: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + description: Group created by my Secure My App UI. + name: vs-msg-marketing + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: MicroServiceGroup (api/microservicegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + service_refs=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'microservicegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_network.py new file mode 100644 index 00000000..5704f264 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_network.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_network +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Network Avi RESTful Object +description: + - This module is used to configure Network object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + configured_subnets: + description: + - List of subnet. + dhcp_enabled: + description: + - Select the ip address management scheme for this network. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + exclude_discovered_subnets: + description: + - When selected, excludes all discovered subnets in this network from consideration for virtual service placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ip6_autocfg_enabled: + description: + - Enable ipv6 auto configuration. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + synced_from_se: + description: + - Boolean flag to set synced_from_se. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + vcenter_dvs: + description: + - Boolean flag to set vcenter_dvs. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + vimgrnw_ref: + description: + - It is a reference to an object of type vimgrnwruntime. + vrf_context_ref: + description: + - It is a reference to an object of type vrfcontext. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Network object + community.network.avi_network: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_network +""" + +RETURN = ''' +obj: + description: Network (api/network) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + configured_subnets=dict(type='list',), + dhcp_enabled=dict(type='bool',), + exclude_discovered_subnets=dict(type='bool',), + ip6_autocfg_enabled=dict(type='bool',), + name=dict(type='str', required=True), + synced_from_se=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + vcenter_dvs=dict(type='bool',), + vimgrnw_ref=dict(type='str',), + vrf_context_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'network', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networkprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networkprofile.py new file mode 100644 index 00000000..00ff2cd6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networkprofile.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_networkprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of NetworkProfile Avi RESTful Object +description: + - This module is used to configure NetworkProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + connection_mirror: + description: + - When enabled, avi mirrors all tcp fastpath connections to standby. + - Applicable only in legacy ha mode. + - Field introduced in 18.1.3,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + name: + description: + - The name of the network profile. + required: true + profile: + description: + - Networkprofileunion settings for networkprofile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the network profile. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a network profile for an UDP application + community.network.avi_networkprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: System-UDP-Fast-Path + profile: + type: PROTOCOL_TYPE_UDP_FAST_PATH + udp_fast_path_profile: + per_pkt_loadbalance: false + session_idle_timeout: 10 + snat: true + tenant_ref: admin +""" + +RETURN = ''' +obj: + description: NetworkProfile (api/networkprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + connection_mirror=dict(type='bool',), + description=dict(type='str',), + name=dict(type='str', required=True), + profile=dict(type='dict', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'networkprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networksecuritypolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networksecuritypolicy.py new file mode 100644 index 00000000..92343848 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_networksecuritypolicy.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_networksecuritypolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of NetworkSecurityPolicy Avi RESTful Object +description: + - This module is used to configure NetworkSecurityPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for network sec policy. + - Internally set by cloud connector. + created_by: + description: + - Creator name. + description: + description: + - User defined description for the object. + name: + description: + - Name of the object. + rules: + description: + - List of networksecurityrule. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a network security policy to block clients represented by ip group known_attackers + community.network.avi_networksecuritypolicy: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + name: vs-gurutest-ns + rules: + - action: NETWORK_SECURITY_POLICY_ACTION_TYPE_DENY + age: 0 + enable: true + index: 1 + log: false + match: + client_ip: + group_refs: + - Demo:known_attackers + match_criteria: IS_IN + name: Rule 1 + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: NetworkSecurityPolicy (api/networksecuritypolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + created_by=dict(type='str',), + description=dict(type='str',), + name=dict(type='str',), + rules=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'networksecuritypolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pkiprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pkiprofile.py new file mode 100644 index 00000000..7107f28a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pkiprofile.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_pkiprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PKIProfile Avi RESTful Object +description: + - This module is used to configure PKIProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + ca_certs: + description: + - List of certificate authorities (root and intermediate) trusted that is used for certificate validation. + created_by: + description: + - Creator name. + crl_check: + description: + - When enabled, avi will verify via crl checks that certificates in the trust chain have not been revoked. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + crls: + description: + - Certificate revocation lists. + ignore_peer_chain: + description: + - When enabled, avi will not trust intermediate and root certs presented by a client. + - Instead, only the chain certs configured in the certificate authority section will be used to verify trust of the client's cert. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + is_federated: + description: + - This field describes the object's replication scope. + - If the field is set to false, then the object is visible within the controller-cluster and its associated service-engines. + - If the field is set to true, then the object is replicated across the federation. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name of the pki profile. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + validate_only_leaf_crl: + description: + - When enabled, avi will only validate the revocation status of the leaf certificate using crl. + - To enable validation for the entire chain, disable this option and provide all the relevant crls. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PKIProfile object + community.network.avi_pkiprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_pkiprofile +""" + +RETURN = ''' +obj: + description: PKIProfile (api/pkiprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + ca_certs=dict(type='list',), + created_by=dict(type='str',), + crl_check=dict(type='bool',), + crls=dict(type='list',), + ignore_peer_chain=dict(type='bool',), + is_federated=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + validate_only_leaf_crl=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'pkiprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pool.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pool.py new file mode 100644 index 00000000..ffc21d13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_pool.py @@ -0,0 +1,497 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_pool +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Pool Avi RESTful Object +description: + - This module is used to configure Pool object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + a_pool: + description: + - Name of container cloud application that constitutes a pool in a a-b pool configuration, if different from vs app. + - Field deprecated in 18.1.2. + ab_pool: + description: + - A/b pool configuration. + - Field deprecated in 18.1.2. + ab_priority: + description: + - Priority of this pool in a a-b pool pair. + - Internally used. + - Field deprecated in 18.1.2. + analytics_policy: + description: + - Determines analytics settings for the pool. + - Field introduced in 18.1.5, 18.2.1. + analytics_profile_ref: + description: + - Specifies settings related to analytics. + - It is a reference to an object of type analyticsprofile. + - Field introduced in 18.1.4,18.2.1. + apic_epg_name: + description: + - Synchronize cisco apic epg members with pool servers. + application_persistence_profile_ref: + description: + - Persistence will ensure the same user sticks to the same server for a desired duration of time. + - It is a reference to an object of type applicationpersistenceprofile. + autoscale_launch_config_ref: + description: + - If configured then avi will trigger orchestration of pool server creation and deletion. + - It is only supported for container clouds like mesos, openshift, kubernetes, docker, etc. + - It is a reference to an object of type autoscalelaunchconfig. + autoscale_networks: + description: + - Network ids for the launch configuration. + autoscale_policy_ref: + description: + - Reference to server autoscale policy. + - It is a reference to an object of type serverautoscalepolicy. + capacity_estimation: + description: + - Inline estimation of capacity of servers. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + capacity_estimation_ttfb_thresh: + description: + - The maximum time-to-first-byte of a server. + - Allowed values are 1-5000. + - Special values are 0 - 'automatic'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + cloud_config_cksum: + description: + - Checksum of cloud configuration for pool. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + conn_pool_properties: + description: + - Connection pool properties. + - Field introduced in 18.2.1. + connection_ramp_duration: + description: + - Duration for which new connections will be gradually ramped up to a server recently brought online. + - Useful for lb algorithms that are least connection based. + - Allowed values are 1-300. + - Special values are 0 - 'immediate'. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + created_by: + description: + - Creator name. + default_server_port: + description: + - Traffic sent to servers will use this destination server port unless overridden by the server's specific port attribute. + - The ssl checkbox enables avi to server encryption. + - Allowed values are 1-65535. + - Default value when not specified in API or module is interpreted by Avi Controller as 80. + delete_server_on_dns_refresh: + description: + - Indicates whether existing ips are disabled(false) or deleted(true) on dns hostname refreshdetail -- on a dns refresh, some ips set on pool may + - no longer be returned by the resolver. + - These ips are deleted from the pool when this knob is set to true. + - They are disabled, if the knob is set to false. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + description: + description: + - A description of the pool. + domain_name: + description: + - Comma separated list of domain names which will be used to verify the common names or subject alternative names presented by server certificates. + - It is performed only when common name check host_check_enabled is enabled. + east_west: + description: + - Inherited config from virtualservice. + type: bool + enabled: + description: + - Enable or disable the pool. + - Disabling will terminate all open connections and pause health monitors. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + external_autoscale_groups: + description: + - Names of external auto-scale groups for pool servers. + - Currently available only for aws and azure. + - Field introduced in 17.1.2. + fail_action: + description: + - Enable an action - close connection, http redirect or local http response - when a pool failure happens. + - By default, a connection will be closed, in case the pool experiences a failure. + fewest_tasks_feedback_delay: + description: + - Periodicity of feedback for fewest tasks server selection algorithm. + - Allowed values are 1-300. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + graceful_disable_timeout: + description: + - Used to gracefully disable a server. + - Virtual service waits for the specified time before terminating the existing connections to the servers that are disabled. + - Allowed values are 1-7200. + - Special values are 0 - 'immediate', -1 - 'infinite'. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + gslb_sp_enabled: + description: + - Indicates if the pool is a site-persistence pool. + - Field introduced in 17.2.1. + type: bool + health_monitor_refs: + description: + - Verify server health by applying one or more health monitors. + - Active monitors generate synthetic traffic from each service engine and mark a server up or down based on the response. + - The passive monitor listens only to client to server communication. + - It raises or lowers the ratio of traffic destined to a server based on successful responses. + - It is a reference to an object of type healthmonitor. + host_check_enabled: + description: + - Enable common name check for server certificate. + - If enabled and no explicit domain name is specified, avi will use the incoming host header to do the match. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + inline_health_monitor: + description: + - The passive monitor will monitor client to server connections and requests and adjust traffic load to servers based on successful responses. + - This may alter the expected behavior of the lb method, such as round robin. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ipaddrgroup_ref: + description: + - Use list of servers from ip address group. + - It is a reference to an object of type ipaddrgroup. + lb_algorithm: + description: + - The load balancing algorithm will pick a server within the pool's list of available servers. + - Enum options - LB_ALGORITHM_LEAST_CONNECTIONS, LB_ALGORITHM_ROUND_ROBIN, LB_ALGORITHM_FASTEST_RESPONSE, LB_ALGORITHM_CONSISTENT_HASH, + - LB_ALGORITHM_LEAST_LOAD, LB_ALGORITHM_FEWEST_SERVERS, LB_ALGORITHM_RANDOM, LB_ALGORITHM_FEWEST_TASKS, LB_ALGORITHM_NEAREST_SERVER, + - LB_ALGORITHM_CORE_AFFINITY, LB_ALGORITHM_TOPOLOGY. + - Default value when not specified in API or module is interpreted by Avi Controller as LB_ALGORITHM_LEAST_CONNECTIONS. + lb_algorithm_consistent_hash_hdr: + description: + - Http header name to be used for the hash key. + lb_algorithm_core_nonaffinity: + description: + - Degree of non-affinity for core affinity based server selection. + - Allowed values are 1-65535. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + lb_algorithm_hash: + description: + - Criteria used as a key for determining the hash between the client and server. + - Enum options - LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS, LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS_AND_PORT, + - LB_ALGORITHM_CONSISTENT_HASH_URI, LB_ALGORITHM_CONSISTENT_HASH_CUSTOM_HEADER, LB_ALGORITHM_CONSISTENT_HASH_CUSTOM_STRING, + - LB_ALGORITHM_CONSISTENT_HASH_CALLID. + - Default value when not specified in API or module is interpreted by Avi Controller as LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS. + lookup_server_by_name: + description: + - Allow server lookup by name. + - Field introduced in 17.1.11,17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + max_concurrent_connections_per_server: + description: + - The maximum number of concurrent connections allowed to each server within the pool. + - Note applied value will be no less than the number of service engines that the pool is placed on. + - If set to 0, no limit is applied. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + max_conn_rate_per_server: + description: + - Rate limit connections to each server. + min_health_monitors_up: + description: + - Minimum number of health monitors in up state to mark server up. + - Field introduced in 18.2.1, 17.2.12. + min_servers_up: + description: + - Minimum number of servers in up state for marking the pool up. + - Field introduced in 18.2.1, 17.2.12. + name: + description: + - The name of the pool. + required: true + networks: + description: + - (internal-use) networks designated as containing servers for this pool. + - The servers may be further narrowed down by a filter. + - This field is used internally by avi, not editable by the user. + nsx_securitygroup: + description: + - A list of nsx service groups where the servers for the pool are created. + - Field introduced in 17.1.1. + pki_profile_ref: + description: + - Avi will validate the ssl certificate present by a server against the selected pki profile. + - It is a reference to an object of type pkiprofile. + placement_networks: + description: + - Manually select the networks and subnets used to provide reachability to the pool's servers. + - Specify the subnet using the following syntax 10-1-1-0/24. + - Use static routes in vrf configuration when pool servers are not directly connected butroutable from the service engine. + prst_hdr_name: + description: + - Header name for custom header persistence. + - Field deprecated in 18.1.2. + request_queue_depth: + description: + - Minimum number of requests to be queued when pool is full. + - Default value when not specified in API or module is interpreted by Avi Controller as 128. + request_queue_enabled: + description: + - Enable request queue when pool is full. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + rewrite_host_header_to_server_name: + description: + - Rewrite incoming host header to server name of the server to which the request is proxied. + - Enabling this feature rewrites host header for requests to all servers in the pool. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + rewrite_host_header_to_sni: + description: + - If sni server name is specified, rewrite incoming host header to the sni server name. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + server_auto_scale: + description: + - Server autoscale. + - Not used anymore. + - Field deprecated in 18.1.2. + type: bool + server_count: + description: + - Field deprecated in 18.2.1. + server_name: + description: + - Fully qualified dns hostname which will be used in the tls sni extension in server connections if sni is enabled. + - If no value is specified, avi will use the incoming host header instead. + server_reselect: + description: + - Server reselect configuration for http requests. + server_timeout: + description: + - Server timeout value specifies the time within which a server connection needs to be established and a request-response exchange completes + - between avi and the server. + - Value of 0 results in using default timeout of 60 minutes. + - Allowed values are 0-3600000. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + servers: + description: + - The pool directs load balanced traffic to this list of destination servers. + - The servers can be configured by ip address, name, network or via ip address group. + service_metadata: + description: + - Metadata pertaining to the service provided by this pool. + - In openshift/kubernetes environments, app metadata info is stored. + - Any user input to this field will be overwritten by avi vantage. + - Field introduced in 17.2.14,18.1.5,18.2.1. + sni_enabled: + description: + - Enable tls sni for server connections. + - If disabled, avi will not send the sni extension as part of the handshake. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ssl_key_and_certificate_ref: + description: + - Service engines will present a client ssl certificate to the server. + - It is a reference to an object of type sslkeyandcertificate. + ssl_profile_ref: + description: + - When enabled, avi re-encrypts traffic to the backend servers. + - The specific ssl profile defines which ciphers and ssl versions will be supported. + - It is a reference to an object of type sslprofile. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_service_port: + description: + - Do not translate the client's destination port when sending the connection to the server. + - The pool or servers specified service port will still be used for health monitoring. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Uuid of the pool. + vrf_ref: + description: + - Virtual routing context that the pool is bound to. + - This is used to provide the isolation of the set of networks the pool is attached to. + - The pool inherits the virtual routing context of the virtual service, and this field is used only internally, and is set by pb-transform. + - It is a reference to an object of type vrfcontext. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a Pool with two servers and HTTP monitor + community.network.avi_pool: + controller: 10.10.1.20 + username: avi_user + password: avi_password + name: testpool1 + description: testpool1 + state: present + health_monitor_refs: + - '/api/healthmonitor?name=System-HTTP' + servers: + - ip: + addr: 10.10.2.20 + type: V4 + - ip: + addr: 10.10.2.21 + type: V4 + +- name: Patch pool with a single server using patch op and avi_credentials + community.network.avi_pool: + avi_api_update_method: patch + avi_api_patch_op: delete + avi_credentials: "{{avi_credentials}}" + name: test-pool + servers: + - ip: + addr: 10.90.64.13 + type: 'V4' + register: pool + when: + - state | default("present") == "present" +""" + +RETURN = ''' +obj: + description: Pool (api/pool) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + a_pool=dict(type='str',), + ab_pool=dict(type='dict',), + ab_priority=dict(type='int',), + analytics_policy=dict(type='dict',), + analytics_profile_ref=dict(type='str',), + apic_epg_name=dict(type='str',), + application_persistence_profile_ref=dict(type='str',), + autoscale_launch_config_ref=dict(type='str',), + autoscale_networks=dict(type='list',), + autoscale_policy_ref=dict(type='str',), + capacity_estimation=dict(type='bool',), + capacity_estimation_ttfb_thresh=dict(type='int',), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + conn_pool_properties=dict(type='dict',), + connection_ramp_duration=dict(type='int',), + created_by=dict(type='str',), + default_server_port=dict(type='int',), + delete_server_on_dns_refresh=dict(type='bool',), + description=dict(type='str',), + domain_name=dict(type='list',), + east_west=dict(type='bool',), + enabled=dict(type='bool',), + external_autoscale_groups=dict(type='list',), + fail_action=dict(type='dict',), + fewest_tasks_feedback_delay=dict(type='int',), + graceful_disable_timeout=dict(type='int',), + gslb_sp_enabled=dict(type='bool',), + health_monitor_refs=dict(type='list',), + host_check_enabled=dict(type='bool',), + inline_health_monitor=dict(type='bool',), + ipaddrgroup_ref=dict(type='str',), + lb_algorithm=dict(type='str',), + lb_algorithm_consistent_hash_hdr=dict(type='str',), + lb_algorithm_core_nonaffinity=dict(type='int',), + lb_algorithm_hash=dict(type='str',), + lookup_server_by_name=dict(type='bool',), + max_concurrent_connections_per_server=dict(type='int',), + max_conn_rate_per_server=dict(type='dict',), + min_health_monitors_up=dict(type='int',), + min_servers_up=dict(type='int',), + name=dict(type='str', required=True), + networks=dict(type='list',), + nsx_securitygroup=dict(type='list',), + pki_profile_ref=dict(type='str',), + placement_networks=dict(type='list',), + prst_hdr_name=dict(type='str',), + request_queue_depth=dict(type='int',), + request_queue_enabled=dict(type='bool',), + rewrite_host_header_to_server_name=dict(type='bool',), + rewrite_host_header_to_sni=dict(type='bool',), + server_auto_scale=dict(type='bool',), + server_count=dict(type='int',), + server_name=dict(type='str',), + server_reselect=dict(type='dict',), + server_timeout=dict(type='int',), + servers=dict(type='list',), + service_metadata=dict(type='str',), + sni_enabled=dict(type='bool',), + ssl_key_and_certificate_ref=dict(type='str',), + ssl_profile_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_service_port=dict(type='bool',), + uuid=dict(type='str',), + vrf_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'pool', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroup.py new file mode 100644 index 00000000..c66269dd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroup.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_poolgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PoolGroup Avi RESTful Object +description: + - This module is used to configure PoolGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_config_cksum: + description: + - Checksum of cloud configuration for poolgroup. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + created_by: + description: + - Name of the user who created the object. + deployment_policy_ref: + description: + - When setup autoscale manager will automatically promote new pools into production when deployment goals are met. + - It is a reference to an object of type poolgroupdeploymentpolicy. + description: + description: + - Description of pool group. + fail_action: + description: + - Enable an action - close connection, http redirect, or local http response - when a pool group failure happens. + - By default, a connection will be closed, in case the pool group experiences a failure. + implicit_priority_labels: + description: + - Whether an implicit set of priority labels is generated. + - Field introduced in 17.1.9,17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + members: + description: + - List of pool group members object of type poolgroupmember. + min_servers: + description: + - The minimum number of servers to distribute traffic to. + - Allowed values are 1-65535. + - Special values are 0 - 'disable'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + name: + description: + - The name of the pool group. + required: true + priority_labels_ref: + description: + - Uuid of the priority labels. + - If not provided, pool group member priority label will be interpreted as a number with a larger number considered higher priority. + - It is a reference to an object of type prioritylabels. + service_metadata: + description: + - Metadata pertaining to the service provided by this poolgroup. + - In openshift/kubernetes environments, app metadata info is stored. + - Any user input to this field will be overwritten by avi vantage. + - Field introduced in 17.2.14,18.1.5,18.2.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the pool group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PoolGroup object + community.network.avi_poolgroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_poolgroup +""" + +RETURN = ''' +obj: + description: PoolGroup (api/poolgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + created_by=dict(type='str',), + deployment_policy_ref=dict(type='str',), + description=dict(type='str',), + fail_action=dict(type='dict',), + implicit_priority_labels=dict(type='bool',), + members=dict(type='list',), + min_servers=dict(type='int',), + name=dict(type='str', required=True), + priority_labels_ref=dict(type='str',), + service_metadata=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'poolgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroupdeploymentpolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroupdeploymentpolicy.py new file mode 100644 index 00000000..952de35a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_poolgroupdeploymentpolicy.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_poolgroupdeploymentpolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PoolGroupDeploymentPolicy Avi RESTful Object +description: + - This module is used to configure PoolGroupDeploymentPolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + auto_disable_old_prod_pools: + description: + - It will automatically disable old production pools once there is a new production candidate. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + description: + description: + - User defined description for the object. + evaluation_duration: + description: + - Duration of evaluation period for automatic deployment. + - Allowed values are 60-86400. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + name: + description: + - The name of the pool group deployment policy. + required: true + rules: + description: + - List of pgdeploymentrule. + scheme: + description: + - Deployment scheme. + - Enum options - BLUE_GREEN, CANARY. + - Default value when not specified in API or module is interpreted by Avi Controller as BLUE_GREEN. + target_test_traffic_ratio: + description: + - Target traffic ratio before pool is made production. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + tenant_ref: + description: + - It is a reference to an object of type tenant. + test_traffic_ratio_rampup: + description: + - Ratio of the traffic that is sent to the pool under test. + - Test ratio of 100 means blue green. + - Allowed values are 1-100. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the pool group deployment policy. + webhook_ref: + description: + - Webhook configured with url that avi controller will pass back information about pool group, old and new pool information and current deployment + - rule results. + - It is a reference to an object of type webhook. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PoolGroupDeploymentPolicy object + community.network.avi_poolgroupdeploymentpolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_poolgroupdeploymentpolicy +""" + +RETURN = ''' +obj: + description: PoolGroupDeploymentPolicy (api/poolgroupdeploymentpolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + auto_disable_old_prod_pools=dict(type='bool',), + description=dict(type='str',), + evaluation_duration=dict(type='int',), + name=dict(type='str', required=True), + rules=dict(type='list',), + scheme=dict(type='str',), + target_test_traffic_ratio=dict(type='int',), + tenant_ref=dict(type='str',), + test_traffic_ratio_rampup=dict(type='int',), + url=dict(type='str',), + uuid=dict(type='str',), + webhook_ref=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'poolgroupdeploymentpolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_prioritylabels.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_prioritylabels.py new file mode 100644 index 00000000..fc8c38ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_prioritylabels.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_prioritylabels +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of PriorityLabels Avi RESTful Object +description: + - This module is used to configure PriorityLabels object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + description: + description: + - A description of the priority labels. + equivalent_labels: + description: + - Equivalent priority labels in descending order. + name: + description: + - The name of the priority labels. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the priority labels. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create PriorityLabels object + community.network.avi_prioritylabels: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_prioritylabels +""" + +RETURN = ''' +obj: + description: PriorityLabels (api/prioritylabels) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + description=dict(type='str',), + equivalent_labels=dict(type='list',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'prioritylabels', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_role.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_role.py new file mode 100644 index 00000000..fc71de19 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_role.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_role +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Role Avi RESTful Object +description: + - This module is used to configure Role object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - Name of the object. + required: true + privileges: + description: + - List of permission. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Role object + community.network.avi_role: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_role +""" + +RETURN = ''' +obj: + description: Role (api/role) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + privileges=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'role', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_scheduler.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_scheduler.py new file mode 100644 index 00000000..ef3cabb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_scheduler.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_scheduler +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Scheduler Avi RESTful Object +description: + - This module is used to configure Scheduler object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + backup_config_ref: + description: + - Backup configuration to be executed by this scheduler. + - It is a reference to an object of type backupconfiguration. + enabled: + description: + - Boolean flag to set enabled. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + end_date_time: + description: + - Scheduler end date and time. + frequency: + description: + - Frequency at which custom scheduler will run. + - Allowed values are 0-60. + frequency_unit: + description: + - Unit at which custom scheduler will run. + - Enum options - SCHEDULER_FREQUENCY_UNIT_MIN, SCHEDULER_FREQUENCY_UNIT_HOUR, SCHEDULER_FREQUENCY_UNIT_DAY, SCHEDULER_FREQUENCY_UNIT_WEEK, + - SCHEDULER_FREQUENCY_UNIT_MONTH. + name: + description: + - Name of scheduler. + required: true + run_mode: + description: + - Scheduler run mode. + - Enum options - RUN_MODE_PERIODIC, RUN_MODE_AT, RUN_MODE_NOW. + run_script_ref: + description: + - Control script to be executed by this scheduler. + - It is a reference to an object of type alertscriptconfig. + scheduler_action: + description: + - Define scheduler action. + - Enum options - SCHEDULER_ACTION_RUN_A_SCRIPT, SCHEDULER_ACTION_BACKUP. + - Default value when not specified in API or module is interpreted by Avi Controller as SCHEDULER_ACTION_BACKUP. + start_date_time: + description: + - Scheduler start date and time. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Scheduler object + community.network.avi_scheduler: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_scheduler +""" + +RETURN = ''' +obj: + description: Scheduler (api/scheduler) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + backup_config_ref=dict(type='str',), + enabled=dict(type='bool',), + end_date_time=dict(type='str',), + frequency=dict(type='int',), + frequency_unit=dict(type='str',), + name=dict(type='str', required=True), + run_mode=dict(type='str',), + run_script_ref=dict(type='str',), + scheduler_action=dict(type='str',), + start_date_time=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'scheduler', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_seproperties.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_seproperties.py new file mode 100644 index 00000000..3150cafb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_seproperties.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_seproperties +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SeProperties Avi RESTful Object +description: + - This module is used to configure SeProperties object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + se_agent_properties: + description: + - Seagentproperties settings for seproperties. + se_bootup_properties: + description: + - Sebootupproperties settings for seproperties. + se_runtime_properties: + description: + - Seruntimeproperties settings for seproperties. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + - Default value when not specified in API or module is interpreted by Avi Controller as default. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SeProperties object + community.network.avi_seproperties: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_seproperties +""" + +RETURN = ''' +obj: + description: SeProperties (api/seproperties) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + se_agent_properties=dict(type='dict',), + se_bootup_properties=dict(type='dict',), + se_runtime_properties=dict(type='dict',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'seproperties', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serverautoscalepolicy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serverautoscalepolicy.py new file mode 100644 index 00000000..0f97a1bb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serverautoscalepolicy.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serverautoscalepolicy +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServerAutoScalePolicy Avi RESTful Object +description: + - This module is used to configure ServerAutoScalePolicy object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + intelligent_autoscale: + description: + - Use avi intelligent autoscale algorithm where autoscale is performed by comparing load on the pool against estimated capacity of all the servers. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + intelligent_scalein_margin: + description: + - Maximum extra capacity as percentage of load used by the intelligent scheme. + - Scalein is triggered when available capacity is more than this margin. + - Allowed values are 1-99. + - Default value when not specified in API or module is interpreted by Avi Controller as 40. + intelligent_scaleout_margin: + description: + - Minimum extra capacity as percentage of load used by the intelligent scheme. + - Scaleout is triggered when available capacity is less than this margin. + - Allowed values are 1-99. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + max_scalein_adjustment_step: + description: + - Maximum number of servers to scalein simultaneously. + - The actual number of servers to scalein is chosen such that target number of servers is always more than or equal to the min_size. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_scaleout_adjustment_step: + description: + - Maximum number of servers to scaleout simultaneously. + - The actual number of servers to scaleout is chosen such that target number of servers is always less than or equal to the max_size. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + max_size: + description: + - Maximum number of servers after scaleout. + - Allowed values are 0-400. + min_size: + description: + - No scale-in happens once number of operationally up servers reach min_servers. + - Allowed values are 0-400. + name: + description: + - Name of the object. + required: true + scalein_alertconfig_refs: + description: + - Trigger scalein when alerts due to any of these alert configurations are raised. + - It is a reference to an object of type alertconfig. + scalein_cooldown: + description: + - Cooldown period during which no new scalein is triggered to allow previous scalein to successfully complete. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + scaleout_alertconfig_refs: + description: + - Trigger scaleout when alerts due to any of these alert configurations are raised. + - It is a reference to an object of type alertconfig. + scaleout_cooldown: + description: + - Cooldown period during which no new scaleout is triggered to allow previous scaleout to successfully complete. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + use_predicted_load: + description: + - Use predicted load rather than current load. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServerAutoScalePolicy object + community.network.avi_serverautoscalepolicy: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serverautoscalepolicy +""" + +RETURN = ''' +obj: + description: ServerAutoScalePolicy (api/serverautoscalepolicy) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + intelligent_autoscale=dict(type='bool',), + intelligent_scalein_margin=dict(type='int',), + intelligent_scaleout_margin=dict(type='int',), + max_scalein_adjustment_step=dict(type='int',), + max_scaleout_adjustment_step=dict(type='int',), + max_size=dict(type='int',), + min_size=dict(type='int',), + name=dict(type='str', required=True), + scalein_alertconfig_refs=dict(type='list',), + scalein_cooldown=dict(type='int',), + scaleout_alertconfig_refs=dict(type='list',), + scaleout_cooldown=dict(type='int',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_predicted_load=dict(type='bool',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serverautoscalepolicy', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceengine.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceengine.py new file mode 100644 index 00000000..c1e54725 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceengine.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serviceengine +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServiceEngine Avi RESTful Object +description: + - This module is used to configure ServiceEngine object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + availability_zone: + description: + - Availability_zone of serviceengine. + cloud_ref: + description: + - It is a reference to an object of type cloud. + container_mode: + description: + - Boolean flag to set container_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + container_type: + description: + - Enum options - container_type_bridge, container_type_host, container_type_host_dpdk. + - Default value when not specified in API or module is interpreted by Avi Controller as CONTAINER_TYPE_HOST. + controller_created: + description: + - Boolean flag to set controller_created. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + controller_ip: + description: + - Controller_ip of serviceengine. + data_vnics: + description: + - List of vnic. + enable_state: + description: + - Inorder to disable se set this field appropriately. + - Enum options - SE_STATE_ENABLED, SE_STATE_DISABLED_FOR_PLACEMENT, SE_STATE_DISABLED, SE_STATE_DISABLED_FORCE. + - Default value when not specified in API or module is interpreted by Avi Controller as SE_STATE_ENABLED. + flavor: + description: + - Flavor of serviceengine. + host_ref: + description: + - It is a reference to an object of type vimgrhostruntime. + hypervisor: + description: + - Enum options - default, vmware_esx, kvm, vmware_vsan, xen. + mgmt_vnic: + description: + - Vnic settings for serviceengine. + name: + description: + - Name of the object. + - Default value when not specified in API or module is interpreted by Avi Controller as VM name unknown. + resources: + description: + - Seresources settings for serviceengine. + se_group_ref: + description: + - It is a reference to an object of type serviceenginegroup. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServiceEngine object + community.network.avi_serviceengine: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serviceengine +""" + +RETURN = ''' +obj: + description: ServiceEngine (api/serviceengine) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + availability_zone=dict(type='str',), + cloud_ref=dict(type='str',), + container_mode=dict(type='bool',), + container_type=dict(type='str',), + controller_created=dict(type='bool',), + controller_ip=dict(type='str',), + data_vnics=dict(type='list',), + enable_state=dict(type='str',), + flavor=dict(type='str',), + host_ref=dict(type='str',), + hypervisor=dict(type='str',), + mgmt_vnic=dict(type='dict',), + name=dict(type='str',), + resources=dict(type='dict',), + se_group_ref=dict(type='str',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serviceengine', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceenginegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceenginegroup.py new file mode 100644 index 00000000..c7c83be2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_serviceenginegroup.py @@ -0,0 +1,1075 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_serviceenginegroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of ServiceEngineGroup Avi RESTful Object +description: + - This module is used to configure ServiceEngineGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + accelerated_networking: + description: + - Enable accelerated networking option for azure se. + - Accelerated networking enables single root i/o virtualization (sr-iov) to a se vm. + - This improves networking performance. + - Field introduced in 17.2.14,18.1.5,18.2.1. + type: bool + active_standby: + description: + - Service engines in active/standby mode for ha failover. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + additional_config_memory: + description: + - Indicates the percent of config memory used for config updates. + - Allowed values are 0-90. + - Field deprecated in 18.1.2. + - Field introduced in 18.1.1. + advertise_backend_networks: + description: + - Advertise reach-ability of backend server networks via adc through bgp for default gateway feature. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + aggressive_failure_detection: + description: + - Enable aggressive failover configuration for ha. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + algo: + description: + - In compact placement, virtual services are placed on existing ses until max_vs_per_se limit is reached. + - Enum options - PLACEMENT_ALGO_PACKED, PLACEMENT_ALGO_DISTRIBUTED. + - Default value when not specified in API or module is interpreted by Avi Controller as PLACEMENT_ALGO_PACKED. + allow_burst: + description: + - Allow ses to be created using burst license. + - Field introduced in 17.2.5. + type: bool + app_cache_percent: + description: + - A percent value of total se memory reserved for application caching. + - This is an se bootup property and requires se restart. + - Allowed values are 0 - 100. + - Special values are 0- 'disable'. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + app_learning_memory_percent: + description: + - A percent value of total se memory reserved for application learning. + - This is an se bootup property and requires se restart. + - Allowed values are 0 - 10. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + archive_shm_limit: + description: + - Amount of se memory in gb until which shared memory is collected in core archive. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + async_ssl: + description: + - Ssl handshakes will be handled by dedicated ssl threads. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + async_ssl_threads: + description: + - Number of async ssl threads per se_dp. + - Allowed values are 1-16. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + auto_rebalance: + description: + - If set, virtual services will be automatically migrated when load on an se is less than minimum or more than maximum thresholds. + - Only alerts are generated when the auto_rebalance is not set. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + auto_rebalance_capacity_per_se: + description: + - Capacities of se for auto rebalance for each criteria. + - Field introduced in 17.2.4. + auto_rebalance_criteria: + description: + - Set of criteria for se auto rebalance. + - Enum options - SE_AUTO_REBALANCE_CPU, SE_AUTO_REBALANCE_PPS, SE_AUTO_REBALANCE_MBPS, SE_AUTO_REBALANCE_OPEN_CONNS, SE_AUTO_REBALANCE_CPS. + - Field introduced in 17.2.3. + auto_rebalance_interval: + description: + - Frequency of rebalance, if 'auto rebalance' is enabled. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + auto_redistribute_active_standby_load: + description: + - Redistribution of virtual services from the takeover se to the replacement se can cause momentary traffic loss. + - If the auto-redistribute load option is left in its default off state, any desired rebalancing requires calls to rest api. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + bgp_state_update_interval: + description: + - Bgp peer state update interval. + - Allowed values are 5-100. + - Field introduced in 17.2.14,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + buffer_se: + description: + - Excess service engine capacity provisioned for ha failover. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + cloud_ref: + description: + - It is a reference to an object of type cloud. + config_debugs_on_all_cores: + description: + - Enable config debugs on all cores of se. + - Field introduced in 17.2.13,18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + connection_memory_percentage: + description: + - Percentage of memory for connection state. + - This will come at the expense of memory used for http in-memory cache. + - Allowed values are 10-90. + - Default value when not specified in API or module is interpreted by Avi Controller as 50. + cpu_reserve: + description: + - Boolean flag to set cpu_reserve. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + cpu_socket_affinity: + description: + - Allocate all the cpu cores for the service engine virtual machines on the same cpu socket. + - Applicable only for vcenter cloud. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + custom_securitygroups_data: + description: + - Custom security groups to be associated with data vnics for se instances in openstack and aws clouds. + - Field introduced in 17.1.3. + custom_securitygroups_mgmt: + description: + - Custom security groups to be associated with management vnic for se instances in openstack and aws clouds. + - Field introduced in 17.1.3. + custom_tag: + description: + - Custom tag will be used to create the tags for se instance in aws. + - Note this is not the same as the prefix for se name. + data_network_id: + description: + - Subnet used to spin up the data nic for service engines, used only for azure cloud. + - Overrides the cloud level setting for service engine subnet. + - Field introduced in 18.2.3. + datascript_timeout: + description: + - Number of instructions before datascript times out. + - Allowed values are 0-100000000. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1000000. + dedicated_dispatcher_core: + description: + - Dedicate the core that handles packet receive/transmit from the network to just the dispatching function. + - Don't use it for tcp/ip and ssl functions. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + disable_avi_securitygroups: + description: + - By default, avi creates and manages security groups along with custom sg provided by user. + - Set this to true to disallow avi to create and manage new security groups. + - Avi will only make use of custom security groups provided by user. + - This option is only supported for aws cloud type. + - Field introduced in 17.2.13,18.1.4,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_csum_offloads: + description: + - Stop using tcp/udp and ip checksum offload features of nics. + - Field introduced in 17.1.14, 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_gro: + description: + - Disable generic receive offload (gro) in dpdk poll-mode driver packet receive path. + - Gro is on by default on nics that do not support lro (large receive offload) or do not gain performance boost from lro. + - Field introduced in 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + disable_se_memory_check: + description: + - If set, disable the config memory check done in service engine. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + disable_tso: + description: + - Disable tcp segmentation offload (tso) in dpdk poll-mode driver packet transmit path. + - Tso is on by default on nics that support it. + - Field introduced in 17.2.5, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + disk_per_se: + description: + - Amount of disk space for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + distribute_load_active_standby: + description: + - Use both the active and standby service engines for virtual service placement in the legacy active standby ha mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + distribute_queues: + description: + - Distributes queue ownership among cores so multiple cores handle dispatcher duties. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_hsm_priming: + description: + - (this is a beta feature). + - Enable hsm key priming. + - If enabled, key handles on the hsm will be synced to se before processing client connections. + - Field introduced in 17.2.7, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_multi_lb: + description: + - Applicable only for azure cloud with basic sku lb. + - If set, additional azure lbs will be automatically created if resources in existing lb are exhausted. + - Field introduced in 17.2.10, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_routing: + description: + - Enable routing for this serviceenginegroup . + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_vip_on_all_interfaces: + description: + - Enable vip on all interfaces of se. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_vmac: + description: + - Use virtual mac address for interfaces on which floating interface ips are placed. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ephemeral_portrange_end: + description: + - End local ephemeral port number for outbound connections. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + ephemeral_portrange_start: + description: + - Start local ephemeral port number for outbound connections. + - Field introduced in 17.2.13, 18.1.5, 18.2.1. + extra_config_multiplier: + description: + - Multiplier for extra config to support large vs/pool config. + - Default value when not specified in API or module is interpreted by Avi Controller as 0.0. + extra_shared_config_memory: + description: + - Extra config memory to support large geo db configuration. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + floating_intf_ip: + description: + - If serviceenginegroup is configured for legacy 1+1 active standby ha mode, floating ip's will be advertised only by the active se in the pair. + - Virtual services in this group must be disabled/enabled for any changes to the floating ip's to take effect. + - Only active se hosting vs tagged with active standby se 1 tag will advertise this floating ip when manual load distribution is enabled. + floating_intf_ip_se_2: + description: + - If serviceenginegroup is configured for legacy 1+1 active standby ha mode, floating ip's will be advertised only by the active se in the pair. + - Virtual services in this group must be disabled/enabled for any changes to the floating ip's to take effect. + - Only active se hosting vs tagged with active standby se 2 tag will advertise this floating ip when manual load distribution is enabled. + flow_table_new_syn_max_entries: + description: + - Maximum number of flow table entries that have not completed tcp three-way handshake yet. + - Field introduced in 17.2.5. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + free_list_size: + description: + - Number of entries in the free list. + - Field introduced in 17.2.10, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1024. + ha_mode: + description: + - High availability mode for all the virtual services using this service engine group. + - Enum options - HA_MODE_SHARED_PAIR, HA_MODE_SHARED, HA_MODE_LEGACY_ACTIVE_STANDBY. + - Default value when not specified in API or module is interpreted by Avi Controller as HA_MODE_SHARED. + hardwaresecuritymodulegroup_ref: + description: + - It is a reference to an object of type hardwaresecuritymodulegroup. + heap_minimum_config_memory: + description: + - Minimum required heap memory to apply any configuration. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + hm_on_standby: + description: + - Enable active health monitoring from the standby se for all placed virtual services. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + host_attribute_key: + description: + - Key of a (key, value) pair identifying a label for a set of nodes usually in container clouds. + - Needs to be specified together with host_attribute_value. + - Ses can be configured differently including ha modes across different se groups. + - May also be used for isolation between different classes of virtualservices. + - Virtualservices' se group may be specified via annotations/labels. + - A openshift/kubernetes namespace maybe annotated with a matching se group label as openshift.io/node-selector apptype=prod. + - When multiple se groups are used in a cloud with host attributes specified,just a single se group can exist as a match-all se group without a + - host_attribute_key. + host_attribute_value: + description: + - Value of a (key, value) pair identifying a label for a set of nodes usually in container clouds. + - Needs to be specified together with host_attribute_key. + host_gateway_monitor: + description: + - Enable the host gateway monitor when service engine is deployed as docker container. + - Disabled by default. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + hypervisor: + description: + - Override default hypervisor. + - Enum options - DEFAULT, VMWARE_ESX, KVM, VMWARE_VSAN, XEN. + ignore_rtt_threshold: + description: + - Ignore rtt samples if it is above threshold. + - Field introduced in 17.1.6,17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 5000. + ingress_access_data: + description: + - Program se security group ingress rules to allow vip data access from remote cidr type. + - Enum options - SG_INGRESS_ACCESS_NONE, SG_INGRESS_ACCESS_ALL, SG_INGRESS_ACCESS_VPC. + - Field introduced in 17.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_INGRESS_ACCESS_ALL. + ingress_access_mgmt: + description: + - Program se security group ingress rules to allow ssh/icmp management access from remote cidr type. + - Enum options - SG_INGRESS_ACCESS_NONE, SG_INGRESS_ACCESS_ALL, SG_INGRESS_ACCESS_VPC. + - Field introduced in 17.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_INGRESS_ACCESS_ALL. + instance_flavor: + description: + - Instance/flavor name for se instance. + iptables: + description: + - Iptables rules. + least_load_core_selection: + description: + - Select core with least load for new flow. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + license_tier: + description: + - Specifies the license tier which would be used. + - This field by default inherits the value from cloud. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + license_type: + description: + - If no license type is specified then default license enforcement for the cloud type is chosen. + - Enum options - LIC_BACKEND_SERVERS, LIC_SOCKETS, LIC_CORES, LIC_HOSTS, LIC_SE_BANDWIDTH, LIC_METERED_SE_BANDWIDTH. + - Field introduced in 17.2.5. + log_disksz: + description: + - Maximum disk capacity (in mb) to be allocated to an se. + - This is exclusively used for debug and log data. + - Default value when not specified in API or module is interpreted by Avi Controller as 10000. + max_cpu_usage: + description: + - When cpu usage on an se exceeds this threshold, virtual services hosted on this se may be rebalanced to other ses to reduce load. + - A new se may be created as part of this process. + - Allowed values are 40-90. + - Default value when not specified in API or module is interpreted by Avi Controller as 80. + max_memory_per_mempool: + description: + - Max bytes that can be allocated in a single mempool. + - Field introduced in 18.1.5. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. + max_public_ips_per_lb: + description: + - Applicable to azure platform only. + - Maximum number of public ips per azure lb. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + max_rules_per_lb: + description: + - Applicable to azure platform only. + - Maximum number of rules per azure lb. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 150. + max_scaleout_per_vs: + description: + - Maximum number of active service engines for the virtual service. + - Allowed values are 1-64. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + max_se: + description: + - Maximum number of services engines in this group. + - Allowed values are 0-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + max_vs_per_se: + description: + - Maximum number of virtual services that can be placed on a single service engine. + - East west virtual services are excluded from this limit. + - Allowed values are 1-1000. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + mem_reserve: + description: + - Boolean flag to set mem_reserve. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + memory_for_config_update: + description: + - Indicates the percent of memory reserved for config updates. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 15. + memory_per_se: + description: + - Amount of memory for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 2048. + mgmt_network_ref: + description: + - Management network to use for avi service engines. + - It is a reference to an object of type network. + mgmt_subnet: + description: + - Management subnet to use for avi service engines. + min_cpu_usage: + description: + - When cpu usage on an se falls below the minimum threshold, virtual services hosted on the se may be consolidated onto other underutilized ses. + - After consolidation, unused service engines may then be eligible for deletion. + - Allowed values are 20-60. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + min_scaleout_per_vs: + description: + - Minimum number of active service engines for the virtual service. + - Allowed values are 1-64. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + min_se: + description: + - Minimum number of services engines in this group (relevant for se autorebalance only). + - Allowed values are 0-1000. + - Field introduced in 17.2.13,18.1.3,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + minimum_connection_memory: + description: + - Indicates the percent of memory reserved for connections. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 20. + minimum_required_config_memory: + description: + - Required available config memory to apply any configuration. + - Allowed values are 0-90. + - Field deprecated in 18.1.2. + - Field introduced in 18.1.1. + n_log_streaming_threads: + description: + - Number of threads to use for log streaming. + - Allowed values are 1-100. + - Field introduced in 17.2.12, 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + name: + description: + - Name of the object. + required: true + non_significant_log_throttle: + description: + - This setting limits the number of non-significant logs generated per second per core on this se. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + num_dispatcher_cores: + description: + - Number of dispatcher cores (0,1,2,4,8 or 16). + - If set to 0, then number of dispatcher cores is deduced automatically. + - Allowed values are 0,1,2,4,8,16. + - Field introduced in 17.2.12, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + num_flow_cores_sum_changes_to_ignore: + description: + - Number of changes in num flow cores sum to ignore. + - Default value when not specified in API or module is interpreted by Avi Controller as 8. + openstack_availability_zone: + description: + - Field deprecated in 17.1.1. + openstack_availability_zones: + description: + - Field introduced in 17.1.1. + openstack_mgmt_network_name: + description: + - Avi management network name. + openstack_mgmt_network_uuid: + description: + - Management network uuid. + os_reserved_memory: + description: + - Amount of extra memory to be reserved for use by the operating system on a service engine. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + per_app: + description: + - Per-app se mode is designed for deploying dedicated load balancers per app (vs). + - In this mode, each se is limited to a max of 2 vss. + - Vcpus in per-app ses count towards licensing usage at 25% rate. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + placement_mode: + description: + - If placement mode is 'auto', virtual services are automatically placed on service engines. + - Enum options - PLACEMENT_MODE_AUTO. + - Default value when not specified in API or module is interpreted by Avi Controller as PLACEMENT_MODE_AUTO. + realtime_se_metrics: + description: + - Enable or disable real time se metrics. + reboot_on_stop: + description: + - Reboot the system if the se is stopped. + - Field introduced in 17.2.16,18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_bandwidth_type: + description: + - Select the se bandwidth for the bandwidth license. + - Enum options - SE_BANDWIDTH_UNLIMITED, SE_BANDWIDTH_25M, SE_BANDWIDTH_200M, SE_BANDWIDTH_1000M, SE_BANDWIDTH_10000M. + - Field introduced in 17.2.5. + se_deprovision_delay: + description: + - Duration to preserve unused service engine virtual machines before deleting them. + - If traffic to a virtual service were to spike up abruptly, this se would still be available to be utilized again rather than creating a new se. + - If this value is set to 0, controller will never delete any ses and administrator has to manually cleanup unused ses. + - Allowed values are 0-525600. + - Default value when not specified in API or module is interpreted by Avi Controller as 120. + se_dos_profile: + description: + - Dosthresholdprofile settings for serviceenginegroup. + se_dpdk_pmd: + description: + - Determines if dpdk pool mode driver should be used or not 0 automatically determine based on hypervisor/nic type 1 unconditionally use dpdk + - poll mode driver 2 don't use dpdk poll mode driver. + - Allowed values are 0-2. + - Field introduced in 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_flow_probe_retries: + description: + - Flow probe retry count if no replies are received. + - Allowed values are 0-5. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 2. + se_flow_probe_timer: + description: + - Timeout in milliseconds for flow probe entries. + - Allowed values are 10-200. + - Field introduced in 18.1.4, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + se_ipc_udp_port: + description: + - Udp port for se_dp ipc in docker bridge mode. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1500. + se_name_prefix: + description: + - Prefix to use for virtual machine name of service engines. + - Default value when not specified in API or module is interpreted by Avi Controller as Avi. + se_pcap_lookahead: + description: + - Enables lookahead mode of packet receive in pcap mode. + - Introduced to overcome an issue with hv_netvsc driver. + - Lookahead mode attempts to ensure that application and kernel's view of the receive rings are consistent. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_pcap_reinit_frequency: + description: + - Frequency in seconds at which periodically a pcap reinit check is triggered. + - May be used in conjunction with the configuration pcap_reinit_threshold. + - (valid range 15 mins - 12 hours, 0 - disables). + - Allowed values are 900-43200. + - Special values are 0- 'disable'. + - Field introduced in 17.2.13, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_pcap_reinit_threshold: + description: + - Threshold for input packet receive errors in pcap mode exceeding which a pcap reinit is triggered. + - If not set, an unconditional reinit is performed. + - This value is checked every pcap_reinit_frequency interval. + - Field introduced in 17.2.13, 18.1.3, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_probe_port: + description: + - Tcp port on se where echo service will be run. + - Field introduced in 17.2.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 7. + se_remote_punt_udp_port: + description: + - Udp port for punted packets in docker bridge mode. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 1501. + se_routing: + description: + - Enable routing via service engine datapath. + - When disabled, routing is done by the linux kernel. + - Ip routing needs to be enabled in service engine group for se routing to be effective. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + se_sb_dedicated_core: + description: + - Sideband traffic will be handled by a dedicated core. + - Field introduced in 16.5.2, 17.1.9, 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_sb_threads: + description: + - Number of sideband threads per se. + - Allowed values are 1-128. + - Field introduced in 16.5.2, 17.1.9, 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + se_thread_multiplier: + description: + - Multiplier for se threads based on vcpu. + - Allowed values are 1-10. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + se_tracert_port_range: + description: + - Traceroute port range. + - Field introduced in 17.2.8. + se_tunnel_mode: + description: + - Determines if dsr from secondary se is active or not 0 automatically determine based on hypervisor type. + - 1 disable dsr unconditionally. + - 2 enable dsr unconditionally. + - Allowed values are 0-2. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_tunnel_udp_port: + description: + - Udp port for tunneled packets from secondary to primary se in docker bridge mode. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 1550. + se_udp_encap_ipc: + description: + - Determines if se-se ipc messages are encapsulated in a udp header 0 automatically determine based on hypervisor type. + - 1 use udp encap unconditionally. + - Allowed values are 0-1. + - Field introduced in 17.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_use_dpdk: + description: + - Determines if dpdk library should be used or not 0 automatically determine based on hypervisor type 1 use dpdk if pcap is not enabled 2 + - don't use dpdk. + - Allowed values are 0-2. + - Field introduced in 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + se_vs_hb_max_pkts_in_batch: + description: + - Maximum number of aggregated vs heartbeat packets to send in a batch. + - Allowed values are 1-256. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. + se_vs_hb_max_vs_in_pkt: + description: + - Maximum number of virtualservices for which heartbeat messages are aggregated in one packet. + - Allowed values are 1-1024. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 256. + self_se_election: + description: + - Enable ses to elect a primary amongst themselves in the absence of a connectivity to controller. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + service_ip6_subnets: + description: + - Ipv6 subnets assigned to the se group. + - Required for vs group placement. + - Field introduced in 18.1.1. + service_ip_subnets: + description: + - Subnets assigned to the se group. + - Required for vs group placement. + - Field introduced in 17.1.1. + shm_minimum_config_memory: + description: + - Minimum required shared memory to apply any configuration. + - Allowed values are 0-100. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + significant_log_throttle: + description: + - This setting limits the number of significant logs generated per second per core on this se. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + ssl_preprocess_sni_hostname: + description: + - (beta) preprocess ssl client hello for sni hostname extension.if set to true, this will apply sni child's ssl protocol(s), if they are different + - from sni parent's allowed ssl protocol(s). + - Field introduced in 17.2.12, 18.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + udf_log_throttle: + description: + - This setting limits the number of udf logs generated per second per core on this se. + - Udf logs are generated due to the configured client log filters or the rules with logging enabled. + - Default is 100 logs per second. + - Set it to zero (0) to disable throttling. + - Field introduced in 17.1.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 100. + url: + description: + - Avi controller URL of the object. + use_standard_alb: + description: + - Use standard sku azure load balancer. + - By default cloud level flag is set. + - If not set, it inherits/uses the use_standard_alb flag from the cloud. + - Field introduced in 18.2.3. + type: bool + uuid: + description: + - Unique object identifier of the object. + vcenter_clusters: + description: + - Vcenterclusters settings for serviceenginegroup. + vcenter_datastore_mode: + description: + - Enum options - vcenter_datastore_any, vcenter_datastore_local, vcenter_datastore_shared. + - Default value when not specified in API or module is interpreted by Avi Controller as VCENTER_DATASTORE_ANY. + vcenter_datastores: + description: + - List of vcenterdatastore. + vcenter_datastores_include: + description: + - Boolean flag to set vcenter_datastores_include. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + vcenter_folder: + description: + - Folder to place all the service engine virtual machines in vcenter. + - Default value when not specified in API or module is interpreted by Avi Controller as AviSeFolder. + vcenter_hosts: + description: + - Vcenterhosts settings for serviceenginegroup. + vcpus_per_se: + description: + - Number of vcpus for each of the service engine virtual machines. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. + vip_asg: + description: + - When vip_asg is set, vip configuration will be managed by avi.user will be able to configure vip_asg or vips individually at the time of create. + - Field introduced in 17.2.12, 18.1.2. + vs_host_redundancy: + description: + - Ensure primary and secondary service engines are deployed on different physical hosts. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + vs_scalein_timeout: + description: + - Time to wait for the scaled in se to drain existing flows before marking the scalein done. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + vs_scalein_timeout_for_upgrade: + description: + - During se upgrade, time to wait for the scaled-in se to drain existing flows before marking the scalein done. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + vs_scaleout_timeout: + description: + - Time to wait for the scaled out se to become ready before marking the scaleout done. + - Default value when not specified in API or module is interpreted by Avi Controller as 600. + vs_se_scaleout_additional_wait_time: + description: + - Wait time for sending scaleout ready notification after virtual service is marked up. + - In certain deployments, there may be an additional delay to accept traffic. + - For example, for bgp, some time is needed for route advertisement. + - Allowed values are 0-20. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + vs_se_scaleout_ready_timeout: + description: + - Timeout in seconds for service engine to sendscaleout ready notification of a virtual service. + - Allowed values are 0-60. + - Field introduced in 18.1.5,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 25. + vs_switchover_timeout: + description: + - During se upgrade in a legacy active/standby segroup, time to wait for the new primary se to accept flows before marking the switchover done. + - Field introduced in 17.2.13,18.1.4,18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as 300. + vss_placement: + description: + - Parameters to place virtual services on only a subset of the cores of an se. + - Field introduced in 17.2.5. + vss_placement_enabled: + description: + - If set, virtual services will be placed on only a subset of the cores of an se. + - Field introduced in 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + waf_learning_interval: + description: + - Frequency with which se publishes waf learning. + - Allowed values are 1-43200. + - Field deprecated in 18.2.3. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 10. + waf_learning_memory: + description: + - Amount of memory reserved on se for waf learning. + - Cannot exceed 5% of se memory. + - Field deprecated in 18.2.3. + - Field introduced in 18.1.2. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + waf_mempool: + description: + - Enable memory pool for waf. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + waf_mempool_size: + description: + - Memory pool size used for waf. + - Field introduced in 17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as 64. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create ServiceEngineGroup object + community.network.avi_serviceenginegroup: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_serviceenginegroup +""" + +RETURN = ''' +obj: + description: ServiceEngineGroup (api/serviceenginegroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + accelerated_networking=dict(type='bool',), + active_standby=dict(type='bool',), + additional_config_memory=dict(type='int',), + advertise_backend_networks=dict(type='bool',), + aggressive_failure_detection=dict(type='bool',), + algo=dict(type='str',), + allow_burst=dict(type='bool',), + app_cache_percent=dict(type='int',), + app_learning_memory_percent=dict(type='int',), + archive_shm_limit=dict(type='int',), + async_ssl=dict(type='bool',), + async_ssl_threads=dict(type='int',), + auto_rebalance=dict(type='bool',), + auto_rebalance_capacity_per_se=dict(type='list',), + auto_rebalance_criteria=dict(type='list',), + auto_rebalance_interval=dict(type='int',), + auto_redistribute_active_standby_load=dict(type='bool',), + bgp_state_update_interval=dict(type='int',), + buffer_se=dict(type='int',), + cloud_ref=dict(type='str',), + config_debugs_on_all_cores=dict(type='bool',), + connection_memory_percentage=dict(type='int',), + cpu_reserve=dict(type='bool',), + cpu_socket_affinity=dict(type='bool',), + custom_securitygroups_data=dict(type='list',), + custom_securitygroups_mgmt=dict(type='list',), + custom_tag=dict(type='list',), + data_network_id=dict(type='str',), + datascript_timeout=dict(type='int',), + dedicated_dispatcher_core=dict(type='bool',), + description=dict(type='str',), + disable_avi_securitygroups=dict(type='bool',), + disable_csum_offloads=dict(type='bool',), + disable_gro=dict(type='bool',), + disable_se_memory_check=dict(type='bool',), + disable_tso=dict(type='bool',), + disk_per_se=dict(type='int',), + distribute_load_active_standby=dict(type='bool',), + distribute_queues=dict(type='bool',), + enable_hsm_priming=dict(type='bool',), + enable_multi_lb=dict(type='bool',), + enable_routing=dict(type='bool',), + enable_vip_on_all_interfaces=dict(type='bool',), + enable_vmac=dict(type='bool',), + ephemeral_portrange_end=dict(type='int',), + ephemeral_portrange_start=dict(type='int',), + extra_config_multiplier=dict(type='float',), + extra_shared_config_memory=dict(type='int',), + floating_intf_ip=dict(type='list',), + floating_intf_ip_se_2=dict(type='list',), + flow_table_new_syn_max_entries=dict(type='int',), + free_list_size=dict(type='int',), + ha_mode=dict(type='str',), + hardwaresecuritymodulegroup_ref=dict(type='str',), + heap_minimum_config_memory=dict(type='int',), + hm_on_standby=dict(type='bool',), + host_attribute_key=dict(type='str',), + host_attribute_value=dict(type='str',), + host_gateway_monitor=dict(type='bool',), + hypervisor=dict(type='str',), + ignore_rtt_threshold=dict(type='int',), + ingress_access_data=dict(type='str',), + ingress_access_mgmt=dict(type='str',), + instance_flavor=dict(type='str',), + iptables=dict(type='list',), + least_load_core_selection=dict(type='bool',), + license_tier=dict(type='str',), + license_type=dict(type='str',), + log_disksz=dict(type='int',), + max_cpu_usage=dict(type='int',), + max_memory_per_mempool=dict(type='int',), + max_public_ips_per_lb=dict(type='int',), + max_rules_per_lb=dict(type='int',), + max_scaleout_per_vs=dict(type='int',), + max_se=dict(type='int',), + max_vs_per_se=dict(type='int',), + mem_reserve=dict(type='bool',), + memory_for_config_update=dict(type='int',), + memory_per_se=dict(type='int',), + mgmt_network_ref=dict(type='str',), + mgmt_subnet=dict(type='dict',), + min_cpu_usage=dict(type='int',), + min_scaleout_per_vs=dict(type='int',), + min_se=dict(type='int',), + minimum_connection_memory=dict(type='int',), + minimum_required_config_memory=dict(type='int',), + n_log_streaming_threads=dict(type='int',), + name=dict(type='str', required=True), + non_significant_log_throttle=dict(type='int',), + num_dispatcher_cores=dict(type='int',), + num_flow_cores_sum_changes_to_ignore=dict(type='int',), + openstack_availability_zone=dict(type='str',), + openstack_availability_zones=dict(type='list',), + openstack_mgmt_network_name=dict(type='str',), + openstack_mgmt_network_uuid=dict(type='str',), + os_reserved_memory=dict(type='int',), + per_app=dict(type='bool',), + placement_mode=dict(type='str',), + realtime_se_metrics=dict(type='dict',), + reboot_on_stop=dict(type='bool',), + se_bandwidth_type=dict(type='str',), + se_deprovision_delay=dict(type='int',), + se_dos_profile=dict(type='dict',), + se_dpdk_pmd=dict(type='int',), + se_flow_probe_retries=dict(type='int',), + se_flow_probe_timer=dict(type='int',), + se_ipc_udp_port=dict(type='int',), + se_name_prefix=dict(type='str',), + se_pcap_lookahead=dict(type='bool',), + se_pcap_reinit_frequency=dict(type='int',), + se_pcap_reinit_threshold=dict(type='int',), + se_probe_port=dict(type='int',), + se_remote_punt_udp_port=dict(type='int',), + se_routing=dict(type='bool',), + se_sb_dedicated_core=dict(type='bool',), + se_sb_threads=dict(type='int',), + se_thread_multiplier=dict(type='int',), + se_tracert_port_range=dict(type='dict',), + se_tunnel_mode=dict(type='int',), + se_tunnel_udp_port=dict(type='int',), + se_udp_encap_ipc=dict(type='int',), + se_use_dpdk=dict(type='int',), + se_vs_hb_max_pkts_in_batch=dict(type='int',), + se_vs_hb_max_vs_in_pkt=dict(type='int',), + self_se_election=dict(type='bool',), + service_ip6_subnets=dict(type='list',), + service_ip_subnets=dict(type='list',), + shm_minimum_config_memory=dict(type='int',), + significant_log_throttle=dict(type='int',), + ssl_preprocess_sni_hostname=dict(type='bool',), + tenant_ref=dict(type='str',), + udf_log_throttle=dict(type='int',), + url=dict(type='str',), + use_standard_alb=dict(type='bool',), + uuid=dict(type='str',), + vcenter_clusters=dict(type='dict',), + vcenter_datastore_mode=dict(type='str',), + vcenter_datastores=dict(type='list',), + vcenter_datastores_include=dict(type='bool',), + vcenter_folder=dict(type='str',), + vcenter_hosts=dict(type='dict',), + vcpus_per_se=dict(type='int',), + vip_asg=dict(type='dict',), + vs_host_redundancy=dict(type='bool',), + vs_scalein_timeout=dict(type='int',), + vs_scalein_timeout_for_upgrade=dict(type='int',), + vs_scaleout_timeout=dict(type='int',), + vs_se_scaleout_additional_wait_time=dict(type='int',), + vs_se_scaleout_ready_timeout=dict(type='int',), + vs_switchover_timeout=dict(type='int',), + vss_placement=dict(type='dict',), + vss_placement_enabled=dict(type='bool',), + waf_learning_interval=dict(type='int',), + waf_learning_memory=dict(type='int',), + waf_mempool=dict(type='bool',), + waf_mempool_size=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'serviceenginegroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_snmptrapprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_snmptrapprofile.py new file mode 100644 index 00000000..d8dec87d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_snmptrapprofile.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_snmptrapprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SnmpTrapProfile Avi RESTful Object +description: + - This module is used to configure SnmpTrapProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + name: + description: + - A user-friendly name of the snmp trap configuration. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + trap_servers: + description: + - The ip address or hostname of the snmp trap destination server. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the snmp trap profile object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SnmpTrapProfile object + community.network.avi_snmptrapprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_snmptrapprofile +""" + +RETURN = ''' +obj: + description: SnmpTrapProfile (api/snmptrapprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + trap_servers=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'snmptrapprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslkeyandcertificate.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslkeyandcertificate.py new file mode 100644 index 00000000..e3328e88 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslkeyandcertificate.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_sslkeyandcertificate +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SSLKeyAndCertificate Avi RESTful Object +description: + - This module is used to configure SSLKeyAndCertificate object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + ca_certs: + description: + - Ca certificates in certificate chain. + certificate: + description: + - Sslcertificate settings for sslkeyandcertificate. + required: true + certificate_base64: + description: + - States if the certificate is base64 encoded. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + certificate_management_profile_ref: + description: + - It is a reference to an object of type certificatemanagementprofile. + created_by: + description: + - Creator name. + dynamic_params: + description: + - Dynamic parameters needed for certificate management profile. + enckey_base64: + description: + - Encrypted private key corresponding to the private key (e.g. + - Those generated by an hsm such as thales nshield). + enckey_name: + description: + - Name of the encrypted private key (e.g. + - Those generated by an hsm such as thales nshield). + format: + description: + - Format of the key/certificate file. + - Enum options - SSL_PEM, SSL_PKCS12. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_PEM. + hardwaresecuritymodulegroup_ref: + description: + - It is a reference to an object of type hardwaresecuritymodulegroup. + key: + description: + - Private key. + key_base64: + description: + - States if the private key is base64 encoded. + - Field introduced in 18.1.2, 18.2.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + key_params: + description: + - Sslkeyparams settings for sslkeyandcertificate. + key_passphrase: + description: + - Passphrase used to encrypt the private key. + - Field introduced in 18.1.2, 18.2.1. + name: + description: + - Name of the object. + required: true + status: + description: + - Enum options - ssl_certificate_finished, ssl_certificate_pending. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_CERTIFICATE_FINISHED. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Enum options - ssl_certificate_type_virtualservice, ssl_certificate_type_system, ssl_certificate_type_ca. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create a SSL Key and Certificate + community.network.avi_sslkeyandcertificate: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + key: | + -----BEGIN PRIVATE KEY----- + .... + -----END PRIVATE KEY----- + certificate: + self_signed: true + certificate: | + -----BEGIN CERTIFICATE----- + .... + -----END CERTIFICATE----- + type: SSL_CERTIFICATE_TYPE_VIRTUALSERVICE + name: MyTestCert +""" + +RETURN = ''' +obj: + description: SSLKeyAndCertificate (api/sslkeyandcertificate) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + ca_certs=dict(type='list',), + certificate=dict(type='dict', required=True), + certificate_base64=dict(type='bool',), + certificate_management_profile_ref=dict(type='str',), + created_by=dict(type='str',), + dynamic_params=dict(type='list',), + enckey_base64=dict(type='str',), + enckey_name=dict(type='str',), + format=dict(type='str',), + hardwaresecuritymodulegroup_ref=dict(type='str',), + key=dict(type='str', no_log=True,), + key_base64=dict(type='bool',), + key_params=dict(type='dict',), + key_passphrase=dict(type='str', no_log=True,), + name=dict(type='str', required=True), + status=dict(type='str',), + tenant_ref=dict(type='str',), + type=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'sslkeyandcertificate', + set(['key_passphrase', 'key'])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslprofile.py new file mode 100644 index 00000000..c98fbf14 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_sslprofile.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_sslprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SSLProfile Avi RESTful Object +description: + - This module is used to configure SSLProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + accepted_ciphers: + description: + - Ciphers suites represented as defined by U(http://www.openssl.org/docs/apps/ciphers.html). + - Default value when not specified in API or module is interpreted by Avi Controller as AES:3DES:RC4. + accepted_versions: + description: + - Set of versions accepted by the server. + cipher_enums: + description: + - Enum options - tls_ecdhe_ecdsa_with_aes_128_gcm_sha256, tls_ecdhe_ecdsa_with_aes_256_gcm_sha384, tls_ecdhe_rsa_with_aes_128_gcm_sha256, + - tls_ecdhe_rsa_with_aes_256_gcm_sha384, tls_ecdhe_ecdsa_with_aes_128_cbc_sha256, tls_ecdhe_ecdsa_with_aes_256_cbc_sha384, + - tls_ecdhe_rsa_with_aes_128_cbc_sha256, tls_ecdhe_rsa_with_aes_256_cbc_sha384, tls_rsa_with_aes_128_gcm_sha256, tls_rsa_with_aes_256_gcm_sha384, + - tls_rsa_with_aes_128_cbc_sha256, tls_rsa_with_aes_256_cbc_sha256, tls_ecdhe_ecdsa_with_aes_128_cbc_sha, tls_ecdhe_ecdsa_with_aes_256_cbc_sha, + - tls_ecdhe_rsa_with_aes_128_cbc_sha, tls_ecdhe_rsa_with_aes_256_cbc_sha, tls_rsa_with_aes_128_cbc_sha, tls_rsa_with_aes_256_cbc_sha, + - tls_rsa_with_3des_ede_cbc_sha, tls_rsa_with_rc4_128_sha. + description: + description: + - User defined description for the object. + dhparam: + description: + - Dh parameters used in ssl. + - At this time, it is not configurable and is set to 2048 bits. + enable_ssl_session_reuse: + description: + - Enable ssl session re-use. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + prefer_client_cipher_ordering: + description: + - Prefer the ssl cipher ordering presented by the client during the ssl handshake over the one specified in the ssl profile. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + send_close_notify: + description: + - Send 'close notify' alert message for a clean shutdown of the ssl connection. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + ssl_rating: + description: + - Sslrating settings for sslprofile. + ssl_session_timeout: + description: + - The amount of time in seconds before an ssl session expires. + - Default value when not specified in API or module is interpreted by Avi Controller as 86400. + tags: + description: + - List of tag. + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Ssl profile type. + - Enum options - SSL_PROFILE_TYPE_APPLICATION, SSL_PROFILE_TYPE_SYSTEM. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as SSL_PROFILE_TYPE_APPLICATION. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create SSL profile with list of allowed ciphers + community.network.avi_sslprofile: + controller: '{{ controller }}' + username: '{{ username }}' + password: '{{ password }}' + accepted_ciphers: > + ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA: + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384: + AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA: + AES256-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA384: + ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA + accepted_versions: + - type: SSL_VERSION_TLS1 + - type: SSL_VERSION_TLS1_1 + - type: SSL_VERSION_TLS1_2 + cipher_enums: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_CBC_SHA256 + - TLS_RSA_WITH_AES_256_CBC_SHA256 + - TLS_RSA_WITH_AES_128_CBC_SHA + - TLS_RSA_WITH_AES_256_CBC_SHA + - TLS_RSA_WITH_3DES_EDE_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + name: PFS-BOTH-RSA-EC + send_close_notify: true + ssl_rating: + compatibility_rating: SSL_SCORE_EXCELLENT + performance_rating: SSL_SCORE_EXCELLENT + security_score: '100.0' + tenant_ref: Demo +""" + +RETURN = ''' +obj: + description: SSLProfile (api/sslprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + accepted_ciphers=dict(type='str',), + accepted_versions=dict(type='list',), + cipher_enums=dict(type='list',), + description=dict(type='str',), + dhparam=dict(type='str',), + enable_ssl_session_reuse=dict(type='bool',), + name=dict(type='str', required=True), + prefer_client_cipher_ordering=dict(type='bool',), + send_close_notify=dict(type='bool',), + ssl_rating=dict(type='dict',), + ssl_session_timeout=dict(type='int',), + tags=dict(type='list',), + tenant_ref=dict(type='str',), + type=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'sslprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_stringgroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_stringgroup.py new file mode 100644 index 00000000..d88ed4d9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_stringgroup.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_stringgroup +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of StringGroup Avi RESTful Object +description: + - This module is used to configure StringGroup object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + description: + description: + - User defined description for the object. + kv: + description: + - Configure key value in the string group. + name: + description: + - Name of the string group. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + type: + description: + - Type of stringgroup. + - Enum options - SG_TYPE_STRING, SG_TYPE_KEYVAL. + - Default value when not specified in API or module is interpreted by Avi Controller as SG_TYPE_STRING. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the string group. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create a string group configuration + community.network.avi_stringgroup: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + kv: + - key: text/html + - key: text/xml + - key: text/plain + - key: text/css + - key: text/javascript + - key: application/javascript + - key: application/x-javascript + - key: application/xml + - key: application/pdf + name: System-Compressible-Content-Types + tenant_ref: admin + type: SG_TYPE_STRING +""" + +RETURN = ''' +obj: + description: StringGroup (api/stringgroup) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + description=dict(type='str',), + kv=dict(type='list',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + type=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'stringgroup', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_systemconfiguration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_systemconfiguration.py new file mode 100644 index 00000000..e44ac67c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_systemconfiguration.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_systemconfiguration +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of SystemConfiguration Avi RESTful Object +description: + - This module is used to configure SystemConfiguration object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + admin_auth_configuration: + description: + - Adminauthconfiguration settings for systemconfiguration. + default_license_tier: + description: + - Specifies the default license tier which would be used by new clouds. + - Enum options - ENTERPRISE_16, ENTERPRISE_18. + - Field introduced in 17.2.5. + - Default value when not specified in API or module is interpreted by Avi Controller as ENTERPRISE_18. + dns_configuration: + description: + - Dnsconfiguration settings for systemconfiguration. + dns_virtualservice_refs: + description: + - Dns virtualservices hosting fqdn records for applications across avi vantage. + - If no virtualservices are provided, avi vantage will provide dns services for configured applications. + - Switching back to avi vantage from dns virtualservices is not allowed. + - It is a reference to an object of type virtualservice. + docker_mode: + description: + - Boolean flag to set docker_mode. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + email_configuration: + description: + - Emailconfiguration settings for systemconfiguration. + global_tenant_config: + description: + - Tenantconfiguration settings for systemconfiguration. + linux_configuration: + description: + - Linuxconfiguration settings for systemconfiguration. + mgmt_ip_access_control: + description: + - Configure ip access control for controller to restrict open access. + ntp_configuration: + description: + - Ntpconfiguration settings for systemconfiguration. + portal_configuration: + description: + - Portalconfiguration settings for systemconfiguration. + proxy_configuration: + description: + - Proxyconfiguration settings for systemconfiguration. + secure_channel_configuration: + description: + - Configure secure channel properties. + - Field introduced in 18.1.4, 18.2.1. + snmp_configuration: + description: + - Snmpconfiguration settings for systemconfiguration. + ssh_ciphers: + description: + - Allowed ciphers list for ssh to the management interface on the controller and service engines. + - If this is not specified, all the default ciphers are allowed. + ssh_hmacs: + description: + - Allowed hmac list for ssh to the management interface on the controller and service engines. + - If this is not specified, all the default hmacs are allowed. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. + welcome_workflow_complete: + description: + - This flag is set once the initial controller setup workflow is complete. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create SystemConfiguration object + community.network.avi_systemconfiguration: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_systemconfiguration +""" + +RETURN = ''' +obj: + description: SystemConfiguration (api/systemconfiguration) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + admin_auth_configuration=dict(type='dict',), + default_license_tier=dict(type='str',), + dns_configuration=dict(type='dict',), + dns_virtualservice_refs=dict(type='list',), + docker_mode=dict(type='bool',), + email_configuration=dict(type='dict',), + global_tenant_config=dict(type='dict',), + linux_configuration=dict(type='dict',), + mgmt_ip_access_control=dict(type='dict',), + ntp_configuration=dict(type='dict',), + portal_configuration=dict(type='dict',), + proxy_configuration=dict(type='dict',), + secure_channel_configuration=dict(type='dict',), + snmp_configuration=dict(type='dict',), + ssh_ciphers=dict(type='list',), + ssh_hmacs=dict(type='list',), + url=dict(type='str',), + uuid=dict(type='str',), + welcome_workflow_complete=dict(type='bool',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'systemconfiguration', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_tenant.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_tenant.py new file mode 100644 index 00000000..d3c12088 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_tenant.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_tenant +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Tenant Avi RESTful Object +description: + - This module is used to configure Tenant object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + config_settings: + description: + - Tenantconfiguration settings for tenant. + created_by: + description: + - Creator of this tenant. + description: + description: + - User defined description for the object. + local: + description: + - Boolean flag to set local. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + name: + description: + - Name of the object. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ + - name: Create Tenant using Service Engines in provider mode + community.network.avi_tenant: + controller: '{{ controller }}' + password: '{{ password }}' + username: '{{ username }}' + config_settings: + se_in_provider_context: false + tenant_access_to_provider_se: true + tenant_vrf: false + description: VCenter, Open Stack, AWS Virtual services + local: true + name: Demo +""" + +RETURN = ''' +obj: + description: Tenant (api/tenant) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + config_settings=dict(type='dict',), + created_by=dict(type='str',), + description=dict(type='str',), + local=dict(type='bool',), + name=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'tenant', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_trafficcloneprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_trafficcloneprofile.py new file mode 100644 index 00000000..406ccc6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_trafficcloneprofile.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_trafficcloneprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of TrafficCloneProfile Avi RESTful Object +description: + - This module is used to configure TrafficCloneProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + clone_servers: + description: + - Field introduced in 17.1.1. + cloud_ref: + description: + - It is a reference to an object of type cloud. + - Field introduced in 17.1.1. + name: + description: + - Name for the traffic clone profile. + - Field introduced in 17.1.1. + required: true + preserve_client_ip: + description: + - Specifies if client ip needs to be preserved to clone destination. + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the traffic clone profile. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create TrafficCloneProfile object + community.network.avi_trafficcloneprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_trafficcloneprofile +""" + +RETURN = ''' +obj: + description: TrafficCloneProfile (api/trafficcloneprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + clone_servers=dict(type='list',), + cloud_ref=dict(type='str',), + name=dict(type='str', required=True), + preserve_client_ip=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'trafficcloneprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_user.py new file mode 100644 index 00000000..fb55695d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_user.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +""" +# Created on Aug 2, 2018 +# +# @author: Shrikant Chaudhari (shrikant.chaudhari@avinetworks.com) GitHub ID: gitshrikant +# +# module_check: supported +# +# 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 . +# +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_user +author: Shrikant Chaudhari (@gitshrikant) +short_description: Avi User Module +description: + - This module can be used for creation, updation and deletion of a user. +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + type: str + name: + description: + - Full name of the user. + required: true + type: str + obj_username: + description: + - Name that the user will supply when signing into Avi Vantage, such as jdoe or jdoe@avinetworks.com. + required: true + type: str + obj_password: + description: + - You may either enter a case-sensitive password in this field for the new or existing user. + required: true + type: str + email: + description: + - Email address of the user. This field is used when a user loses their password and requests to have it reset. See Password Recovery. + type: str + access: + description: + - Access settings (write, read, or no access) for each type of resource within Vantage. + type: list + is_superuser: + description: + - If the user will need to have the same privileges as the admin account, set it to true. + type: bool + is_active: + description: + - Activates the current user account. + type: bool + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["post", "put", "patch"] + type: str + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + type: str + user_profile_ref: + description: + - Refer user profile. + - This can also be full URI same as it comes in response payload + type: str + default_tenant_ref: + description: + - Default tenant reference. + - This can also be full URI same as it comes in response payload + default: /api/tenant?name=admin + type: str + + +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: User creation + community.network.avi_user: + controller: "" + username: "" + password: "" + api_version: "" + name: "testuser" + obj_username: "testuser" + obj_password: "test123" + email: "test@abc.test" + access: + - role_ref: "/api/role?name=Tenant-Admin" + tenant_ref: "/api/tenant/admin#admin" + user_profile_ref: "/api/useraccountprofile?name=Default-User-Account-Profile" + is_active: true + is_superuser: true + default_tenant_ref: "/api/tenant?name=admin" + + - name: User creation + community.network.avi_user: + controller: "" + username: "" + password: "" + api_version: "" + name: "testuser" + obj_username: "testuser2" + obj_password: "password" + email: "testuser2@abc.test" + access: + - role_ref: "https://192.0.2.10/api/role?name=Tenant-Admin" + tenant_ref: "https://192.0.2.10/api/tenant/admin#admin" + user_profile_ref: "https://192.0.2.10/api/useraccountprofile?name=Default-User-Account-Profile" + is_active: true + is_superuser: true + default_tenant_ref: "https://192.0.2.10/api/tenant?name=admin" +''' + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.ansible_utils import ( + avi_ansible_api) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + name=dict(type='str', required=True), + obj_username=dict(type='str', required=True), + obj_password=dict(type='str', required=True, no_log=True), + access=dict(type='list',), + email=dict(type='str',), + is_superuser=dict(type='bool',), + is_active=dict(type='bool',), + avi_api_update_method=dict(default='put', + choices=['post', 'put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + user_profile_ref=dict(type='str',), + default_tenant_ref=dict(type='str', default='/api/tenant?name=admin'), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'user', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccount.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccount.py new file mode 100644 index 00000000..9d3f4c2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccount.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +""" +# Created on Aug 12, 2016 +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23 +# +# module_check: not supported +# +# 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 . +# +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_useraccount +author: Chaitanya Deshpande (@chaitanyaavi) +short_description: Avi UserAccount Module +description: + - This module can be used for updating the password of a user. + - This module is useful for setting up admin password for Controller bootstrap. +requirements: [ avisdk ] +options: + old_password: + description: + - Old password for update password or default password for bootstrap. + force_change: + description: + - If specifically set to true then old password is tried first for controller and then the new password is + tried. If not specified this flag then the new password is tried first. + default: false + +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = ''' + - name: Update user password + community.network.avi_useraccount: + controller: "" + username: "" + password: new_password + old_password: "" + api_version: "" + force_change: false + + - name: Update user password using avi_credentials + community.network.avi_useraccount: + avi_credentials: "" + old_password: "" + force_change: false +''' + +RETURN = ''' +obj: + description: Avi REST resource + returned: success, changed + type: dict +''' + +import json +import time +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, ansible_return, avi_obj_cmp, + cleanup_absent_fields, HAS_AVI) + from ansible_collections.community.network.plugins.module_utils.network.avi.avi_api import ( + ApiSession, AviCredentials) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + old_password=dict(type='str', required=True, no_log=True), + # Flag to specify priority of old/new password while establishing session with controller. + # To handle both Saas and conventional (Entire state in playbook) scenario. + force_change=dict(type='bool', default=False) + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule(argument_spec=argument_specs) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + api_creds = AviCredentials() + api_creds.update_from_ansible_module(module) + old_password = module.params.get('old_password') + force_change = module.params.get('force_change', False) + data = { + 'old_password': old_password, + 'password': api_creds.password + } + # First try old password if 'force_change' is set to true + if force_change: + first_pwd = old_password + second_pwd = api_creds.password + # First try new password if 'force_change' is set to false or not specified in playbook. + else: + first_pwd = api_creds.password + second_pwd = old_password + password_changed = False + try: + api = ApiSession.get_session( + api_creds.controller, api_creds.username, + password=first_pwd, timeout=api_creds.timeout, + tenant=api_creds.tenant, tenant_uuid=api_creds.tenant_uuid, + token=api_creds.token, port=api_creds.port) + if force_change: + rsp = api.put('useraccount', data=data) + if rsp: + password_changed = True + except Exception: + pass + if not password_changed: + api = ApiSession.get_session( + api_creds.controller, api_creds.username, password=second_pwd, + timeout=api_creds.timeout, tenant=api_creds.tenant, + tenant_uuid=api_creds.tenant_uuid, token=api_creds.token, + port=api_creds.port) + if not force_change: + rsp = api.put('useraccount', data=data) + if rsp: + password_changed = True + if password_changed: + return ansible_return(module, rsp, True, req=data) + else: + return ansible_return(module, rsp, False, req=data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccountprofile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccountprofile.py new file mode 100644 index 00000000..d098aff0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_useraccountprofile.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_useraccountprofile +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of UserAccountProfile Avi RESTful Object +description: + - This module is used to configure UserAccountProfile object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + account_lock_timeout: + description: + - Lock timeout period (in minutes). + - Default is 30 minutes. + - Default value when not specified in API or module is interpreted by Avi Controller as 30. + credentials_timeout_threshold: + description: + - The time period after which credentials expire. + - Default is 180 days. + - Default value when not specified in API or module is interpreted by Avi Controller as 180. + max_concurrent_sessions: + description: + - Maximum number of concurrent sessions allowed. + - There are unlimited sessions by default. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + max_login_failure_count: + description: + - Number of login attempts before lockout. + - Default is 3 attempts. + - Default value when not specified in API or module is interpreted by Avi Controller as 3. + max_password_history_count: + description: + - Maximum number of passwords to be maintained in the password history. + - Default is 4 passwords. + - Default value when not specified in API or module is interpreted by Avi Controller as 4. + name: + description: + - Name of the object. + required: true + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create UserAccountProfile object + community.network.avi_useraccountprofile: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_useraccountprofile +""" + +RETURN = ''' +obj: + description: UserAccountProfile (api/useraccountprofile) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + account_lock_timeout=dict(type='int',), + credentials_timeout_threshold=dict(type='int',), + max_concurrent_sessions=dict(type='int',), + max_login_failure_count=dict(type='int',), + max_password_history_count=dict(type='int',), + name=dict(type='str', required=True), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'useraccountprofile', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_virtualservice.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_virtualservice.py new file mode 100644 index 00000000..eec62055 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_virtualservice.py @@ -0,0 +1,652 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_virtualservice +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VirtualService Avi RESTful Object +description: + - This module is used to configure VirtualService object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + active_standby_se_tag: + description: + - This configuration only applies if the virtualservice is in legacy active standby ha mode and load distribution among active standby is enabled. + - This field is used to tag the virtualservice so that virtualservices with the same tag will share the same active serviceengine. + - Virtualservices with different tags will have different active serviceengines. + - If one of the serviceengine's in the serviceenginegroup fails, all virtualservices will end up using the same active serviceengine. + - Redistribution of the virtualservices can be either manual or automated when the failed serviceengine recovers. + - Redistribution is based on the auto redistribute property of the serviceenginegroup. + - Enum options - ACTIVE_STANDBY_SE_1, ACTIVE_STANDBY_SE_2. + - Default value when not specified in API or module is interpreted by Avi Controller as ACTIVE_STANDBY_SE_1. + allow_invalid_client_cert: + description: + - Process request even if invalid client certificate is presented. + - Datascript apis need to be used for processing of such requests. + - Field introduced in 18.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + analytics_policy: + description: + - Determines analytics settings for the application. + analytics_profile_ref: + description: + - Specifies settings related to analytics. + - It is a reference to an object of type analyticsprofile. + apic_contract_graph: + description: + - The name of the contract/graph associated with the virtual service. + - Should be in the format. + - This is applicable only for service integration mode with cisco apic controller. + - Field introduced in 17.2.12,18.1.2. + application_profile_ref: + description: + - Enable application layer specific features for the virtual service. + - It is a reference to an object of type applicationprofile. + auto_allocate_floating_ip: + description: + - Auto-allocate floating/elastic ip from the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + auto_allocate_ip: + description: + - Auto-allocate vip from the provided subnet. + - Field deprecated in 17.1.1. + type: bool + availability_zone: + description: + - Availability-zone to place the virtual service. + - Field deprecated in 17.1.1. + avi_allocated_fip: + description: + - (internal-use) fip allocated by avi in the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + avi_allocated_vip: + description: + - (internal-use) vip allocated by avi in the cloud infrastructure. + - Field deprecated in 17.1.1. + type: bool + azure_availability_set: + description: + - (internal-use)applicable for azure only. + - Azure availability set to which this vs is associated. + - Internally set by the cloud connector. + - Field introduced in 17.2.12, 18.1.2. + bulk_sync_kvcache: + description: + - (this is a beta feature). + - Sync key-value cache to the new ses when vs is scaled out. + - For ex ssl sessions are stored using vs's key-value cache. + - When the vs is scaled out, the ssl session information is synced to the new se, allowing existing ssl sessions to be reused on the new se. + - Field introduced in 17.2.7, 18.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + client_auth: + description: + - Http authentication configuration for protected resources. + close_client_conn_on_config_update: + description: + - Close client connection on vs config update. + - Field introduced in 17.2.4. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + cloud_config_cksum: + description: + - Checksum of cloud configuration for vs. + - Internally set by cloud connector. + cloud_ref: + description: + - It is a reference to an object of type cloud. + cloud_type: + description: + - Enum options - cloud_none, cloud_vcenter, cloud_openstack, cloud_aws, cloud_vca, cloud_apic, cloud_mesos, cloud_linuxserver, cloud_docker_ucp, + - cloud_rancher, cloud_oshift_k8s, cloud_azure, cloud_gcp. + - Default value when not specified in API or module is interpreted by Avi Controller as CLOUD_NONE. + connections_rate_limit: + description: + - Rate limit the incoming connections to this virtual service. + content_rewrite: + description: + - Profile used to match and rewrite strings in request and/or response body. + created_by: + description: + - Creator name. + delay_fairness: + description: + - Select the algorithm for qos fairness. + - This determines how multiple virtual services sharing the same service engines will prioritize traffic over a congested network. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + description: + description: + - User defined description for the object. + discovered_network_ref: + description: + - (internal-use) discovered networks providing reachability for client facing virtual service ip. + - This field is deprecated. + - It is a reference to an object of type network. + - Field deprecated in 17.1.1. + discovered_networks: + description: + - (internal-use) discovered networks providing reachability for client facing virtual service ip. + - This field is used internally by avi, not editable by the user. + - Field deprecated in 17.1.1. + discovered_subnet: + description: + - (internal-use) discovered subnets providing reachability for client facing virtual service ip. + - This field is deprecated. + - Field deprecated in 17.1.1. + dns_info: + description: + - Service discovery specific data including fully qualified domain name, type and time-to-live of the dns record. + - Note that only one of fqdn and dns_info setting is allowed. + dns_policies: + description: + - Dns policies applied on the dns traffic of the virtual service. + - Field introduced in 17.1.1. + east_west_placement: + description: + - Force placement on all se's in service group (mesos mode only). + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + enable_autogw: + description: + - Response traffic to clients will be sent back to the source mac address of the connection, rather than statically sent to a default gateway. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + enable_rhi: + description: + - Enable route health injection using the bgp config in the vrf context. + type: bool + enable_rhi_snat: + description: + - Enable route health injection for source nat'ted floating ip address using the bgp config in the vrf context. + type: bool + enabled: + description: + - Enable or disable the virtual service. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + error_page_profile_ref: + description: + - Error page profile to be used for this virtualservice.this profile is used to send the custom error page to the client generated by the proxy. + - It is a reference to an object of type errorpageprofile. + - Field introduced in 17.2.4. + floating_ip: + description: + - Floating ip to associate with this virtual service. + - Field deprecated in 17.1.1. + floating_subnet_uuid: + description: + - If auto_allocate_floating_ip is true and more than one floating-ip subnets exist, then the subnet for the floating ip address allocation. + - This field is applicable only if the virtualservice belongs to an openstack or aws cloud. + - In openstack or aws cloud it is required when auto_allocate_floating_ip is selected. + - Field deprecated in 17.1.1. + flow_dist: + description: + - Criteria for flow distribution among ses. + - Enum options - LOAD_AWARE, CONSISTENT_HASH_SOURCE_IP_ADDRESS, CONSISTENT_HASH_SOURCE_IP_ADDRESS_AND_PORT. + - Default value when not specified in API or module is interpreted by Avi Controller as LOAD_AWARE. + flow_label_type: + description: + - Criteria for flow labelling. + - Enum options - NO_LABEL, APPLICATION_LABEL, SERVICE_LABEL. + - Default value when not specified in API or module is interpreted by Avi Controller as NO_LABEL. + fqdn: + description: + - Dns resolvable, fully qualified domain name of the virtualservice. + - Only one of 'fqdn' and 'dns_info' configuration is allowed. + host_name_xlate: + description: + - Translate the host name sent to the servers to this value. + - Translate the host name sent from servers back to the value used by the client. + http_policies: + description: + - Http policies applied on the data traffic of the virtual service. + ign_pool_net_reach: + description: + - Ignore pool servers network reachability constraints for virtual service placement. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + ip_address: + description: + - Ip address of the virtual service. + - Field deprecated in 17.1.1. + ipam_network_subnet: + description: + - Subnet and/or network for allocating virtualservice ip by ipam provider module. + - Field deprecated in 17.1.1. + l4_policies: + description: + - L4 policies applied to the data traffic of the virtual service. + - Field introduced in 17.2.7. + limit_doser: + description: + - Limit potential dos attackers who exceed max_cps_per_client significantly to a fraction of max_cps_per_client for a while. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + max_cps_per_client: + description: + - Maximum connections per second per client ip. + - Allowed values are 10-1000. + - Special values are 0- 'unlimited'. + - Default value when not specified in API or module is interpreted by Avi Controller as 0. + microservice_ref: + description: + - Microservice representing the virtual service. + - It is a reference to an object of type microservice. + min_pools_up: + description: + - Minimum number of up pools to mark vs up. + - Field introduced in 18.2.1, 17.2.12. + name: + description: + - Name for the virtual service. + required: true + network_profile_ref: + description: + - Determines network settings such as protocol, tcp or udp, and related options for the protocol. + - It is a reference to an object of type networkprofile. + network_ref: + description: + - Manually override the network on which the virtual service is placed. + - It is a reference to an object of type network. + - Field deprecated in 17.1.1. + network_security_policy_ref: + description: + - Network security policies for the virtual service. + - It is a reference to an object of type networksecuritypolicy. + nsx_securitygroup: + description: + - A list of nsx service groups representing the clients which can access the virtual ip of the virtual service. + - Field introduced in 17.1.1. + performance_limits: + description: + - Optional settings that determine performance limits like max connections or bandwidth etc. + pool_group_ref: + description: + - The pool group is an object that contains pools. + - It is a reference to an object of type poolgroup. + pool_ref: + description: + - The pool is an object that contains destination servers and related attributes such as load-balancing and persistence. + - It is a reference to an object of type pool. + port_uuid: + description: + - (internal-use) network port assigned to the virtual service ip address. + - Field deprecated in 17.1.1. + remove_listening_port_on_vs_down: + description: + - Remove listening port if virtualservice is down. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + requests_rate_limit: + description: + - Rate limit the incoming requests to this virtual service. + saml_sp_config: + description: + - Application-specific saml config. + - Field introduced in 18.2.3. + scaleout_ecmp: + description: + - Disable re-distribution of flows across service engines for a virtual service. + - Enable if the network itself performs flow hashing with ecmp in environments such as gcp. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + se_group_ref: + description: + - The service engine group to use for this virtual service. + - Moving to a new se group is disruptive to existing connections for this vs. + - It is a reference to an object of type serviceenginegroup. + security_policy_ref: + description: + - Security policy applied on the traffic of the virtual service. + - This policy is used to perform security actions such as distributed denial of service (ddos) attack mitigation, etc. + - It is a reference to an object of type securitypolicy. + - Field introduced in 18.2.1. + server_network_profile_ref: + description: + - Determines the network settings profile for the server side of tcp proxied connections. + - Leave blank to use the same settings as the client to vs side of the connection. + - It is a reference to an object of type networkprofile. + service_metadata: + description: + - Metadata pertaining to the service provided by this virtual service. + - In openshift/kubernetes environments, egress pod info is stored. + - Any user input to this field will be overwritten by avi vantage. + service_pool_select: + description: + - Select pool based on destination port. + services: + description: + - List of services defined for this virtual service. + sideband_profile: + description: + - Sideband configuration to be used for this virtualservice.it can be used for sending traffic to sideband vips for external inspection etc. + snat_ip: + description: + - Nat'ted floating source ip address(es) for upstream connection to servers. + sp_pool_refs: + description: + - Gslb pools used to manage site-persistence functionality. + - Each site-persistence pool contains the virtualservices in all the other sites, that is auto-generated by the gslb manager. + - This is a read-only field for the user. + - It is a reference to an object of type pool. + - Field introduced in 17.2.2. + ssl_key_and_certificate_refs: + description: + - Select or create one or two certificates, ec and/or rsa, that will be presented to ssl/tls terminated connections. + - It is a reference to an object of type sslkeyandcertificate. + ssl_profile_ref: + description: + - Determines the set of ssl versions and ciphers to accept for ssl/tls terminated connections. + - It is a reference to an object of type sslprofile. + ssl_profile_selectors: + description: + - Select ssl profile based on client ip address match. + - Field introduced in 18.2.3. + ssl_sess_cache_avg_size: + description: + - Expected number of ssl session cache entries (may be exceeded). + - Allowed values are 1024-16383. + - Default value when not specified in API or module is interpreted by Avi Controller as 1024. + sso_policy: + description: + - Client authentication and authorization policy for the virtualservice. + - Field deprecated in 18.2.3. + - Field introduced in 18.2.1. + sso_policy_ref: + description: + - The sso policy attached to the virtualservice. + - It is a reference to an object of type ssopolicy. + - Field introduced in 18.2.3. + static_dns_records: + description: + - List of static dns records applied to this virtual service. + - These are static entries and no health monitoring is performed against the ip addresses. + subnet: + description: + - Subnet providing reachability for client facing virtual service ip. + - Field deprecated in 17.1.1. + subnet_uuid: + description: + - It represents subnet for the virtual service ip address allocation when auto_allocate_ip is true.it is only applicable in openstack or aws cloud. + - This field is required if auto_allocate_ip is true. + - Field deprecated in 17.1.1. + tenant_ref: + description: + - It is a reference to an object of type tenant. + topology_policies: + description: + - Topology policies applied on the dns traffic of the virtual service based ongslb topology algorithm. + - Field introduced in 18.2.3. + traffic_clone_profile_ref: + description: + - Server network or list of servers for cloning traffic. + - It is a reference to an object of type trafficcloneprofile. + - Field introduced in 17.1.1. + traffic_enabled: + description: + - Knob to enable the virtual service traffic on its assigned service engines. + - This setting is effective only when the enabled flag is set to true. + - Field introduced in 17.2.8. + - Default value when not specified in API or module is interpreted by Avi Controller as True. + type: bool + type: + description: + - Specify if this is a normal virtual service, or if it is the parent or child of an sni-enabled virtual hosted virtual service. + - Enum options - VS_TYPE_NORMAL, VS_TYPE_VH_PARENT, VS_TYPE_VH_CHILD. + - Default value when not specified in API or module is interpreted by Avi Controller as VS_TYPE_NORMAL. + url: + description: + - Avi controller URL of the object. + use_bridge_ip_as_vip: + description: + - Use bridge ip as vip on each host in mesos deployments. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + use_vip_as_snat: + description: + - Use the virtual ip as the snat ip for health monitoring and sending traffic to the backend servers instead of the service engine interface ip. + - The caveat of enabling this option is that the virtualservice cannot be configued in an active-active ha mode. + - Dns based multi vip solution has to be used for ha & non-disruptive upgrade purposes. + - Field introduced in 17.1.9,17.2.3. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + uuid: + description: + - Uuid of the virtualservice. + vh_domain_name: + description: + - The exact name requested from the client's sni-enabled tls hello domain name field. + - If this is a match, the parent vs will forward the connection to this child vs. + vh_parent_vs_uuid: + description: + - Specifies the virtual service acting as virtual hosting (sni) parent. + vip: + description: + - List of virtual service ips. + - While creating a 'shared vs',please use vsvip_ref to point to the shared entities. + - Field introduced in 17.1.1. + vrf_context_ref: + description: + - Virtual routing context that the virtual service is bound to. + - This is used to provide the isolation of the set of networks the application is attached to. + - It is a reference to an object of type vrfcontext. + vs_datascripts: + description: + - Datascripts applied on the data traffic of the virtual service. + vsvip_cloud_config_cksum: + description: + - Checksum of cloud configuration for vsvip. + - Internally set by cloud connector. + - Field introduced in 17.2.9, 18.1.2. + vsvip_ref: + description: + - Mostly used during the creation of shared vs, this field refers to entities that can be shared across virtual services. + - It is a reference to an object of type vsvip. + - Field introduced in 17.1.1. + waf_policy_ref: + description: + - Waf policy for the virtual service. + - It is a reference to an object of type wafpolicy. + - Field introduced in 17.2.1. + weight: + description: + - The quality of service weight to assign to traffic transmitted from this virtual service. + - A higher weight will prioritize traffic versus other virtual services sharing the same service engines. + - Allowed values are 1-128. + - Default value when not specified in API or module is interpreted by Avi Controller as 1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Create SSL Virtual Service using Pool testpool2 + community.network.avi_virtualservice: + controller: 10.10.27.90 + username: admin + password: AviNetworks123! + name: newtestvs + state: present + performance_limits: + max_concurrent_connections: 1000 + services: + - port: 443 + enable_ssl: true + - port: 80 + ssl_profile_ref: '/api/sslprofile?name=System-Standard' + application_profile_ref: '/api/applicationprofile?name=System-Secure-HTTP' + ssl_key_and_certificate_refs: + - '/api/sslkeyandcertificate?name=System-Default-Cert' + ip_address: + addr: 10.90.131.103 + type: V4 + pool_ref: '/api/pool?name=testpool2' +""" + +RETURN = ''' +obj: + description: VirtualService (api/virtualservice) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + active_standby_se_tag=dict(type='str',), + allow_invalid_client_cert=dict(type='bool',), + analytics_policy=dict(type='dict',), + analytics_profile_ref=dict(type='str',), + apic_contract_graph=dict(type='str',), + application_profile_ref=dict(type='str',), + auto_allocate_floating_ip=dict(type='bool',), + auto_allocate_ip=dict(type='bool',), + availability_zone=dict(type='str',), + avi_allocated_fip=dict(type='bool',), + avi_allocated_vip=dict(type='bool',), + azure_availability_set=dict(type='str',), + bulk_sync_kvcache=dict(type='bool',), + client_auth=dict(type='dict',), + close_client_conn_on_config_update=dict(type='bool',), + cloud_config_cksum=dict(type='str',), + cloud_ref=dict(type='str',), + cloud_type=dict(type='str',), + connections_rate_limit=dict(type='dict',), + content_rewrite=dict(type='dict',), + created_by=dict(type='str',), + delay_fairness=dict(type='bool',), + description=dict(type='str',), + discovered_network_ref=dict(type='list',), + discovered_networks=dict(type='list',), + discovered_subnet=dict(type='list',), + dns_info=dict(type='list',), + dns_policies=dict(type='list',), + east_west_placement=dict(type='bool',), + enable_autogw=dict(type='bool',), + enable_rhi=dict(type='bool',), + enable_rhi_snat=dict(type='bool',), + enabled=dict(type='bool',), + error_page_profile_ref=dict(type='str',), + floating_ip=dict(type='dict',), + floating_subnet_uuid=dict(type='str',), + flow_dist=dict(type='str',), + flow_label_type=dict(type='str',), + fqdn=dict(type='str',), + host_name_xlate=dict(type='str',), + http_policies=dict(type='list',), + ign_pool_net_reach=dict(type='bool',), + ip_address=dict(type='dict',), + ipam_network_subnet=dict(type='dict',), + l4_policies=dict(type='list',), + limit_doser=dict(type='bool',), + max_cps_per_client=dict(type='int',), + microservice_ref=dict(type='str',), + min_pools_up=dict(type='int',), + name=dict(type='str', required=True), + network_profile_ref=dict(type='str',), + network_ref=dict(type='str',), + network_security_policy_ref=dict(type='str',), + nsx_securitygroup=dict(type='list',), + performance_limits=dict(type='dict',), + pool_group_ref=dict(type='str',), + pool_ref=dict(type='str',), + port_uuid=dict(type='str',), + remove_listening_port_on_vs_down=dict(type='bool',), + requests_rate_limit=dict(type='dict',), + saml_sp_config=dict(type='dict',), + scaleout_ecmp=dict(type='bool',), + se_group_ref=dict(type='str',), + security_policy_ref=dict(type='str',), + server_network_profile_ref=dict(type='str',), + service_metadata=dict(type='str',), + service_pool_select=dict(type='list',), + services=dict(type='list',), + sideband_profile=dict(type='dict',), + snat_ip=dict(type='list',), + sp_pool_refs=dict(type='list',), + ssl_key_and_certificate_refs=dict(type='list',), + ssl_profile_ref=dict(type='str',), + ssl_profile_selectors=dict(type='list',), + ssl_sess_cache_avg_size=dict(type='int',), + sso_policy=dict(type='dict',), + sso_policy_ref=dict(type='str',), + static_dns_records=dict(type='list',), + subnet=dict(type='dict',), + subnet_uuid=dict(type='str',), + tenant_ref=dict(type='str',), + topology_policies=dict(type='list',), + traffic_clone_profile_ref=dict(type='str',), + traffic_enabled=dict(type='bool',), + type=dict(type='str',), + url=dict(type='str',), + use_bridge_ip_as_vip=dict(type='bool',), + use_vip_as_snat=dict(type='bool',), + uuid=dict(type='str',), + vh_domain_name=dict(type='list',), + vh_parent_vs_uuid=dict(type='str',), + vip=dict(type='list',), + vrf_context_ref=dict(type='str',), + vs_datascripts=dict(type='list',), + vsvip_cloud_config_cksum=dict(type='str',), + vsvip_ref=dict(type='str',), + waf_policy_ref=dict(type='str',), + weight=dict(type='int',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'virtualservice', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vrfcontext.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vrfcontext.py new file mode 100644 index 00000000..c4294245 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vrfcontext.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vrfcontext +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VrfContext Avi RESTful Object +description: + - This module is used to configure VrfContext object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + bgp_profile: + description: + - Bgp local and peer info. + cloud_ref: + description: + - It is a reference to an object of type cloud. + debugvrfcontext: + description: + - Configure debug flags for vrf. + - Field introduced in 17.1.1. + description: + description: + - User defined description for the object. + gateway_mon: + description: + - Configure ping based heartbeat check for gateway in service engines of vrf. + internal_gateway_monitor: + description: + - Configure ping based heartbeat check for all default gateways in service engines of vrf. + - Field introduced in 17.1.1. + name: + description: + - Name of the object. + required: true + static_routes: + description: + - List of staticroute. + system_default: + description: + - Boolean flag to set system_default. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Unique object identifier of the object. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VrfContext object + community.network.avi_vrfcontext: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vrfcontext +""" + +RETURN = ''' +obj: + description: VrfContext (api/vrfcontext) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + bgp_profile=dict(type='dict',), + cloud_ref=dict(type='str',), + debugvrfcontext=dict(type='dict',), + description=dict(type='str',), + gateway_mon=dict(type='list',), + internal_gateway_monitor=dict(type='dict',), + name=dict(type='str', required=True), + static_routes=dict(type='list',), + system_default=dict(type='bool',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vrfcontext', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsdatascriptset.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsdatascriptset.py new file mode 100644 index 00000000..1558d00c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsdatascriptset.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.1 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vsdatascriptset +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VSDataScriptSet Avi RESTful Object +description: + - This module is used to configure VSDataScriptSet object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + created_by: + description: + - Creator name. + - Field introduced in 17.1.11,17.2.4. + datascript: + description: + - Datascripts to execute. + description: + description: + - User defined description for the object. + ipgroup_refs: + description: + - Uuid of ip groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type ipaddrgroup. + name: + description: + - Name for the virtual service datascript collection. + required: true + pool_group_refs: + description: + - Uuid of pool groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type poolgroup. + pool_refs: + description: + - Uuid of pools that could be referred by vsdatascriptset objects. + - It is a reference to an object of type pool. + protocol_parser_refs: + description: + - List of protocol parsers that could be referred by vsdatascriptset objects. + - It is a reference to an object of type protocolparser. + - Field introduced in 18.2.3. + string_group_refs: + description: + - Uuid of string groups that could be referred by vsdatascriptset objects. + - It is a reference to an object of type stringgroup. + tenant_ref: + description: + - It is a reference to an object of type tenant. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the virtual service datascript collection. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VSDataScriptSet object + community.network.avi_vsdatascriptset: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vsdatascriptset +""" + +RETURN = ''' +obj: + description: VSDataScriptSet (api/vsdatascriptset) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + created_by=dict(type='str',), + datascript=dict(type='list',), + description=dict(type='str',), + ipgroup_refs=dict(type='list',), + name=dict(type='str', required=True), + pool_group_refs=dict(type='list',), + pool_refs=dict(type='list',), + protocol_parser_refs=dict(type='list',), + string_group_refs=dict(type='list',), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vsdatascriptset', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsvip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsvip.py new file mode 100644 index 00000000..6aa86a3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_vsvip.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# Avi Version: 17.1.2 +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_vsvip +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of VsVip Avi RESTful Object +description: + - This module is used to configure VsVip object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + cloud_ref: + description: + - It is a reference to an object of type cloud. + - Field introduced in 17.1.1. + dns_info: + description: + - Service discovery specific data including fully qualified domain name, type and time-to-live of the dns record. + - Field introduced in 17.1.1. + east_west_placement: + description: + - Force placement on all service engines in the service engine group (container clouds only). + - Field introduced in 17.1.1. + - Default value when not specified in API or module is interpreted by Avi Controller as False. + type: bool + name: + description: + - Name for the vsvip object. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + use_standard_alb: + description: + - This overrides the cloud level default and needs to match the se group value in which it will be used if the se group use_standard_alb value is + - set. + - This is only used when fip is used for vs on azure cloud. + - Field introduced in 18.2.3. + type: bool + uuid: + description: + - Uuid of the vsvip object. + - Field introduced in 17.1.1. + vip: + description: + - List of virtual service ips and other shareable entities. + - Field introduced in 17.1.1. + vrf_context_ref: + description: + - Virtual routing context that the virtual service is bound to. + - This is used to provide the isolation of the set of networks the application is attached to. + - It is a reference to an object of type vrfcontext. + - Field introduced in 17.1.1. + vsvip_cloud_config_cksum: + description: + - Checksum of cloud configuration for vsvip. + - Internally set by cloud connector. + - Field introduced in 17.2.9, 18.1.2. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create VsVip object + community.network.avi_vsvip: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_vsvip +""" + +RETURN = ''' +obj: + description: VsVip (api/vsvip) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + cloud_ref=dict(type='str',), + dns_info=dict(type='list',), + east_west_placement=dict(type='bool',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + use_standard_alb=dict(type='bool',), + uuid=dict(type='str',), + vip=dict(type='list',), + vrf_context_ref=dict(type='str',), + vsvip_cloud_config_cksum=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'vsvip', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_webhook.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_webhook.py new file mode 100644 index 00000000..2374076d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/avi/avi_webhook.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# +# @author: Gaurav Rastogi (grastogi@avinetworks.com) +# Eric Anderson (eanderson@avinetworks.com) +# module_check: supported +# +# Copyright: (c) 2017 Gaurav Rastogi, +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: avi_webhook +author: Gaurav Rastogi (@grastogi23) + +short_description: Module for setup of Webhook Avi RESTful Object +description: + - This module is used to configure Webhook object + - more examples at U(https://github.com/avinetworks/devops) +requirements: [ avisdk ] +options: + state: + description: + - The state that should be applied on the entity. + default: present + choices: ["absent", "present"] + avi_api_update_method: + description: + - Default method for object update is HTTP PUT. + - Setting to patch will override that behavior to use HTTP PATCH. + default: put + choices: ["put", "patch"] + avi_api_patch_op: + description: + - Patch operation to use when using avi_api_update_method as patch. + choices: ["add", "replace", "delete"] + callback_url: + description: + - Callback url for the webhook. + - Field introduced in 17.1.1. + description: + description: + - Field introduced in 17.1.1. + name: + description: + - The name of the webhook profile. + - Field introduced in 17.1.1. + required: true + tenant_ref: + description: + - It is a reference to an object of type tenant. + - Field introduced in 17.1.1. + url: + description: + - Avi controller URL of the object. + uuid: + description: + - Uuid of the webhook profile. + - Field introduced in 17.1.1. + verification_token: + description: + - Verification token sent back with the callback asquery parameters. + - Field introduced in 17.1.1. +extends_documentation_fragment: +- community.network.avi + +''' + +EXAMPLES = """ +- name: Example to create Webhook object + community.network.avi_webhook: + controller: 10.10.25.42 + username: admin + password: something + state: present + name: sample_webhook +""" + +RETURN = ''' +obj: + description: Webhook (api/webhook) object + returned: success, changed + type: dict +''' + +from ansible.module_utils.basic import AnsibleModule +try: + from ansible_collections.community.network.plugins.module_utils.network.avi.avi import ( + avi_common_argument_spec, avi_ansible_api, HAS_AVI) +except ImportError: + HAS_AVI = False + + +def main(): + argument_specs = dict( + state=dict(default='present', + choices=['absent', 'present']), + avi_api_update_method=dict(default='put', + choices=['put', 'patch']), + avi_api_patch_op=dict(choices=['add', 'replace', 'delete']), + callback_url=dict(type='str',), + description=dict(type='str',), + name=dict(type='str', required=True), + tenant_ref=dict(type='str',), + url=dict(type='str',), + uuid=dict(type='str',), + verification_token=dict(type='str',), + ) + argument_specs.update(avi_common_argument_spec()) + module = AnsibleModule( + argument_spec=argument_specs, supports_check_mode=True) + if not HAS_AVI: + return module.fail_json(msg=( + 'Avi python API SDK (avisdk>=17.1) or requests is not installed. ' + 'For more details visit https://github.com/avinetworks/sdk.')) + return avi_ansible_api(module, 'webhook', + set([])) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bcf_switch.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bcf_switch.py new file mode 100644 index 00000000..ee15fa24 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bcf_switch.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: bcf_switch +author: "Ted (@tedelhourani)" +short_description: Create and remove a bcf switch. +description: + - Create and remove a Big Cloud Fabric switch. +options: + name: + description: + - The name of the switch. + required: true + fabric_role: + description: + - Fabric role of the switch. + choices: ['spine', 'leaf'] + required: true + leaf_group: + description: + - The leaf group of the switch if the switch is a leaf. + required: false + mac: + description: + - The MAC address of the switch. + required: true + state: + description: + - Whether the switch should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller IP address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Big Cloud Fabric access token. If this isn't set then the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. +''' + + +EXAMPLES = ''' +- name: Bcf leaf switch + community.network.bcf_switch: + name: Rack1Leaf1 + fabric_role: leaf + leaf_group: R1 + mac: 00:00:00:02:00:02 + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + + +RETURN = ''' # ''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def switch(module, check_mode): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + fabric_role = module.params['fabric_role'] + leaf_group = module.params['leaf_group'] + dpid = '00:00:' + module.params['mac'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/core') + + response = rest.get('switch-config', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing switch config: {0}".format(response.json['description'])) + + config_present = False + for switch in response.json: + if all((switch['name'] == name, + switch['fabric-role'] == fabric_role, + switch['dpid'] == dpid)): + config_present = switch.get('leaf-group', None) == leaf_group + if config_present: + break + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if check_mode: + module.exit_json(changed=True) + + if state in ('present'): + data = {'name': name, 'fabric-role': fabric_role, 'leaf-group': leaf_group, 'dpid': dpid} + response = rest.put('switch-config[name="%s"]' % name, data) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error configuring switch '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('switch-config[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting switch '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + fabric_role=dict(choices=['spine', 'leaf'], required=True), + leaf_group=dict(type='str', required=False), + mac=dict(type='str', required=True), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ), + supports_check_mode=True, + ) + + try: + switch(module, check_mode=module.check_mode) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_chain.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_chain.py new file mode 100644 index 00000000..a393f4e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_chain.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ansible module to manage Big Monitoring Fabric service chains + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: bigmon_chain +author: "Ted (@tedelhourani)" +short_description: Create and remove a bigmon inline service chain. +description: + - Create and remove a bigmon inline service chain. +options: + name: + description: + - The name of the chain. + required: true + state: + description: + - Whether the service chain should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller IP address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Bigmon access token. If this isn't set, the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. +''' + + +EXAMPLES = ''' +- name: Bigmon inline service chain + community.network.bigmon_chain: + name: MyChain + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + + +RETURN = ''' # ''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def chain(module): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/applications/bigchain') + + if None in (name, state, controller): + module.fail_json(msg='parameter `name` is missing') + + response = rest.get('chain?config=true', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing chain config: {0}".format(response.json['description'])) + + config_present = False + matching = [chain for chain in response.json if chain['name'] == name] + if matching: + config_present = True + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if state in ('present'): + response = rest.put('chain[name="%s"]' % name, data={'name': name}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error creating chain '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('chain[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting chain '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ) + ) + + try: + chain(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_policy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_policy.py new file mode 100644 index 00000000..f35d7780 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/bigswitch/bigmon_policy.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Ted Elhourani +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ansible module to manage Big Monitoring Fabric service chains + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: bigmon_policy +author: "Ted (@tedelhourani)" +short_description: Create and remove a bigmon out-of-band policy. +description: + - Create and remove a bigmon out-of-band policy. +options: + name: + description: + - The name of the policy. + required: true + policy_description: + description: + - Description of policy. + action: + description: + - Forward matching packets to delivery interfaces, Drop is for measure rate of matching packets, + but do not forward to delivery interfaces, capture packets and write to a PCAP file, or enable NetFlow generation. + default: forward + choices: ['forward', 'drop', 'flow-gen'] + priority: + description: + - A priority associated with this policy. The higher priority policy takes precedence over a lower priority. + default: 100 + duration: + description: + - Run policy for duration duration or until delivery_packet_count packets are delivered, whichever comes first. + default: 0 + start_time: + description: + - Date the policy becomes active + default: ansible_date_time.iso8601 + delivery_packet_count: + description: + - Run policy until delivery_packet_count packets are delivered. + default: 0 + state: + description: + - Whether the policy should be present or absent. + default: present + choices: ['present', 'absent'] + controller: + description: + - The controller address. + required: true + validate_certs: + description: + - If C(false), SSL certificates will not be validated. This should only be used + on personally controlled devices using self-signed certificates. + required: false + default: true + type: bool + access_token: + description: + - Bigmon access token. If this isn't set, the environment variable C(BIGSWITCH_ACCESS_TOKEN) is used. + +''' + +EXAMPLES = ''' +- name: Policy to aggregate filter and deliver data center (DC) 1 traffic + community.network.bigmon_policy: + name: policy1 + policy_description: DC 1 traffic policy + action: drop + controller: '{{ inventory_hostname }}' + state: present + validate_certs: false +''' + +RETURN = ''' # ''' + +import datetime +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.bigswitch.bigswitch import Rest +from ansible.module_utils._text import to_native + + +def policy(module): + try: + access_token = module.params['access_token'] or os.environ['BIGSWITCH_ACCESS_TOKEN'] + except KeyError as e: + module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + + name = module.params['name'] + policy_description = module.params['policy_description'] + action = module.params['action'] + priority = module.params['priority'] + duration = module.params['duration'] + start_time = module.params['start_time'] + delivery_packet_count = module.params['delivery_packet_count'] + state = module.params['state'] + controller = module.params['controller'] + + rest = Rest(module, + {'content-type': 'application/json', 'Cookie': 'session_cookie=' + access_token}, + 'https://' + controller + ':8443/api/v1/data/controller/applications/bigtap') + + if name is None: + module.fail_json(msg='parameter `name` is missing') + + response = rest.get('policy?config=true', data={}) + if response.status_code != 200: + module.fail_json(msg="failed to obtain existing policy config: {0}".format(response.json['description'])) + + config_present = False + + matching = [policy for policy in response.json + if policy['name'] == name and + policy['duration'] == duration and + policy['delivery-packet-count'] == delivery_packet_count and + policy['policy-description'] == policy_description and + policy['action'] == action and + policy['priority'] == priority] + + if matching: + config_present = True + + if state in ('present') and config_present: + module.exit_json(changed=False) + + if state in ('absent') and not config_present: + module.exit_json(changed=False) + + if state in ('present'): + data = {'name': name, 'action': action, 'policy-description': policy_description, + 'priority': priority, 'duration': duration, 'start-time': start_time, + 'delivery-packet-count': delivery_packet_count} + + response = rest.put('policy[name="%s"]' % name, data=data) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error creating policy '{0}': {1}".format(name, response.json['description'])) + + if state in ('absent'): + response = rest.delete('policy[name="%s"]' % name, data={}) + if response.status_code == 204: + module.exit_json(changed=True) + else: + module.fail_json(msg="error deleting policy '{0}': {1}".format(name, response.json['description'])) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + policy_description=dict(type='str', default=''), + action=dict(choices=['forward', 'drop', 'capture', 'flow-gen'], default='forward'), + priority=dict(type='int', default=100), + duration=dict(type='int', default=0), + start_time=dict(type='str', default=datetime.datetime.now().isoformat() + '+00:00'), + delivery_packet_count=dict(type='int', default=0), + controller=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present'), + validate_certs=dict(type='bool', default='True'), + access_token=dict(type='str', no_log=True) + ) + ) + + try: + policy(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/check_point/cp_publish.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/check_point/cp_publish.py new file mode 100644 index 00000000..27a0c7ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/check_point/cp_publish.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage Check Point Firewall (c) 2019 +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: cp_publish +short_description: All the changes done by this user will be seen by all users only after publish is called. +description: + - All the changes done by this user will be seen by all users only after publish is called. + All operations are performed over Web Services API. +author: "Or Soffer (@chkp-orso)" +options: + uid: + description: + - Session unique identifier. Specify it to publish a different session than the one you currently use. + type: str +extends_documentation_fragment: +- check_point.mgmt.checkpoint_commands + +''' + +EXAMPLES = """ +- name: Publish + community.network.cp_publish: +""" + +RETURN = """ +cp_publish: + description: The checkpoint publish output. + returned: always. + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.check_point.mgmt.plugins.module_utils.checkpoint import checkpoint_argument_spec_for_commands, api_command + + +def main(): + argument_spec = dict( + uid=dict(type='str') + ) + argument_spec.update(checkpoint_argument_spec_for_commands) + + module = AnsibleModule(argument_spec=argument_spec) + + command = "publish" + + result = api_command(module, command) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server.py new file mode 100644 index 00000000..31cbb2a4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server.py @@ -0,0 +1,2176 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: ce_aaa_server +short_description: Manages AAA server global configuration on HUAWEI CloudEngine switches. +description: + - Manages AAA server global configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + type: str + choices: [ absent, present ] + default: present + authen_scheme_name: + description: + - Name of an authentication scheme. + The value is a string of 1 to 32 characters. + type: str + first_authen_mode: + description: + - Preferred authentication mode. + type: str + choices: ['invalid', 'local', 'hwtacacs', 'radius', 'none'] + default: local + author_scheme_name: + description: + - Name of an authorization scheme. + The value is a string of 1 to 32 characters. + type: str + first_author_mode: + description: + - Preferred authorization mode. + type: str + choices: ['invalid', 'local', 'hwtacacs', 'if-authenticated', 'none'] + default: local + acct_scheme_name: + description: + - Accounting scheme name. + The value is a string of 1 to 32 characters. + type: str + accounting_mode: + description: + - Accounting Mode. + type: str + choices: ['invalid', 'hwtacacs', 'radius', 'none'] + default: none + domain_name: + description: + - Name of a domain. + The value is a string of 1 to 64 characters. + type: str + radius_server_group: + description: + - RADIUS server group's name. + The value is a string of 1 to 32 case-insensitive characters. + type: str + hwtacas_template: + description: + - Name of a HWTACACS template. + The value is a string of 1 to 32 case-insensitive characters. + type: str + local_user_group: + description: + - Name of the user group where the user belongs. The user inherits all the rights of the user group. + The value is a string of 1 to 32 characters. + type: str +''' + +EXAMPLES = r''' + +- name: AAA server test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Radius authentication Server Basic settings" + community.network.ce_aaa_server: + state: present + authen_scheme_name: test1 + first_authen_mode: radius + radius_server_group: test2 + provider: "{{ cli }}" + + - name: "Undo radius authentication Server Basic settings" + community.network.ce_aaa_server: + state: absent + authen_scheme_name: test1 + first_authen_mode: radius + radius_server_group: test2 + provider: "{{ cli }}" + + - name: "Hwtacacs accounting Server Basic settings" + community.network.ce_aaa_server: + state: present + acct_scheme_name: test1 + accounting_mode: hwtacacs + hwtacas_template: test2 + provider: "{{ cli }}" + + - name: "Undo hwtacacs accounting Server Basic settings" + community.network.ce_aaa_server: + state: absent + acct_scheme_name: test1 + accounting_mode: hwtacacs + hwtacas_template: test2 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"accounting_mode": "hwtacacs", "acct_scheme_name": "test1", + "hwtacas_template": "test2", "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"accounting scheme": [["hwtacacs"], ["default"]], + "hwtacacs template": ["huawei"]} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"accounting scheme": [["hwtacacs", "test1"]], + "hwtacacs template": ["huawei", "test2"]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["accounting-scheme test1", + "accounting-mode hwtacacs", + "hwtacacs server template test2", + "hwtacacs enable"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +SUCCESS = """success""" +FAILED = """failed""" + +INVALID_SCHEME_CHAR = [' ', '/', '\\', ':', '*', '?', '"', '|', '<', '>'] +INVALID_DOMAIN_CHAR = [' ', '*', '?', '"', '\''] +INVALID_GROUP_CHAR = ['/', '\\', ':', '*', '?', '"', '|', '<', '>'] + + +# get authentication scheme +CE_GET_AUTHENTICATION_SCHEME = """ + + + + + + + + + + + +""" + +# merge authentication scheme +CE_MERGE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# create authentication scheme +CE_CREATE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# delete authentication scheme +CE_DELETE_AUTHENTICATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# get authorization scheme +CE_GET_AUTHORIZATION_SCHEME = """ + + + + + + + + + + + +""" + +# merge authorization scheme +CE_MERGE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# create authorization scheme +CE_CREATE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# delete authorization scheme +CE_DELETE_AUTHORIZATION_SCHEME = """ + + + + + %s + %s + invalid + + + + +""" + +# get accounting scheme +CE_GET_ACCOUNTING_SCHEME = """ + + + + + + + + + + +""" + +# merge accounting scheme +CE_MERGE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# create accounting scheme +CE_CREATE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# delete accounting scheme +CE_DELETE_ACCOUNTING_SCHEME = """ + + + + + %s + %s + + + + +""" + +# get authentication domain +CE_GET_AUTHENTICATION_DOMAIN = """ + + + + + + + + + + +""" + +# merge authentication domain +CE_MERGE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create authentication domain +CE_CREATE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete authentication domain +CE_DELETE_AUTHENTICATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get authorization domain +CE_GET_AUTHORIZATION_DOMAIN = """ + + + + + + + + + + +""" + +# merge authorization domain +CE_MERGE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create authorization domain +CE_CREATE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete authorization domain +CE_DELETE_AUTHORIZATION_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get accounting domain +CE_GET_ACCOUNTING_DOMAIN = """ + + + + + + + + + + +""" + +# merge accounting domain +CE_MERGE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# create accounting domain +CE_CREATE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# delete accounting domain +CE_DELETE_ACCOUNTING_DOMAIN = """ + + + + + %s + %s + + + + +""" + +# get radius template +CE_GET_RADIUS_TEMPLATE = """ + + + + + + + + + + + +""" + +# merge radius template +CE_MERGE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# create radius template +CE_CREATE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# delete radius template +CE_DELETE_RADIUS_TEMPLATE = """ + + + + + %s + 3 + 5 + + + + +""" + +# get hwtacacs template +CE_GET_HWTACACS_TEMPLATE = """ + + + + + + + + + + + +""" + +# merge hwtacacs template +CE_MERGE_HWTACACS_TEMPLATE = """ + + + + + %s + true + 5 + + + + +""" + +# create hwtacacs template +CE_CREATE_HWTACACS_TEMPLATE = """ + + + + + %s + true + 5 + + + + +""" + +# delete hwtacacs template +CE_DELETE_HWTACACS_TEMPLATE = """ + + + + + %s + + + + +""" + +# get radius client +CE_GET_RADIUS_CLIENT = """ + + + + + + + + + +""" + +# merge radius client +CE_MERGE_RADIUS_CLIENT = """ + + + + %s + + + +""" + +# get hwtacacs global config +CE_GET_HWTACACS_GLOBAL_CFG = """ + + + + + + + + + +""" + +# merge hwtacacs global config +CE_MERGE_HWTACACS_GLOBAL_CFG = """ + + + + %s + + + +""" + +# get local user group +CE_GET_LOCAL_USER_GROUP = """ + + + + + + + + + +""" +# merge local user group +CE_MERGE_LOCAL_USER_GROUP = """ + + + + + %s + + + + +""" +# delete local user group +CE_DELETE_LOCAL_USER_GROUP = """ + + + + + %s + + + + +""" + + +class AaaServer(object): + """ Manages aaa configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + recv_xml = set_nc_config(module, conf_str) + + return recv_xml + + def get_authentication_scheme(self, **kwargs): + """ Get scheme of authentication """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHENTICATION_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_authentication_domain(self, **kwargs): + """ Get domain of authentication """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHENTICATION_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_authentication_scheme(self, **kwargs): + """ Merge scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authentication scheme failed.') + + cmds = [] + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode %s" % first_authen_mode + cmds.append(cmd) + + return cmds + + def merge_authentication_domain(self, **kwargs): + """ Merge domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authentication domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + + return cmds + + def create_authentication_scheme(self, **kwargs): + """ Create scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authentication scheme failed.') + + cmds = [] + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode %s" % first_authen_mode + cmds.append(cmd) + + return cmds + + def create_authentication_domain(self, **kwargs): + """ Create domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authentication domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + + return cmds + + def delete_authentication_scheme(self, **kwargs): + """ Delete scheme of authentication """ + + authen_scheme_name = kwargs["authen_scheme_name"] + first_authen_mode = kwargs["first_authen_mode"] + module = kwargs["module"] + + if authen_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHENTICATION_SCHEME % ( + authen_scheme_name, first_authen_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authentication scheme failed.') + + cmds = [] + cmd = "undo authentication-scheme %s" % authen_scheme_name + cmds.append(cmd) + cmd = "authentication-mode none" + cmds.append(cmd) + + return cmds + + def delete_authentication_domain(self, **kwargs): + """ Delete domain of authentication """ + + domain_name = kwargs["domain_name"] + authen_scheme_name = kwargs["authen_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHENTICATION_DOMAIN % ( + domain_name, authen_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authentication domain failed.') + + cmds = [] + cmd = "undo authentication-scheme" + cmds.append(cmd) + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + + return cmds + + def get_authorization_scheme(self, **kwargs): + """ Get scheme of authorization """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHORIZATION_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_authorization_domain(self, **kwargs): + """ Get domain of authorization """ + + module = kwargs["module"] + conf_str = CE_GET_AUTHORIZATION_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_authorization_scheme(self, **kwargs): + """ Merge scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authorization scheme failed.') + + cmds = [] + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode %s" % first_author_mode + cmds.append(cmd) + + return cmds + + def merge_authorization_domain(self, **kwargs): + """ Merge domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge authorization domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + + return cmds + + def create_authorization_scheme(self, **kwargs): + """ Create scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authorization scheme failed.') + + cmds = [] + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode %s" % first_author_mode + cmds.append(cmd) + + return cmds + + def create_authorization_domain(self, **kwargs): + """ Create domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create authorization domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + + return cmds + + def delete_authorization_scheme(self, **kwargs): + """ Delete scheme of authorization """ + + author_scheme_name = kwargs["author_scheme_name"] + first_author_mode = kwargs["first_author_mode"] + module = kwargs["module"] + + if author_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHORIZATION_SCHEME % ( + author_scheme_name, first_author_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authorization scheme failed.') + + cmds = [] + cmd = "undo authorization-scheme %s" % author_scheme_name + cmds.append(cmd) + cmd = "authorization-mode none" + cmds.append(cmd) + + return cmds + + def delete_authorization_domain(self, **kwargs): + """ Delete domain of authorization """ + + domain_name = kwargs["domain_name"] + author_scheme_name = kwargs["author_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_AUTHORIZATION_DOMAIN % ( + domain_name, author_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete authorization domain failed.') + + cmds = [] + cmd = "undo authorization-scheme" + cmds.append(cmd) + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + + return cmds + + def get_accounting_scheme(self, **kwargs): + """ Get scheme of accounting """ + + module = kwargs["module"] + conf_str = CE_GET_ACCOUNTING_SCHEME + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall(r'.*(.*)\s*(.*)', xml_str) + if re_find: + return re_find + else: + return result + + def get_accounting_domain(self, **kwargs): + """ Get domain of accounting """ + + module = kwargs["module"] + conf_str = CE_GET_ACCOUNTING_DOMAIN + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*' + r'(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_accounting_scheme(self, **kwargs): + """ Merge scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + conf_str = CE_MERGE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge accounting scheme failed.') + + cmds = [] + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode %s" % accounting_mode + cmds.append(cmd) + + return cmds + + def merge_accounting_domain(self, **kwargs): + """ Merge domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + conf_str = CE_MERGE_ACCOUNTING_DOMAIN % (domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge accounting domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + + return cmds + + def create_accounting_scheme(self, **kwargs): + """ Create scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + conf_str = CE_CREATE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create accounting scheme failed.') + + cmds = [] + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode %s" % accounting_mode + cmds.append(cmd) + + return cmds + + def create_accounting_domain(self, **kwargs): + """ Create domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + conf_str = CE_CREATE_ACCOUNTING_DOMAIN % ( + domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create accounting domain failed.') + + cmds = [] + cmd = "domain %s" % domain_name + cmds.append(cmd) + cmd = "accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + + return cmds + + def delete_accounting_scheme(self, **kwargs): + """ Delete scheme of accounting """ + + acct_scheme_name = kwargs["acct_scheme_name"] + accounting_mode = kwargs["accounting_mode"] + module = kwargs["module"] + + if acct_scheme_name == "default": + return SUCCESS + + conf_str = CE_DELETE_ACCOUNTING_SCHEME % ( + acct_scheme_name, accounting_mode) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete accounting scheme failed.') + + cmds = [] + cmd = "undo accounting-scheme %s" % acct_scheme_name + cmds.append(cmd) + cmd = "accounting-mode none" + cmds.append(cmd) + + return cmds + + def delete_accounting_domain(self, **kwargs): + """ Delete domain of accounting """ + + domain_name = kwargs["domain_name"] + acct_scheme_name = kwargs["acct_scheme_name"] + module = kwargs["module"] + + if domain_name == "default": + return SUCCESS + + conf_str = CE_DELETE_ACCOUNTING_DOMAIN % ( + domain_name, acct_scheme_name) + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete accounting domain failed.') + + cmds = [] + cmd = "undo domain %s" % domain_name + cmds.append(cmd) + cmd = "undo accounting-scheme" + cmds.append(cmd) + + return cmds + + def get_radius_template(self, **kwargs): + """ Get radius template """ + + module = kwargs["module"] + conf_str = CE_GET_RADIUS_TEMPLATE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_radius_template(self, **kwargs): + """ Merge radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_MERGE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge radius template failed.') + + cmds = [] + cmd = "radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def create_radius_template(self, **kwargs): + """ Create radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_CREATE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create radius template failed.') + + cmds = [] + cmd = "radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def delete_radius_template(self, **kwargs): + """ Delete radius template """ + + radius_server_group = kwargs["radius_server_group"] + module = kwargs["module"] + conf_str = CE_DELETE_RADIUS_TEMPLATE % radius_server_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete radius template failed.') + + cmds = [] + cmd = "undo radius server group %s" % radius_server_group + cmds.append(cmd) + + return cmds + + def get_radius_client(self, **kwargs): + """ Get radius client """ + + module = kwargs["module"] + conf_str = CE_GET_RADIUS_CLIENT + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_radius_client(self, **kwargs): + """ Merge radius client """ + + enable = kwargs["isEnable"] + module = kwargs["module"] + conf_str = CE_MERGE_RADIUS_CLIENT % enable + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge radius client failed.') + + cmds = [] + if enable == "true": + cmd = "radius enable" + else: + cmd = "undo radius enable" + cmds.append(cmd) + + return cmds + + def get_hwtacacs_template(self, **kwargs): + """ Get hwtacacs template """ + + module = kwargs["module"] + conf_str = CE_GET_HWTACACS_TEMPLATE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_hwtacacs_template(self, **kwargs): + """ Merge hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_MERGE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge hwtacacs template failed.') + + cmds = [] + cmd = "hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def create_hwtacacs_template(self, **kwargs): + """ Create hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_CREATE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Create hwtacacs template failed.') + + cmds = [] + cmd = "hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def delete_hwtacacs_template(self, **kwargs): + """ Delete hwtacacs template """ + + hwtacas_template = kwargs["hwtacas_template"] + module = kwargs["module"] + conf_str = CE_DELETE_HWTACACS_TEMPLATE % hwtacas_template + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete hwtacacs template failed.') + + cmds = [] + cmd = "undo hwtacacs server template %s" % hwtacas_template + cmds.append(cmd) + + return cmds + + def get_hwtacacs_global_cfg(self, **kwargs): + """ Get hwtacacs global configure """ + + module = kwargs["module"] + conf_str = CE_GET_HWTACACS_GLOBAL_CFG + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_hwtacacs_global_cfg(self, **kwargs): + """ Merge hwtacacs global configure """ + + enable = kwargs["isEnable"] + module = kwargs["module"] + conf_str = CE_MERGE_HWTACACS_GLOBAL_CFG % enable + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge hwtacacs global config failed.') + + cmds = [] + + if enable == "true": + cmd = "hwtacacs enable" + else: + cmd = "undo hwtacacs enable" + cmds.append(cmd) + + return cmds + + def get_local_user_group(self, **kwargs): + """ Get local user group """ + + module = kwargs["module"] + conf_str = CE_GET_LOCAL_USER_GROUP + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_local_user_group(self, **kwargs): + """ Merge local user group """ + + local_user_group = kwargs["local_user_group"] + module = kwargs["module"] + conf_str = CE_MERGE_LOCAL_USER_GROUP % local_user_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Merge local user group failed.') + + cmds = [] + cmd = "user-group %s" % local_user_group + cmds.append(cmd) + + return cmds + + def delete_local_user_group(self, **kwargs): + """ Delete local user group """ + + local_user_group = kwargs["local_user_group"] + module = kwargs["module"] + conf_str = CE_DELETE_LOCAL_USER_GROUP % local_user_group + + xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in xml: + module.fail_json(msg='Error: Delete local user group failed.') + + cmds = [] + cmd = "undo user-group %s" % local_user_group + cmds.append(cmd) + + return cmds + + +def check_name(**kwargs): + """ Check invalid name """ + + module = kwargs["module"] + name = kwargs["name"] + invalid_char = kwargs["invalid_char"] + + for item in invalid_char: + if item in name: + module.fail_json( + msg='Error: invalid char %s is in the name %s.' % (item, name)) + + +def check_module_argument(**kwargs): + """ Check module argument """ + + module = kwargs["module"] + + authen_scheme_name = module.params['authen_scheme_name'] + author_scheme_name = module.params['author_scheme_name'] + acct_scheme_name = module.params['acct_scheme_name'] + domain_name = module.params['domain_name'] + radius_server_group = module.params['radius_server_group'] + hwtacas_template = module.params['hwtacas_template'] + local_user_group = module.params['local_user_group'] + + if authen_scheme_name: + if len(authen_scheme_name) > 32: + module.fail_json( + msg='Error: authen_scheme_name %s ' + 'is large than 32.' % authen_scheme_name) + check_name(module=module, name=authen_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if author_scheme_name: + if len(author_scheme_name) > 32: + module.fail_json( + msg='Error: author_scheme_name %s ' + 'is large than 32.' % author_scheme_name) + check_name(module=module, name=author_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if acct_scheme_name: + if len(acct_scheme_name) > 32: + module.fail_json( + msg='Error: acct_scheme_name %s ' + 'is large than 32.' % acct_scheme_name) + check_name(module=module, name=acct_scheme_name, + invalid_char=INVALID_SCHEME_CHAR) + + if domain_name: + if len(domain_name) > 64: + module.fail_json( + msg='Error: domain_name %s ' + 'is large than 64.' % domain_name) + check_name(module=module, name=domain_name, + invalid_char=INVALID_DOMAIN_CHAR) + if domain_name == "-" or domain_name == "--": + module.fail_json(msg='domain_name %s ' + 'is invalid.' % domain_name) + + if radius_server_group and len(radius_server_group) > 32: + module.fail_json(msg='Error: radius_server_group %s ' + 'is large than 32.' % radius_server_group) + + if hwtacas_template and len(hwtacas_template) > 32: + module.fail_json( + msg='Error: hwtacas_template %s ' + 'is large than 32.' % hwtacas_template) + + if local_user_group: + if len(local_user_group) > 32: + module.fail_json( + msg='Error: local_user_group %s ' + 'is large than 32.' % local_user_group) + check_name(module=module, name=local_user_group, invalid_char=INVALID_GROUP_CHAR) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + authen_scheme_name=dict(type='str'), + first_authen_mode=dict(default='local', choices=['invalid', 'local', 'hwtacacs', 'radius', 'none']), + author_scheme_name=dict(type='str'), + first_author_mode=dict(default='local', choices=['invalid', 'local', 'hwtacacs', 'if-authenticated', 'none']), + acct_scheme_name=dict(type='str'), + accounting_mode=dict(default='none', choices=['invalid', 'hwtacacs', 'radius', 'none']), + domain_name=dict(type='str'), + radius_server_group=dict(type='str'), + hwtacas_template=dict(type='str'), + local_user_group=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + check_module_argument(module=module) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + authen_scheme_name = module.params['authen_scheme_name'] + first_authen_mode = module.params['first_authen_mode'] + author_scheme_name = module.params['author_scheme_name'] + first_author_mode = module.params['first_author_mode'] + acct_scheme_name = module.params['acct_scheme_name'] + accounting_mode = module.params['accounting_mode'] + domain_name = module.params['domain_name'] + radius_server_group = module.params['radius_server_group'] + hwtacas_template = module.params['hwtacas_template'] + local_user_group = module.params['local_user_group'] + + ce_aaa_server = AaaServer() + + if not ce_aaa_server: + module.fail_json(msg='Error: init module failed.') + + # get proposed + proposed["state"] = state + if authen_scheme_name: + proposed["authen_scheme_name"] = authen_scheme_name + if first_authen_mode: + proposed["first_authen_mode"] = first_authen_mode + if author_scheme_name: + proposed["author_scheme_name"] = author_scheme_name + if first_author_mode: + proposed["first_author_mode"] = first_author_mode + if acct_scheme_name: + proposed["acct_scheme_name"] = acct_scheme_name + if accounting_mode: + proposed["accounting_mode"] = accounting_mode + if domain_name: + proposed["domain_name"] = domain_name + if radius_server_group: + proposed["radius_server_group"] = radius_server_group + if hwtacas_template: + proposed["hwtacas_template"] = hwtacas_template + if local_user_group: + proposed["local_user_group"] = local_user_group + + # authentication + if authen_scheme_name: + + scheme_exist = ce_aaa_server.get_authentication_scheme(module=module) + scheme_new = (authen_scheme_name.lower(), first_authen_mode.lower(), "invalid") + + existing["authentication scheme"] = scheme_exist + + if state == "present": + # present authentication scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + + updates.append(cmd) + changed = True + + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + updates.append(cmd) + changed = True + + # present authentication domain + if domain_name: + domain_exist = ce_aaa_server.get_authentication_domain( + module=module) + domain_new = (domain_name.lower(), authen_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent authentication scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_authentication_scheme( + module=module, + authen_scheme_name=authen_scheme_name, + first_authen_mode=first_authen_mode) + updates.append(cmd) + changed = True + + # absent authentication domain + else: + domain_exist = ce_aaa_server.get_authentication_domain( + module=module) + domain_new = (domain_name.lower(), authen_scheme_name.lower()) + + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_authentication_domain( + module=module, + domain_name=domain_name, + authen_scheme_name=authen_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_authentication_scheme(module=module) + end_state["authentication scheme"] = scheme_end + + # authorization + if author_scheme_name: + + scheme_exist = ce_aaa_server.get_authorization_scheme(module=module) + scheme_new = (author_scheme_name.lower(), first_author_mode.lower(), "invalid") + + existing["authorization scheme"] = scheme_exist + + if state == "present": + # present authorization scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + + # present authorization domain + if domain_name: + domain_exist = ce_aaa_server.get_authorization_domain( + module=module) + domain_new = (domain_name.lower(), author_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent authorization scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_authorization_scheme( + module=module, + author_scheme_name=author_scheme_name, + first_author_mode=first_author_mode) + updates.append(cmd) + changed = True + + # absent authorization domain + else: + domain_exist = ce_aaa_server.get_authorization_domain( + module=module) + domain_new = (domain_name.lower(), author_scheme_name.lower()) + + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_authorization_domain( + module=module, + domain_name=domain_name, + author_scheme_name=author_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_authorization_scheme(module=module) + end_state["authorization scheme"] = scheme_end + + # accounting + if acct_scheme_name: + + scheme_exist = ce_aaa_server.get_accounting_scheme(module=module) + scheme_new = (acct_scheme_name.lower(), accounting_mode.lower()) + + existing["accounting scheme"] = scheme_exist + + if state == "present": + # present accounting scheme + if len(scheme_exist) == 0: + cmd = ce_aaa_server.create_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + elif scheme_new not in scheme_exist: + cmd = ce_aaa_server.merge_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + + # present accounting domain + if domain_name: + domain_exist = ce_aaa_server.get_accounting_domain( + module=module) + domain_new = (domain_name.lower(), acct_scheme_name.lower()) + + if len(domain_exist) == 0: + cmd = ce_aaa_server.create_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + elif domain_new not in domain_exist: + cmd = ce_aaa_server.merge_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + + else: + # absent accounting scheme + if not domain_name: + if len(scheme_exist) == 0: + pass + elif scheme_new not in scheme_exist: + pass + else: + cmd = ce_aaa_server.delete_accounting_scheme( + module=module, + acct_scheme_name=acct_scheme_name, + accounting_mode=accounting_mode) + updates.append(cmd) + changed = True + + # absent accounting domain + else: + domain_exist = ce_aaa_server.get_accounting_domain( + module=module) + domain_new = (domain_name.lower(), acct_scheme_name.lower()) + if len(domain_exist) == 0: + pass + elif domain_new not in domain_exist: + pass + else: + cmd = ce_aaa_server.delete_accounting_domain( + module=module, + domain_name=domain_name, + acct_scheme_name=acct_scheme_name) + updates.append(cmd) + changed = True + + scheme_end = ce_aaa_server.get_accounting_scheme(module=module) + end_state["accounting scheme"] = scheme_end + + # radius group name + if (authen_scheme_name and first_authen_mode.lower() == "radius") \ + or (acct_scheme_name and accounting_mode.lower() == "radius"): + + if not radius_server_group: + module.fail_json(msg='please input radius_server_group when use radius.') + + rds_template_exist = ce_aaa_server.get_radius_template(module=module) + rds_template_new = (radius_server_group) + + rds_enable_exist = ce_aaa_server.get_radius_client(module=module) + + existing["radius template"] = rds_template_exist + existing["radius enable"] = rds_enable_exist + + if state == "present": + # present radius group name + if len(rds_template_exist) == 0: + cmd = ce_aaa_server.create_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + elif rds_template_new not in rds_template_exist: + cmd = ce_aaa_server.merge_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + + rds_enable_new = ("true") + if rds_enable_new not in rds_enable_exist: + cmd = ce_aaa_server.merge_radius_client( + module=module, isEnable="true") + updates.append(cmd) + changed = True + + else: + # absent radius group name + if len(rds_template_exist) == 0: + pass + elif rds_template_new not in rds_template_exist: + pass + else: + cmd = ce_aaa_server.delete_radius_template( + module=module, radius_server_group=radius_server_group) + updates.append(cmd) + changed = True + + rds_enable_new = ("false") + if rds_enable_new not in rds_enable_exist: + cmd = ce_aaa_server.merge_radius_client( + module=module, isEnable="false") + updates.append(cmd) + changed = True + else: + pass + + rds_template_end = ce_aaa_server.get_radius_template(module=module) + end_state["radius template"] = rds_template_end + + rds_enable_end = ce_aaa_server.get_radius_client(module=module) + end_state["radius enable"] = rds_enable_end + + tmp_scheme = author_scheme_name + + # hwtacas template + if (authen_scheme_name and first_authen_mode.lower() == "hwtacacs") \ + or (tmp_scheme and first_author_mode.lower() == "hwtacacs") \ + or (acct_scheme_name and accounting_mode.lower() == "hwtacacs"): + + if not hwtacas_template: + module.fail_json( + msg='please input hwtacas_template when use hwtacas.') + + hwtacacs_exist = ce_aaa_server.get_hwtacacs_template(module=module) + hwtacacs_new = (hwtacas_template) + + hwtacacs_enbale_exist = ce_aaa_server.get_hwtacacs_global_cfg( + module=module) + + existing["hwtacacs template"] = hwtacacs_exist + existing["hwtacacs enable"] = hwtacacs_enbale_exist + + if state == "present": + # present hwtacas template + if len(hwtacacs_exist) == 0: + cmd = ce_aaa_server.create_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + elif hwtacacs_new not in hwtacacs_exist: + cmd = ce_aaa_server.merge_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + + hwtacacs_enbale_new = ("true") + if hwtacacs_enbale_new not in hwtacacs_enbale_exist: + cmd = ce_aaa_server.merge_hwtacacs_global_cfg( + module=module, isEnable="true") + updates.append(cmd) + changed = True + + else: + # absent hwtacas template + if len(hwtacacs_exist) == 0: + pass + elif hwtacacs_new not in hwtacacs_exist: + pass + else: + cmd = ce_aaa_server.delete_hwtacacs_template( + module=module, hwtacas_template=hwtacas_template) + updates.append(cmd) + changed = True + + hwtacacs_enbale_new = ("false") + if hwtacacs_enbale_new not in hwtacacs_enbale_exist: + cmd = ce_aaa_server.merge_hwtacacs_global_cfg( + module=module, isEnable="false") + updates.append(cmd) + changed = True + else: + pass + + hwtacacs_end = ce_aaa_server.get_hwtacacs_template(module=module) + end_state["hwtacacs template"] = hwtacacs_end + + hwtacacs_enable_end = ce_aaa_server.get_hwtacacs_global_cfg( + module=module) + end_state["hwtacacs enable"] = hwtacacs_enable_end + + # local user group + if local_user_group: + + user_group_exist = ce_aaa_server.get_local_user_group(module=module) + user_group_new = (local_user_group) + + existing["local user group"] = user_group_exist + + if state == "present": + # present local user group + if len(user_group_exist) == 0: + cmd = ce_aaa_server.merge_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + elif user_group_new not in user_group_exist: + cmd = ce_aaa_server.merge_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + + else: + # absent local user group + if len(user_group_exist) == 0: + pass + elif user_group_new not in user_group_exist: + pass + else: + cmd = ce_aaa_server.delete_local_user_group( + module=module, local_user_group=local_user_group) + updates.append(cmd) + changed = True + + user_group_end = ce_aaa_server.get_local_user_group(module=module) + end_state["local user group"] = user_group_end + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server_host.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server_host.py new file mode 100644 index 00000000..00fc4cdd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_aaa_server_host.py @@ -0,0 +1,2636 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_aaa_server_host +short_description: Manages AAA server host configuration on HUAWEI CloudEngine switches. +description: + - Manages AAA server host configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + local_user_name: + description: + - Name of a local user. + The value is a string of 1 to 253 characters. + local_password: + description: + - Login password of a user. The password can contain letters, numbers, and special characters. + The value is a string of 1 to 255 characters. + local_service_type: + description: + - The type of local user login through, such as ftp ssh snmp telnet. + local_ftp_dir: + description: + - FTP user directory. + The value is a string of 1 to 255 characters. + local_user_level: + description: + - Login level of a local user. + The value is an integer ranging from 0 to 15. + local_user_group: + description: + - Name of the user group where the user belongs. The user inherits all the rights of the user group. + The value is a string of 1 to 32 characters. + radius_group_name: + description: + - RADIUS server group's name. + The value is a string of 1 to 32 case-insensitive characters. + radius_server_type: + description: + - Type of Radius Server. + choices: ['Authentication', 'Accounting'] + radius_server_ip: + description: + - IPv4 address of configured server. + The value is a string of 0 to 255 characters, in dotted decimal notation. + radius_server_ipv6: + description: + - IPv6 address of configured server. + The total length is 128 bits. + radius_server_port: + description: + - Configured server port for a particular server. + The value is an integer ranging from 1 to 65535. + radius_server_mode: + description: + - Configured primary or secondary server for a particular server. + choices: ['Secondary-server', 'Primary-server'] + radius_vpn_name: + description: + - Set VPN instance. + The value is a string of 1 to 31 case-sensitive characters. + radius_server_name: + description: + - Hostname of configured server. + The value is a string of 0 to 255 case-sensitive characters. + hwtacacs_template: + description: + - Name of a HWTACACS template. + The value is a string of 1 to 32 case-insensitive characters. + hwtacacs_server_ip: + description: + - Server IPv4 address. Must be a valid unicast IP address. + The value is a string of 0 to 255 characters, in dotted decimal notation. + hwtacacs_server_ipv6: + description: + - Server IPv6 address. Must be a valid unicast IP address. + The total length is 128 bits. + hwtacacs_server_type: + description: + - Hwtacacs server type. + choices: ['Authentication', 'Authorization', 'Accounting', 'Common'] + hwtacacs_is_secondary_server: + description: + - Whether the server is secondary. + type: bool + default: 'no' + hwtacacs_vpn_name: + description: + - VPN instance name. + hwtacacs_is_public_net: + description: + - Set the public-net. + type: bool + default: 'no' + hwtacacs_server_host_name: + description: + - Hwtacacs server host name. +''' + +EXAMPLES = ''' + +- name: AAA server host test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config local user when use local scheme" + community.network.ce_aaa_server_host: + state: present + local_user_name: user1 + local_password: 123456 + provider: "{{ cli }}" + + - name: "Undo local user when use local scheme" + community.network.ce_aaa_server_host: + state: absent + local_user_name: user1 + local_password: 123456 + provider: "{{ cli }}" + + - name: "Config radius server ip" + community.network.ce_aaa_server_host: + state: present + radius_group_name: group1 + radius_server_type: Authentication + radius_server_ip: 10.1.10.1 + radius_server_port: 2000 + radius_server_mode: Primary-server + radius_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Undo radius server ip" + community.network.ce_aaa_server_host: + state: absent + radius_group_name: group1 + radius_server_type: Authentication + radius_server_ip: 10.1.10.1 + radius_server_port: 2000 + radius_server_mode: Primary-server + radius_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Config hwtacacs server ip" + community.network.ce_aaa_server_host: + state: present + hwtacacs_template: template + hwtacacs_server_ip: 10.10.10.10 + hwtacacs_server_type: Authorization + hwtacacs_vpn_name: _public_ + provider: "{{ cli }}" + + - name: "Undo hwtacacs server ip" + community.network.ce_aaa_server_host: + state: absent + hwtacacs_template: template + hwtacacs_server_ip: 10.10.10.10 + hwtacacs_server_type: Authorization + hwtacacs_vpn_name: _public_ + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"hwtacacs_is_public_net": "false", + "hwtacacs_is_secondary_server": "false", + "hwtacacs_server_ip": "10.135.182.157", + "hwtacacs_server_type": "Authorization", + "hwtacacs_template": "wdz", + "hwtacacs_vpn_name": "_public_", + "local_password": "******", + "state": "present"} +existing: + description: k/v pairs of existing aaa server host + returned: always + type: dict + sample: {"radius server ipv4": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"radius server ipv4": [ + [ + "10.1.10.1", + "Authentication", + "2000", + "Primary-server", + "_public_" + ] + ]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["hwtacacs server template test", + "hwtacacs server authorization 10.135.182.157 vpn-instance test_vpn public-net"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +SUCCESS = """success""" +FAILED = """failed""" + +INVALID_USER_NAME_CHAR = [' ', '/', '\\', + ':', '*', '?', '"', '\'', '<', '>', '%'] + +# get local user name +CE_GET_LOCAL_USER_INFO_HEADER = """ + + + + + + + +""" +CE_GET_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# merge local user name +CE_MERGE_LOCAL_USER_INFO_HEADER = """ + + + + + + %s +""" +CE_MERGE_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# delete local user name +CE_DELETE_LOCAL_USER_INFO_HEADER = """ + + + + + + %s +""" +CE_DELETE_LOCAL_USER_INFO_TAIL = """ + + + + + +""" + +# get radius server config ipv4 +CE_GET_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge radius server config ipv4 +CE_MERGE_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete radius server config ipv4 +CE_DELETE_RADIUS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get radius server config ipv6 +CE_GET_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + + + + + + + + + + +""" + +# merge radius server config ipv6 +CE_MERGE_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# delete radius server config ipv6 +CE_DELETE_RADIUS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# get radius server name +CE_GET_RADIUS_SERVER_NAME = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge radius server name +CE_MERGE_RADIUS_SERVER_NAME = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete radius server name +CE_DELETE_RADIUS_SERVER_NAME = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs server config ipv4 +CE_GET_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge hwtacacs server config ipv4 +CE_MERGE_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs server config ipv4 +CE_DELETE_HWTACACS_SERVER_CFG_IPV4 = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs server config ipv6 +CE_GET_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + + + + + + + + + + +""" + +# merge hwtacacs server config ipv6 +CE_MERGE_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs server config ipv6 +CE_DELETE_HWTACACS_SERVER_CFG_IPV6 = """ + + + + + %s + + + %s + %s + %s + %s + + + + + + +""" + +# get hwtacacs host server config +CE_GET_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + + + + + + + + + + + +""" + +# merge hwtacacs host server config +CE_MERGE_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + +# delete hwtacacs host server config +CE_DELETE_HWTACACS_HOST_SERVER_CFG = """ + + + + + %s + + + %s + %s + %s + %s + %s + + + + + + +""" + + +class AaaServerHost(object): + """ Manages aaa server host configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + recv_xml = set_nc_config(module, conf_str) + + return recv_xml + + def get_local_user_info(self, **kwargs): + """ Get local user information """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + state = module.params['state'] + + result = dict() + result["local_user_info"] = [] + need_cfg = False + + conf_str = CE_GET_LOCAL_USER_INFO_HEADER + + if local_service_type: + if local_service_type == "none": + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + conf_str += "" + elif local_service_type == "dot1x": + conf_str += "" + else: + option = local_service_type.split(" ") + for tmp in option: + if tmp == "dot1x": + module.fail_json( + msg='Error: Do not input dot1x with other service type.') + elif tmp == "none": + module.fail_json( + msg='Error: Do not input none with other service type.') + elif tmp == "ftp": + conf_str += "" + elif tmp == "snmp": + conf_str += "" + elif tmp == "ssh": + conf_str += "" + elif tmp == "telnet": + conf_str += "" + elif tmp == "terminal": + conf_str += "" + else: + module.fail_json( + msg='Error: Do not support the type [%s].' % tmp) + + if local_ftp_dir: + conf_str += "" + + if local_user_level: + conf_str += "" + + if local_user_group: + conf_str += "" + + conf_str += CE_GET_LOCAL_USER_INFO_TAIL + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_user_info = root.findall("aaa/lam/users/user") + if local_user_info: + for tmp in local_user_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["userName", "password", "userLevel", "ftpDir", "userGroupName", + "serviceTerminal", "serviceTelnet", "serviceFtp", "serviceSsh", + "serviceSnmp", "serviceDot1x"]: + tmp_dict[site.tag] = site.text + + result["local_user_info"].append(tmp_dict) + + if state == "present": + need_cfg = True + else: + if result["local_user_info"]: + for tmp in result["local_user_info"]: + if "userName" in tmp.keys(): + if tmp["userName"] == local_user_name: + + if not local_service_type and not local_user_level \ + and not local_ftp_dir and not local_user_group: + + need_cfg = True + + if local_service_type: + if local_service_type == "none": + if tmp.get("serviceTerminal") == "true" or \ + tmp.get("serviceTelnet") == "true" or \ + tmp.get("serviceFtp") == "true" or \ + tmp.get("serviceSsh") == "true" or \ + tmp.get("serviceSnmp") == "true" or \ + tmp.get("serviceDot1x") == "true": + need_cfg = True + elif local_service_type == "dot1x": + if tmp.get("serviceDot1x") == "true": + need_cfg = True + elif tmp == "ftp": + if tmp.get("serviceFtp") == "true": + need_cfg = True + elif tmp == "snmp": + if tmp.get("serviceSnmp") == "true": + need_cfg = True + elif tmp == "ssh": + if tmp.get("serviceSsh") == "true": + need_cfg = True + elif tmp == "telnet": + if tmp.get("serviceTelnet") == "true": + need_cfg = True + elif tmp == "terminal": + if tmp.get("serviceTerminal") == "true": + need_cfg = True + + if local_user_level: + if tmp.get("userLevel") == local_user_level: + need_cfg = True + + if local_ftp_dir: + if tmp.get("ftpDir") == local_ftp_dir: + need_cfg = True + + if local_user_group: + if tmp.get("userGroupName") == local_user_group: + need_cfg = True + + break + + result["need_cfg"] = need_cfg + return result + + def merge_local_user_info(self, **kwargs): + """ Merge local user information by netconf """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + state = module.params['state'] + + cmds = [] + + conf_str = CE_MERGE_LOCAL_USER_INFO_HEADER % local_user_name + + if local_password: + conf_str += "%s" % local_password + + if state == "present": + cmd = "local-user %s password cipher %s" % ( + local_user_name, local_password) + cmds.append(cmd) + + if local_service_type: + if local_service_type == "none": + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + conf_str += "false" + + cmd = "local-user %s service-type none" % local_user_name + cmds.append(cmd) + + elif local_service_type == "dot1x": + if state == "present": + conf_str += "true" + cmd = "local-user %s service-type dot1x" % local_user_name + else: + conf_str += "false" + cmd = "undo local-user %s service-type" % local_user_name + + cmds.append(cmd) + + else: + option = local_service_type.split(" ") + for tmp in option: + if tmp == "dot1x": + module.fail_json( + msg='Error: Do not input dot1x with other service type.') + if tmp == "none": + module.fail_json( + msg='Error: Do not input none with other service type.') + + if state == "present": + if tmp == "ftp": + conf_str += "true" + cmd = "local-user %s service-type ftp" % local_user_name + elif tmp == "snmp": + conf_str += "true" + cmd = "local-user %s service-type snmp" % local_user_name + elif tmp == "ssh": + conf_str += "true" + cmd = "local-user %s service-type ssh" % local_user_name + elif tmp == "telnet": + conf_str += "true" + cmd = "local-user %s service-type telnet" % local_user_name + elif tmp == "terminal": + conf_str += "true" + cmd = "local-user %s service-type terminal" % local_user_name + + cmds.append(cmd) + + else: + if tmp == "ftp": + conf_str += "false" + elif tmp == "snmp": + conf_str += "false" + elif tmp == "ssh": + conf_str += "false" + elif tmp == "telnet": + conf_str += "false" + elif tmp == "terminal": + conf_str += "false" + + if state == "absent": + cmd = "undo local-user %s service-type" % local_user_name + cmds.append(cmd) + + if local_ftp_dir: + if state == "present": + conf_str += "%s" % local_ftp_dir + cmd = "local-user %s ftp-directory %s" % ( + local_user_name, local_ftp_dir) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s ftp-directory" % local_user_name + cmds.append(cmd) + + if local_user_level: + if state == "present": + conf_str += "%s" % local_user_level + cmd = "local-user %s level %s" % ( + local_user_name, local_user_level) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s level" % local_user_name + cmds.append(cmd) + + if local_user_group: + if state == "present": + conf_str += "%s" % local_user_group + cmd = "local-user %s user-group %s" % ( + local_user_name, local_user_group) + cmds.append(cmd) + else: + conf_str += "" + cmd = "undo local-user %s user-group" % local_user_name + cmds.append(cmd) + + conf_str += CE_MERGE_LOCAL_USER_INFO_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge local user info failed.') + + return cmds + + def delete_local_user_info(self, **kwargs): + """ Delete local user information by netconf """ + + module = kwargs["module"] + local_user_name = module.params['local_user_name'] + conf_str = CE_DELETE_LOCAL_USER_INFO_HEADER % local_user_name + conf_str += CE_DELETE_LOCAL_USER_INFO_TAIL + + cmds = [] + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete local user info failed.') + + cmd = "undo local-user %s" % local_user_name + cmds.append(cmd) + + return cmds + + def get_radius_server_cfg_ipv4(self, **kwargs): + """ Get radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + state = module.params['state'] + + result = dict() + result["radius_server_ip_v4"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_CFG_IPV4 % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_ip_v4 = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerIPV4s/rdsServerIPV4") + if radius_server_ip_v4: + for tmp in radius_server_ip_v4: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverIPAddress", "serverPort", "serverMode", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["radius_server_ip_v4"].append(tmp_dict) + + if result["radius_server_ip_v4"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_ip: + cfg["serverIPAddress"] = radius_server_ip.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + if radius_vpn_name: + cfg["vpnName"] = radius_vpn_name.lower() + + for tmp in result["radius_server_ip_v4"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_ip: + exist_cfg["serverIPAddress"] = tmp.get("serverIPAddress").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + if radius_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_cfg_ipv4(self, **kwargs): + """ Merge radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_MERGE_RADIUS_SERVER_CFG_IPV4 % ( + radius_group_name, radius_server_type, + radius_server_ip, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge radius server config ipv4 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_cfg_ipv4(self, **kwargs): + """ Delete radius server configure ipv4 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_DELETE_RADIUS_SERVER_CFG_IPV4 % ( + radius_group_name, radius_server_type, + radius_server_ip, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Create radius server config ipv4 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting %s %s" % ( + radius_server_ip, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_radius_server_cfg_ipv6(self, **kwargs): + """ Get radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + state = module.params['state'] + + result = dict() + result["radius_server_ip_v6"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_CFG_IPV6 % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_ip_v6 = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerIPV6s/rdsServerIPV6") + if radius_server_ip_v6: + for tmp in radius_server_ip_v6: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverIPAddress", "serverPort", "serverMode"]: + tmp_dict[site.tag] = site.text + + result["radius_server_ip_v6"].append(tmp_dict) + + if result["radius_server_ip_v6"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_ipv6: + cfg["serverIPAddress"] = radius_server_ipv6.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + + for tmp in result["radius_server_ip_v6"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_ipv6: + exist_cfg["serverIPAddress"] = tmp.get("serverIPAddress").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_cfg_ipv6(self, **kwargs): + """ Merge radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + + conf_str = CE_MERGE_RADIUS_SERVER_CFG_IPV6 % ( + radius_group_name, radius_server_type, + radius_server_ipv6, radius_server_port, + radius_server_mode) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge radius server config ipv6 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_cfg_ipv6(self, **kwargs): + """ Delete radius server configure ipv6 """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + + conf_str = CE_DELETE_RADIUS_SERVER_CFG_IPV6 % ( + radius_group_name, radius_server_type, + radius_server_ipv6, radius_server_port, + radius_server_mode) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Create radius server config ipv6 failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting %s %s" % ( + radius_server_ipv6, radius_server_port) + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_radius_server_name(self, **kwargs): + """ Get radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + state = module.params['state'] + + result = dict() + result["radius_server_name_cfg"] = [] + need_cfg = False + + conf_str = CE_GET_RADIUS_SERVER_NAME % radius_group_name + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + radius_server_name_cfg = root.findall( + "radius/rdsTemplates/rdsTemplate/rdsServerNames/rdsServerName") + if radius_server_name_cfg: + for tmp in radius_server_name_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverType", "serverName", "serverPort", "serverMode", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["radius_server_name_cfg"].append(tmp_dict) + + if result["radius_server_name_cfg"]: + cfg = dict() + config_list = list() + if radius_server_type: + cfg["serverType"] = radius_server_type.lower() + if radius_server_name: + cfg["serverName"] = radius_server_name.lower() + if radius_server_port: + cfg["serverPort"] = radius_server_port.lower() + if radius_server_mode: + cfg["serverMode"] = radius_server_mode.lower() + if radius_vpn_name: + cfg["vpnName"] = radius_vpn_name.lower() + + for tmp in result["radius_server_name_cfg"]: + exist_cfg = dict() + if radius_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if radius_server_name: + exist_cfg["serverName"] = tmp.get("serverName").lower() + if radius_server_port: + exist_cfg["serverPort"] = tmp.get("serverPort").lower() + if radius_server_mode: + exist_cfg["serverMode"] = tmp.get("serverMode").lower() + if radius_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_radius_server_name(self, **kwargs): + """ Merge radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_MERGE_RADIUS_SERVER_NAME % ( + radius_group_name, radius_server_type, + radius_server_name, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge radius server name failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "radius server authentication hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "radius server accounting hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_radius_server_name(self, **kwargs): + """ Delete radius server name """ + + module = kwargs["module"] + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_name = module.params['radius_server_name'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + + conf_str = CE_DELETE_RADIUS_SERVER_NAME % ( + radius_group_name, radius_server_type, + radius_server_name, radius_server_port, + radius_server_mode, radius_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: delete radius server name failed.') + + cmds = [] + + cmd = "radius server group %s" % radius_group_name + cmds.append(cmd) + + if radius_server_type == "Authentication": + cmd = "undo radius server authentication hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + else: + cmd = "undo radius server accounting hostname %s %s" % ( + radius_server_name, radius_server_port) + + if radius_vpn_name and radius_vpn_name != "_public_": + cmd += " vpn-instance %s" % radius_vpn_name + + if radius_server_mode == "Secondary-server": + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Get hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + state = module.params["state"] + + result = dict() + result["hwtacacs_server_cfg_ipv4"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_SERVER_CFG_IPV4 % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_cfg_ipv4 = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacSrvCfgs/hwTacSrvCfg") + if hwtacacs_server_cfg_ipv4: + for tmp in hwtacacs_server_cfg_ipv4: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverIpAddress", "serverType", "isSecondaryServer", "isPublicNet", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_cfg_ipv4"].append(tmp_dict) + + if result["hwtacacs_server_cfg_ipv4"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_ip: + cfg["serverIpAddress"] = hwtacacs_server_ip.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_is_public_net: + cfg["isPublicNet"] = str(hwtacacs_is_public_net).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_cfg_ipv4"]: + exist_cfg = dict() + if hwtacacs_server_ip: + exist_cfg["serverIpAddress"] = tmp.get("serverIpAddress").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_is_public_net: + exist_cfg["isPublicNet"] = tmp.get("isPublicNet").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Merge hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_MERGE_HWTACACS_SERVER_CFG_IPV4 % ( + hwtacacs_template, hwtacacs_server_ip, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs server config ipv4 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_server_cfg_ipv4(self, **kwargs): + """ Delete hwtacacs server configure ipv4 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ip = module.params["hwtacacs_server_ip"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_DELETE_HWTACACS_SERVER_CFG_IPV4 % ( + hwtacacs_template, hwtacacs_server_ip, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs server config ipv4 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server %s" % hwtacacs_server_ip + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Get hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + state = module.params["state"] + + result = dict() + result["hwtacacs_server_cfg_ipv6"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_SERVER_CFG_IPV6 % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_cfg_ipv6 = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacIpv6SrvCfgs/hwTacIpv6SrvCfg") + if hwtacacs_server_cfg_ipv6: + for tmp in hwtacacs_server_cfg_ipv6: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverIpAddress", "serverType", "isSecondaryServer", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_cfg_ipv6"].append(tmp_dict) + + if result["hwtacacs_server_cfg_ipv6"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_ipv6: + cfg["serverIpAddress"] = hwtacacs_server_ipv6.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_cfg_ipv6"]: + exist_cfg = dict() + if hwtacacs_server_ipv6: + exist_cfg["serverIpAddress"] = tmp.get("serverIpAddress").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Merge hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + + conf_str = CE_MERGE_HWTACACS_SERVER_CFG_IPV6 % ( + hwtacacs_template, hwtacacs_server_ipv6, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs server config ipv6 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_server_cfg_ipv6(self, **kwargs): + """ Delete hwtacacs server configure ipv6 """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_ipv6 = module.params["hwtacacs_server_ipv6"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + + conf_str = CE_DELETE_HWTACACS_SERVER_CFG_IPV6 % ( + hwtacacs_template, hwtacacs_server_ipv6, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs server config ipv6 failed.') + + cmds = [] + + cmd = "hwtacacs server template %s" % hwtacacs_template + cmds.append(cmd) + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server %s" % hwtacacs_server_ipv6 + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def get_hwtacacs_host_server_cfg(self, **kwargs): + """ Get hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = "true" if module.params[ + "hwtacacs_is_secondary_server"] is True else "false" + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = "true" if module.params[ + "hwtacacs_is_public_net"] is True else "false" + state = module.params["state"] + + result = dict() + result["hwtacacs_server_name_cfg"] = [] + need_cfg = False + + conf_str = CE_GET_HWTACACS_HOST_SERVER_CFG % hwtacacs_template + + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + hwtacacs_server_name_cfg = root.findall( + "hwtacacs/hwTacTempCfgs/hwTacTempCfg/hwTacHostSrvCfgs/hwTacHostSrvCfg") + if hwtacacs_server_name_cfg: + for tmp in hwtacacs_server_name_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["serverHostName", "serverType", "isSecondaryServer", "isPublicNet", "vpnName"]: + tmp_dict[site.tag] = site.text + + result["hwtacacs_server_name_cfg"].append(tmp_dict) + + if result["hwtacacs_server_name_cfg"]: + cfg = dict() + config_list = list() + + if hwtacacs_server_host_name: + cfg["serverHostName"] = hwtacacs_server_host_name.lower() + if hwtacacs_server_type: + cfg["serverType"] = hwtacacs_server_type.lower() + if hwtacacs_is_secondary_server: + cfg["isSecondaryServer"] = str(hwtacacs_is_secondary_server).lower() + if hwtacacs_is_public_net: + cfg["isPublicNet"] = str(hwtacacs_is_public_net).lower() + if hwtacacs_vpn_name: + cfg["vpnName"] = hwtacacs_vpn_name.lower() + + for tmp in result["hwtacacs_server_name_cfg"]: + exist_cfg = dict() + if hwtacacs_server_host_name: + exist_cfg["serverHostName"] = tmp.get("serverHostName").lower() + if hwtacacs_server_type: + exist_cfg["serverType"] = tmp.get("serverType").lower() + if hwtacacs_is_secondary_server: + exist_cfg["isSecondaryServer"] = tmp.get("isSecondaryServer").lower() + if hwtacacs_is_public_net: + exist_cfg["isPublicNet"] = tmp.get("isPublicNet").lower() + if hwtacacs_vpn_name: + exist_cfg["vpnName"] = tmp.get("vpnName").lower() + config_list.append(exist_cfg) + if cfg in config_list: + if state == "present": + need_cfg = False + else: + need_cfg = True + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + result["need_cfg"] = need_cfg + return result + + def merge_hwtacacs_host_server_cfg(self, **kwargs): + """ Merge hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_MERGE_HWTACACS_HOST_SERVER_CFG % ( + hwtacacs_template, hwtacacs_server_host_name, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge hwtacacs host server config failed.') + + cmds = [] + + if hwtacacs_server_type == "Authentication": + cmd = "hwtacacs server authentication host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "hwtacacs server authorization host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "hwtacacs server accounting host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "hwtacacs server host host-name %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + def delete_hwtacacs_host_server_cfg(self, **kwargs): + """ Delete hwtacacs host server configure """ + + module = kwargs["module"] + hwtacacs_template = module.params["hwtacacs_template"] + hwtacacs_server_host_name = module.params["hwtacacs_server_host_name"] + hwtacacs_server_type = module.params["hwtacacs_server_type"] + hwtacacs_is_secondary_server = module.params[ + "hwtacacs_is_secondary_server"] + hwtacacs_vpn_name = module.params["hwtacacs_vpn_name"] + hwtacacs_is_public_net = module.params["hwtacacs_is_public_net"] + + conf_str = CE_DELETE_HWTACACS_HOST_SERVER_CFG % ( + hwtacacs_template, hwtacacs_server_host_name, + hwtacacs_server_type, str(hwtacacs_is_secondary_server).lower(), + hwtacacs_vpn_name, str(hwtacacs_is_public_net).lower()) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete hwtacacs host server config failed.') + + cmds = [] + + if hwtacacs_server_type == "Authentication": + cmd = "undo hwtacacs server authentication host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Authorization": + cmd = "undo hwtacacs server authorization host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Accounting": + cmd = "undo hwtacacs server accounting host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + elif hwtacacs_server_type == "Common": + cmd = "undo hwtacacs server host %s" % hwtacacs_server_host_name + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + cmd += " vpn-instance %s" % hwtacacs_vpn_name + if hwtacacs_is_public_net: + cmd += " public-net" + if hwtacacs_is_secondary_server: + cmd += " secondary" + + cmds.append(cmd) + return cmds + + +def check_name(**kwargs): + """ Check invalid name """ + + module = kwargs["module"] + name = kwargs["name"] + invalid_char = kwargs["invalid_char"] + + for item in invalid_char: + if item in name: + module.fail_json( + msg='Error: Invalid char %s is in the name %s ' % (item, name)) + + +def check_module_argument(**kwargs): + """ Check module argument """ + + module = kwargs["module"] + + # local para + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + + # radius para + radius_group_name = module.params['radius_group_name'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_port = module.params['radius_server_port'] + radius_vpn_name = module.params['radius_vpn_name'] + radius_server_name = module.params['radius_server_name'] + + # hwtacacs para + hwtacacs_template = module.params['hwtacacs_template'] + hwtacacs_server_ip = module.params['hwtacacs_server_ip'] + hwtacacs_vpn_name = module.params['hwtacacs_vpn_name'] + hwtacacs_server_host_name = module.params['hwtacacs_server_host_name'] + + if local_user_name: + if len(local_user_name) > 253: + module.fail_json( + msg='Error: The local_user_name %s is large than 253.' % local_user_name) + check_name(module=module, name=local_user_name, + invalid_char=INVALID_USER_NAME_CHAR) + + if local_password and len(local_password) > 255: + module.fail_json( + msg='Error: The local_password %s is large than 255.' % local_password) + + if local_user_level: + if int(local_user_level) > 15 or int(local_user_level) < 0: + module.fail_json( + msg='Error: The local_user_level %s is out of [0 - 15].' % local_user_level) + + if local_ftp_dir: + if len(local_ftp_dir) > 255: + module.fail_json( + msg='Error: The local_ftp_dir %s is large than 255.' % local_ftp_dir) + + if local_user_group: + if len(local_user_group) > 32 or len(local_user_group) < 1: + module.fail_json( + msg='Error: The local_user_group %s is out of [1 - 32].' % local_user_group) + + if radius_group_name and len(radius_group_name) > 32: + module.fail_json( + msg='Error: The radius_group_name %s is large than 32.' % radius_group_name) + + if radius_server_ip and not check_ip_addr(radius_server_ip): + module.fail_json( + msg='Error: The radius_server_ip %s is invalid.' % radius_server_ip) + + if radius_server_port and not radius_server_port.isdigit(): + module.fail_json( + msg='Error: The radius_server_port %s is invalid.' % radius_server_port) + + if radius_vpn_name: + if len(radius_vpn_name) > 31: + module.fail_json( + msg='Error: The radius_vpn_name %s is large than 31.' % radius_vpn_name) + if ' ' in radius_vpn_name: + module.fail_json( + msg='Error: The radius_vpn_name %s include space.' % radius_vpn_name) + + if radius_server_name: + if len(radius_server_name) > 255: + module.fail_json( + msg='Error: The radius_server_name %s is large than 255.' % radius_server_name) + if ' ' in radius_server_name: + module.fail_json( + msg='Error: The radius_server_name %s include space.' % radius_server_name) + + if hwtacacs_template and len(hwtacacs_template) > 32: + module.fail_json( + msg='Error: The hwtacacs_template %s is large than 32.' % hwtacacs_template) + + if hwtacacs_server_ip and not check_ip_addr(hwtacacs_server_ip): + module.fail_json( + msg='Error: The hwtacacs_server_ip %s is invalid.' % hwtacacs_server_ip) + + if hwtacacs_vpn_name: + if len(hwtacacs_vpn_name) > 31: + module.fail_json( + msg='Error: The hwtacacs_vpn_name %s is large than 31.' % hwtacacs_vpn_name) + if ' ' in hwtacacs_vpn_name: + module.fail_json( + msg='Error: The hwtacacs_vpn_name %s include space.' % hwtacacs_vpn_name) + + if hwtacacs_server_host_name: + if len(hwtacacs_server_host_name) > 255: + module.fail_json( + msg='Error: The hwtacacs_server_host_name %s is large than 255.' % hwtacacs_server_host_name) + if ' ' in hwtacacs_server_host_name: + module.fail_json( + msg='Error: The hwtacacs_server_host_name %s include space.' % hwtacacs_server_host_name) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + local_user_name=dict(type='str'), + local_password=dict(type='str', no_log=True), + local_service_type=dict(type='str'), + local_ftp_dir=dict(type='str'), + local_user_level=dict(type='str'), + local_user_group=dict(type='str'), + radius_group_name=dict(type='str'), + radius_server_type=dict(choices=['Authentication', 'Accounting']), + radius_server_ip=dict(type='str'), + radius_server_ipv6=dict(type='str'), + radius_server_port=dict(type='str'), + radius_server_mode=dict( + choices=['Secondary-server', 'Primary-server']), + radius_vpn_name=dict(type='str'), + radius_server_name=dict(type='str'), + hwtacacs_template=dict(type='str'), + hwtacacs_server_ip=dict(type='str'), + hwtacacs_server_ipv6=dict(type='str'), + hwtacacs_server_type=dict( + choices=['Authentication', 'Authorization', 'Accounting', 'Common']), + hwtacacs_is_secondary_server=dict( + required=False, default=False, type='bool'), + hwtacacs_vpn_name=dict(type='str'), + hwtacacs_is_public_net=dict( + required=False, default=False, type='bool'), + hwtacacs_server_host_name=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + check_module_argument(module=module) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + # common para + state = module.params['state'] + + # local para + local_user_name = module.params['local_user_name'] + local_password = module.params['local_password'] + local_service_type = module.params['local_service_type'] + local_ftp_dir = module.params['local_ftp_dir'] + local_user_level = module.params['local_user_level'] + local_user_group = module.params['local_user_group'] + + # radius para + radius_group_name = module.params['radius_group_name'] + radius_server_type = module.params['radius_server_type'] + radius_server_ip = module.params['radius_server_ip'] + radius_server_ipv6 = module.params['radius_server_ipv6'] + radius_server_port = module.params['radius_server_port'] + radius_server_mode = module.params['radius_server_mode'] + radius_vpn_name = module.params['radius_vpn_name'] + radius_server_name = module.params['radius_server_name'] + + # hwtacacs para + hwtacacs_template = module.params['hwtacacs_template'] + hwtacacs_server_ip = module.params['hwtacacs_server_ip'] + hwtacacs_server_ipv6 = module.params['hwtacacs_server_ipv6'] + hwtacacs_server_type = module.params['hwtacacs_server_type'] + hwtacacs_is_secondary_server = module.params[ + 'hwtacacs_is_secondary_server'] + hwtacacs_vpn_name = module.params['hwtacacs_vpn_name'] + hwtacacs_is_public_net = module.params['hwtacacs_is_public_net'] + hwtacacs_server_host_name = module.params['hwtacacs_server_host_name'] + + ce_aaa_server_host = AaaServerHost() + + if not ce_aaa_server_host: + module.fail_json(msg='Error: Construct ce_aaa_server failed.') + + # get proposed + proposed["state"] = state + if local_user_name: + proposed["local_user_name"] = local_user_name + if local_password: + proposed["local_password"] = "******" + if local_service_type: + proposed["local_service_type"] = local_service_type + if local_ftp_dir: + proposed["local_ftp_dir"] = local_ftp_dir + if local_user_level: + proposed["local_user_level"] = local_user_level + if local_user_group: + proposed["local_user_group"] = local_user_group + if radius_group_name: + proposed["radius_group_name"] = radius_group_name + if radius_server_type: + proposed["radius_server_type"] = radius_server_type + if radius_server_ip: + proposed["radius_server_ip"] = radius_server_ip + if radius_server_ipv6: + proposed["radius_server_ipv6"] = radius_server_ipv6 + if radius_server_port: + proposed["radius_server_port"] = radius_server_port + if radius_server_mode: + proposed["radius_server_mode"] = radius_server_mode + if radius_vpn_name: + proposed["radius_vpn_name"] = radius_vpn_name + if radius_server_name: + proposed["radius_server_name"] = radius_server_name + if hwtacacs_template: + proposed["hwtacacs_template"] = hwtacacs_template + if hwtacacs_server_ip: + proposed["hwtacacs_server_ip"] = hwtacacs_server_ip + if hwtacacs_server_ipv6: + proposed["hwtacacs_server_ipv6"] = hwtacacs_server_ipv6 + if hwtacacs_server_type: + proposed["hwtacacs_server_type"] = hwtacacs_server_type + proposed["hwtacacs_is_secondary_server"] = hwtacacs_is_secondary_server + if hwtacacs_vpn_name: + proposed["hwtacacs_vpn_name"] = hwtacacs_vpn_name + proposed["hwtacacs_is_public_net"] = hwtacacs_is_public_net + if hwtacacs_server_host_name: + proposed["hwtacacs_server_host_name"] = hwtacacs_server_host_name + + if local_user_name: + + if state == "present" and not local_password: + module.fail_json( + msg='Error: Please input local_password when config local user.') + + local_user_result = ce_aaa_server_host.get_local_user_info( + module=module) + existing["local user name"] = local_user_result["local_user_info"] + + if state == "present": + # present local user + if local_user_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_local_user_info(module=module) + + changed = True + updates.append(cmd) + + else: + # absent local user + if local_user_result["need_cfg"]: + if not local_service_type and not local_ftp_dir and not local_user_level and not local_user_group: + cmd = ce_aaa_server_host.delete_local_user_info( + module=module) + else: + cmd = ce_aaa_server_host.merge_local_user_info( + module=module) + + changed = True + updates.append(cmd) + + local_user_result = ce_aaa_server_host.get_local_user_info( + module=module) + end_state["local user name"] = local_user_result["local_user_info"] + + if radius_group_name: + + if not radius_server_ip and not radius_server_ipv6 and not radius_server_name: + module.fail_json( + msg='Error: Please input radius_server_ip or radius_server_ipv6 or radius_server_name.') + + if radius_server_ip and radius_server_ipv6: + module.fail_json( + msg='Error: Please do not input radius_server_ip and radius_server_ipv6 at the same time.') + + if not radius_server_type or not radius_server_port or not radius_server_mode or not radius_vpn_name: + module.fail_json( + msg='Error: Please input radius_server_type radius_server_port radius_server_mode radius_vpn_name.') + + if radius_server_ip: + rds_server_ipv4_result = ce_aaa_server_host.get_radius_server_cfg_ipv4( + module=module) + if radius_server_ipv6: + rds_server_ipv6_result = ce_aaa_server_host.get_radius_server_cfg_ipv6( + module=module) + if radius_server_name: + rds_server_name_result = ce_aaa_server_host.get_radius_server_name( + module=module) + + if radius_server_ip and rds_server_ipv4_result["radius_server_ip_v4"]: + existing["radius server ipv4"] = rds_server_ipv4_result[ + "radius_server_ip_v4"] + if radius_server_ipv6 and rds_server_ipv6_result["radius_server_ip_v6"]: + existing["radius server ipv6"] = rds_server_ipv6_result[ + "radius_server_ip_v6"] + if radius_server_name and rds_server_name_result["radius_server_name_cfg"]: + existing["radius server name cfg"] = rds_server_name_result[ + "radius_server_name_cfg"] + + if state == "present": + if radius_server_ip and rds_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ipv6 and rds_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if radius_server_name and rds_server_name_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_radius_server_name( + module=module) + changed = True + updates.append(cmd) + else: + if radius_server_ip and rds_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ipv6 and rds_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if radius_server_name and rds_server_name_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_radius_server_name( + module=module) + changed = True + updates.append(cmd) + + if radius_server_ip: + rds_server_ipv4_result = ce_aaa_server_host.get_radius_server_cfg_ipv4( + module=module) + if radius_server_ipv6: + rds_server_ipv6_result = ce_aaa_server_host.get_radius_server_cfg_ipv6( + module=module) + if radius_server_name: + rds_server_name_result = ce_aaa_server_host.get_radius_server_name( + module=module) + + if radius_server_ip and rds_server_ipv4_result["radius_server_ip_v4"]: + end_state["radius server ipv4"] = rds_server_ipv4_result[ + "radius_server_ip_v4"] + if radius_server_ipv6 and rds_server_ipv6_result["radius_server_ip_v6"]: + end_state["radius server ipv6"] = rds_server_ipv6_result[ + "radius_server_ip_v6"] + if radius_server_name and rds_server_name_result["radius_server_name_cfg"]: + end_state["radius server name cfg"] = rds_server_name_result[ + "radius_server_name_cfg"] + + if hwtacacs_template: + + if not hwtacacs_server_ip and not hwtacacs_server_ipv6 and not hwtacacs_server_host_name: + module.fail_json( + msg='Error: Please input hwtacacs_server_ip or hwtacacs_server_ipv6 or hwtacacs_server_host_name.') + + if not hwtacacs_server_type or not hwtacacs_vpn_name: + module.fail_json( + msg='Error: Please input hwtacacs_server_type hwtacacs_vpn_name.') + + if hwtacacs_server_ip and hwtacacs_server_ipv6: + module.fail_json( + msg='Error: Please do not set hwtacacs_server_ip and hwtacacs_server_ipv6 at the same time.') + + if hwtacacs_vpn_name and hwtacacs_vpn_name != "_public_": + if hwtacacs_is_public_net: + module.fail_json( + msg='Error: Please do not set vpn and public net at the same time.') + + if hwtacacs_server_ip: + hwtacacs_server_ipv4_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv4( + module=module) + if hwtacacs_server_ipv6: + hwtacacs_server_ipv6_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv6( + module=module) + if hwtacacs_server_host_name: + hwtacacs_host_name_result = ce_aaa_server_host.get_hwtacacs_host_server_cfg( + module=module) + + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["hwtacacs_server_cfg_ipv4"]: + existing["hwtacacs server cfg ipv4"] = hwtacacs_server_ipv4_result[ + "hwtacacs_server_cfg_ipv4"] + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["hwtacacs_server_cfg_ipv6"]: + existing["hwtacacs server cfg ipv6"] = hwtacacs_server_ipv6_result[ + "hwtacacs_server_cfg_ipv6"] + if hwtacacs_server_host_name and hwtacacs_host_name_result["hwtacacs_server_name_cfg"]: + existing["hwtacacs server name cfg"] = hwtacacs_host_name_result[ + "hwtacacs_server_name_cfg"] + + if state == "present": + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_host_name and hwtacacs_host_name_result["need_cfg"]: + cmd = ce_aaa_server_host.merge_hwtacacs_host_server_cfg( + module=module) + changed = True + updates.append(cmd) + + else: + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_server_cfg_ipv4( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_server_cfg_ipv6( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_host_name and hwtacacs_host_name_result["need_cfg"]: + cmd = ce_aaa_server_host.delete_hwtacacs_host_server_cfg( + module=module) + changed = True + updates.append(cmd) + + if hwtacacs_server_ip: + hwtacacs_server_ipv4_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv4( + module=module) + if hwtacacs_server_ipv6: + hwtacacs_server_ipv6_result = ce_aaa_server_host.get_hwtacacs_server_cfg_ipv6( + module=module) + if hwtacacs_server_host_name: + hwtacacs_host_name_result = ce_aaa_server_host.get_hwtacacs_host_server_cfg( + module=module) + + if hwtacacs_server_ip and hwtacacs_server_ipv4_result["hwtacacs_server_cfg_ipv4"]: + end_state["hwtacacs server cfg ipv4"] = hwtacacs_server_ipv4_result[ + "hwtacacs_server_cfg_ipv4"] + if hwtacacs_server_ipv6 and hwtacacs_server_ipv6_result["hwtacacs_server_cfg_ipv6"]: + end_state["hwtacacs server cfg ipv6"] = hwtacacs_server_ipv6_result[ + "hwtacacs_server_cfg_ipv6"] + if hwtacacs_server_host_name and hwtacacs_host_name_result["hwtacacs_server_name_cfg"]: + end_state["hwtacacs server name cfg"] = hwtacacs_host_name_result[ + "hwtacacs_server_name_cfg"] + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl.py new file mode 100644 index 00000000..2e7b2c50 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl.py @@ -0,0 +1,1000 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl +short_description: Manages base ACL configuration on HUAWEI CloudEngine switches. +description: + - Manages base ACL configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent','delete_acl'] + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 2000 to 2999 indicates a basic ACL. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + acl_num: + description: + - ACL number. + The value is an integer ranging from 2000 to 2999. + acl_step: + description: + - ACL step. + The value is an integer ranging from 1 to 20. The default value is 5. + acl_description: + description: + - ACL description. + The value is a string of 1 to 127 characters. + rule_name: + description: + - Name of a basic ACL rule. + The value is a string of 1 to 32 characters. + The value is case-insensitive, and cannot contain spaces or begin with an underscore (_). + rule_id: + description: + - ID of a basic ACL rule in configuration mode. + The value is an integer ranging from 0 to 4294967294. + rule_action: + description: + - Matching mode of basic ACL rules. + choices: ['permit','deny'] + source_ip: + description: + - Source IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + src_mask: + description: + - Mask of a source IP address. + The value is an integer ranging from 1 to 32. + frag_type: + description: + - Type of packet fragmentation. + choices: ['fragment', 'clear_fragment'] + vrf_name: + description: + - VPN instance name. + The value is a string of 1 to 31 characters.The default value is _public_. + time_range: + description: + - Name of a time range in which an ACL rule takes effect. + The value is a string of 1 to 32 characters. + The value is case-insensitive, and cannot contain spaces. The name must start with an uppercase + or lowercase letter. In addition, the word "all" cannot be specified as a time range name. + rule_description: + description: + - Description about an ACL rule. + The value is a string of 1 to 127 characters. + log_flag: + description: + - Flag of logging matched data packets. + type: bool + default: 'no' +''' + +EXAMPLES = ''' + +- name: CloudEngine acl test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config ACL" + community.network.ce_acl: + state: present + acl_name: 2200 + provider: "{{ cli }}" + + - name: "Undo ACL" + community.network.ce_acl: + state: delete_acl + acl_name: 2200 + provider: "{{ cli }}" + + - name: "Config ACL base rule" + community.network.ce_acl: + state: present + acl_name: 2200 + rule_name: test_rule + rule_id: 111 + rule_action: permit + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + time_range: wdz_acl_time + provider: "{{ cli }}" + + - name: "undo ACL base rule" + community.network.ce_acl: + state: absent + acl_name: 2200 + rule_name: test_rule + rule_id: 111 + rule_action: permit + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + time_range: wdz_acl_time + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "test", "state": "delete_acl"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"aclNumOrName": "test", "aclType": "Basic"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: ["undo acl name test"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get acl +CE_GET_ACL_HEADER = """ + + + + + +""" +CE_GET_ACL_TAIL = """ + + + + +""" +# merge acl +CE_MERGE_ACL_HEADER = """ + + + + + %s +""" +CE_MERGE_ACL_TAIL = """ + + + + +""" +# delete acl +CE_DELETE_ACL_HEADER = """ + + + + + %s +""" +CE_DELETE_ACL_TAIL = """ + + + + +""" + +# get acl base rule +CE_GET_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + +""" +CE_GET_ACL_BASE_RULE_TAIL = """ + + + + + + +""" +# merge acl base rule +CE_MERGE_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_MERGE_ACL_BASE_RULE_TAIL = """ + + + + + + +""" +# delete acl base rule +CE_DELETE_ACL_BASE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_DELETE_ACL_BASE_RULE_TAIL = """ + + + + + + +""" + + +class BaseAcl(object): + """ Manages base acl configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] or None + self.acl_num = self.module.params['acl_num'] or None + self.acl_type = None + self.acl_step = self.module.params['acl_step'] or None + self.acl_description = self.module.params['acl_description'] or None + self.rule_name = self.module.params['rule_name'] or None + self.rule_id = self.module.params['rule_id'] or None + self.rule_action = self.module.params['rule_action'] or None + self.source_ip = self.module.params['source_ip'] or None + self.src_mask = self.module.params['src_mask'] or None + self.src_wild = None + self.frag_type = self.module.params['frag_type'] or None + self.vrf_name = self.module.params['vrf_name'] or None + self.time_range = self.module.params['time_range'] or None + self.rule_description = self.module.params['rule_description'] or None + self.log_flag = self.module.params['log_flag'] + + # cur config + self.cur_acl_cfg = dict() + self.cur_base_rule_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def get_wildcard_mask(self): + """ convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255 """ + + mask_int = ["255"] * 4 + value = int(self.src_mask) + + if value > 32: + self.module.fail_json(msg='Error: IPv4 ipaddress mask length is invalid.') + if value < 8: + mask_int[0] = str(int(~(0xFF << (8 - value % 8)) & 0xFF)) + if value >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (value % 16))) & 0xFF)) + if value >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (value % 24))) & 0xFF)) + if value >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (value % 32))) & 0xFF)) + if value == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + def check_acl_args(self): + """ Check acl invalid args """ + + need_cfg = False + find_flag = False + self.cur_acl_cfg["acl_info"] = [] + + if self.acl_name: + + if self.acl_name.isdigit(): + if int(self.acl_name) < 2000 or int(self.acl_name) > 2999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000-2999] for base ACL.') + + if self.acl_num: + self.module.fail_json( + msg='Error: The acl_name is digit, so should not input acl_num at the same time.') + else: + + self.acl_type = "Basic" + + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.state == "present": + if not self.acl_num and not self.acl_type and not self.rule_name: + self.module.fail_json( + msg='Error: Please input acl_num or acl_type when config ACL.') + + if self.acl_num: + if self.acl_num.isdigit(): + if int(self.acl_num) < 2000 or int(self.acl_num) > 2999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000-2999] for base ACL.') + else: + self.module.fail_json( + msg='Error: The acl_num is not digit.') + + if self.acl_step: + if self.acl_step.isdigit(): + if int(self.acl_step) < 1 or int(self.acl_step) > 20: + self.module.fail_json( + msg='Error: The value of acl_step is out of [1 - 20].') + else: + self.module.fail_json( + msg='Error: The acl_step is not digit.') + + if self.acl_description: + if len(self.acl_description) < 1 or len(self.acl_description) > 127: + self.module.fail_json( + msg='Error: The len of acl_description is out of [1 - 127].') + + conf_str = CE_GET_ACL_HEADER + + if self.acl_type: + conf_str += "" + if self.acl_num or self.acl_name.isdigit(): + conf_str += "" + if self.acl_step: + conf_str += "" + if self.acl_description: + conf_str += "" + + conf_str += CE_GET_ACL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # parse acl + acl_info = root.findall( + "acl/aclGroups/aclGroup") + if acl_info: + for tmp in acl_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclNumOrName", "aclType", "aclNumber", "aclStep", "aclDescription"]: + tmp_dict[site.tag] = site.text + + self.cur_acl_cfg["acl_info"].append(tmp_dict) + + if self.cur_acl_cfg["acl_info"]: + find_list = list() + for tmp in self.cur_acl_cfg["acl_info"]: + cur_cfg_dict = dict() + exist_cfg_dict = dict() + if self.acl_name: + if self.acl_name.isdigit() and tmp.get("aclNumber"): + cur_cfg_dict["aclNumber"] = self.acl_name + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + else: + cur_cfg_dict["aclNumOrName"] = self.acl_name + exist_cfg_dict["aclNumOrName"] = tmp.get("aclNumOrName") + if self.acl_type: + cur_cfg_dict["aclType"] = self.acl_type + exist_cfg_dict["aclType"] = tmp.get("aclType") + if self.acl_num: + cur_cfg_dict["aclNumber"] = self.acl_num + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + if self.acl_step: + cur_cfg_dict["aclStep"] = self.acl_step + exist_cfg_dict["aclStep"] = tmp.get("aclStep") + if self.acl_description: + cur_cfg_dict["aclDescription"] = self.acl_description + exist_cfg_dict["aclDescription"] = tmp.get("aclDescription") + + if cur_cfg_dict == exist_cfg_dict: + find_bool = True + else: + find_bool = False + find_list.append(find_bool) + + for mem in find_list: + if mem: + find_flag = True + break + else: + find_flag = False + + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "delete_acl": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_acl_cfg["need_cfg"] = need_cfg + + def check_base_rule_args(self): + """ Check base rule invalid args """ + + need_cfg = False + find_flag = False + self.cur_base_rule_cfg["base_rule_info"] = [] + + if self.acl_name: + + if self.state == "absent": + if not self.rule_name: + self.module.fail_json( + msg='Error: Please input rule_name when state is absent.') + + # config rule + if self.rule_name: + if len(self.rule_name) < 1 or len(self.rule_name) > 32: + self.module.fail_json( + msg='Error: The len of rule_name is out of [1 - 32].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + if self.rule_id: + if self.rule_id.isdigit(): + if int(self.rule_id) < 0 or int(self.rule_id) > 4294967294: + self.module.fail_json( + msg='Error: The value of rule_id is out of [0 - 4294967294].') + else: + self.module.fail_json( + msg='Error: The rule_id is not digit.') + + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The source_ip %s is invalid.' % self.source_ip) + if not self.src_mask: + self.module.fail_json( + msg='Error: Please input src_mask.') + + if self.src_mask: + if self.src_mask.isdigit(): + if int(self.src_mask) < 1 or int(self.src_mask) > 32: + self.module.fail_json( + msg='Error: The src_mask is out of [1 - 32].') + self.src_wild = self.get_wildcard_mask() + else: + self.module.fail_json( + msg='Error: The src_mask is not digit.') + + if self.vrf_name: + if len(self.vrf_name) < 1 or len(self.vrf_name) > 31: + self.module.fail_json( + msg='Error: The len of vrf_name is out of [1 - 31].') + + if self.time_range: + if len(self.time_range) < 1 or len(self.time_range) > 32: + self.module.fail_json( + msg='Error: The len of time_range is out of [1 - 32].') + + if self.rule_description: + if len(self.rule_description) < 1 or len(self.rule_description) > 127: + self.module.fail_json( + msg='Error: The len of rule_description is out of [1 - 127].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + conf_str = CE_GET_ACL_BASE_RULE_HEADER % self.acl_name + + if self.rule_id: + conf_str += "" + if self.rule_action: + conf_str += "" + if self.source_ip: + conf_str += "" + if self.src_wild: + conf_str += "" + if self.frag_type: + conf_str += "" + if self.vrf_name: + conf_str += "" + if self.time_range: + conf_str += "" + if self.rule_description: + conf_str += "" + conf_str += "" + + conf_str += CE_GET_ACL_BASE_RULE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse base rule + base_rule_info = root.findall( + "acl/aclGroups/aclGroup/aclRuleBas4s/aclRuleBas4") + if base_rule_info: + for tmp in base_rule_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclRuleName", "aclRuleID", "aclAction", "aclSourceIp", "aclSrcWild", + "aclFragType", "vrfName", "aclTimeName", "aclRuleDescription", + "aclLogFlag"]: + tmp_dict[site.tag] = site.text + + self.cur_base_rule_cfg[ + "base_rule_info"].append(tmp_dict) + + if self.cur_base_rule_cfg["base_rule_info"]: + for tmp in self.cur_base_rule_cfg["base_rule_info"]: + find_flag = True + + if self.rule_name and tmp.get("aclRuleName") != self.rule_name: + find_flag = False + if self.rule_id and tmp.get("aclRuleID") != self.rule_id: + find_flag = False + if self.rule_action and tmp.get("aclAction") != self.rule_action: + find_flag = False + if self.source_ip: + tmp_src_ip = self.source_ip.split(".") + tmp_src_wild = self.src_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclSourceIp"): + find_flag = False + if self.src_wild and tmp.get("aclSrcWild") != self.src_wild: + find_flag = False + frag_type = "clear_fragment" if tmp.get("aclFragType") is None else tmp.get("aclFragType") + if self.frag_type and frag_type != self.frag_type: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.time_range and tmp.get("aclTimeName") != self.time_range: + find_flag = False + if self.rule_description and tmp.get("aclRuleDescription") != self.rule_description: + find_flag = False + if tmp.get("aclLogFlag") != str(self.log_flag).lower(): + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_base_rule_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + if self.acl_num: + self.proposed["acl_num"] = self.acl_num + if self.acl_step: + self.proposed["acl_step"] = self.acl_step + if self.acl_description: + self.proposed["acl_description"] = self.acl_description + if self.rule_name: + self.proposed["rule_name"] = self.rule_name + if self.rule_id: + self.proposed["rule_id"] = self.rule_id + if self.rule_action: + self.proposed["rule_action"] = self.rule_action + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.src_mask: + self.proposed["src_mask"] = self.src_mask + if self.frag_type: + self.proposed["frag_type"] = self.frag_type + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.time_range: + self.proposed["time_range"] = self.time_range + if self.rule_description: + self.proposed["rule_description"] = self.rule_description + if self.log_flag: + self.proposed["log_flag"] = self.log_flag + + def get_existing(self): + """ Get existing state """ + + self.existing["acl_info"] = self.cur_acl_cfg["acl_info"] + self.existing["base_rule_info"] = self.cur_base_rule_cfg[ + "base_rule_info"] + + def get_end_state(self): + """ Get end state """ + + self.check_acl_args() + self.end_state["acl_info"] = self.cur_acl_cfg["acl_info"] + + self.check_base_rule_args() + self.end_state["base_rule_info"] = self.cur_base_rule_cfg[ + "base_rule_info"] + + def merge_acl(self): + """ Merge acl operation """ + + conf_str = CE_MERGE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_MERGE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl failed.') + + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + if self.acl_type and not self.acl_num: + cmd = "acl name %s %s" % (self.acl_name, self.acl_type.lower()) + elif self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + elif not self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + self.updates_cmd.append(cmd) + + if self.acl_description: + cmd = "description %s" % self.acl_description + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "step %s" % self.acl_step + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_acl(self): + """ Delete acl operation """ + + conf_str = CE_DELETE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_DELETE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl failed.') + + if self.acl_description: + cmd = "undo description" + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "undo step" + self.updates_cmd.append(cmd) + + if self.acl_name.isdigit(): + cmd = "undo acl number %s" % self.acl_name + else: + cmd = "undo acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_base_rule(self): + """ Merge base rule operation """ + + conf_str = CE_MERGE_ACL_BASE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_MERGE_ACL_BASE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl base rule failed.') + + if self.rule_action: + cmd = "rule" + if self.rule_id: + cmd += " %s" % self.rule_id + cmd += " %s" % self.rule_action + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + if self.rule_description: + cmd = "rule %s description %s" % ( + self.rule_id, self.rule_description) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_base_rule(self): + """ Delete base rule operation """ + + conf_str = CE_DELETE_ACL_BASE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_DELETE_ACL_BASE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl base rule failed.') + + if self.rule_description: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s description" % self.rule_id + self.updates_cmd.append(cmd) + + if self.rule_id: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s" % self.rule_id + self.updates_cmd.append(cmd) + elif self.rule_action: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule" + cmd += " %s" % self.rule_action + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_acl_args() + self.check_base_rule_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_acl_cfg["need_cfg"]: + self.merge_acl() + if self.cur_base_rule_cfg["need_cfg"]: + self.merge_base_rule() + + elif self.state == "absent": + if self.cur_base_rule_cfg["need_cfg"]: + self.delete_base_rule() + + elif self.state == "delete_acl": + if self.cur_acl_cfg["need_cfg"]: + self.delete_acl() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent', + 'delete_acl'], default='present'), + acl_name=dict(type='str', required=True), + acl_num=dict(type='str'), + acl_step=dict(type='str'), + acl_description=dict(type='str'), + rule_name=dict(type='str'), + rule_id=dict(type='str'), + rule_action=dict(choices=['permit', 'deny']), + source_ip=dict(type='str'), + src_mask=dict(type='str'), + frag_type=dict(choices=['fragment', 'clear_fragment']), + vrf_name=dict(type='str'), + time_range=dict(type='str'), + rule_description=dict(type='str'), + log_flag=dict(required=False, default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = BaseAcl(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_advance.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_advance.py new file mode 100644 index 00000000..ce3e420e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_advance.py @@ -0,0 +1,1746 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl_advance +short_description: Manages advanced ACL configuration on HUAWEI CloudEngine switches. +description: + - Manages advanced ACL configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + required: false + default: present + choices: ['present','absent','delete_acl'] + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 3000 to 3999 indicates a advance ACL. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + acl_num: + description: + - ACL number. + The value is an integer ranging from 3000 to 3999. + acl_step: + description: + - ACL step. + The value is an integer ranging from 1 to 20. The default value is 5. + acl_description: + description: + - ACL description. + The value is a string of 1 to 127 characters. + rule_name: + description: + - Name of a basic ACL rule. + The value is a string of 1 to 32 characters. + rule_id: + description: + - ID of a basic ACL rule in configuration mode. + The value is an integer ranging from 0 to 4294967294. + rule_action: + description: + - Matching mode of basic ACL rules. + choices: ['permit','deny'] + protocol: + description: + - Protocol type. + choices: ['ip', 'icmp', 'igmp', 'ipinip', 'tcp', 'udp', 'gre', 'ospf'] + source_ip: + description: + - Source IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + src_mask: + description: + - Source IP address mask. + The value is an integer ranging from 1 to 32. + src_pool_name: + description: + - Name of a source pool. + The value is a string of 1 to 32 characters. + dest_ip: + description: + - Destination IP address. + The value is a string of 0 to 255 characters.The default value is 0.0.0.0. + The value is in dotted decimal notation. + dest_mask: + description: + - Destination IP address mask. + The value is an integer ranging from 1 to 32. + dest_pool_name: + description: + - Name of a destination pool. + The value is a string of 1 to 32 characters. + src_port_op: + description: + - Range type of the source port. + choices: ['lt','eq', 'gt', 'range'] + src_port_begin: + description: + - Start port number of the source port. + The value is an integer ranging from 0 to 65535. + src_port_end: + description: + - End port number of the source port. + The value is an integer ranging from 0 to 65535. + src_port_pool_name: + description: + - Name of a source port pool. + The value is a string of 1 to 32 characters. + dest_port_op: + description: + - Range type of the destination port. + choices: ['lt','eq', 'gt', 'range'] + dest_port_begin: + description: + - Start port number of the destination port. + The value is an integer ranging from 0 to 65535. + dest_port_end: + description: + - End port number of the destination port. + The value is an integer ranging from 0 to 65535. + dest_port_pool_name: + description: + - Name of a destination port pool. + The value is a string of 1 to 32 characters. + frag_type: + description: + - Type of packet fragmentation. + choices: ['fragment', 'clear_fragment'] + precedence: + description: + - Data packets can be filtered based on the priority field. + The value is an integer ranging from 0 to 7. + tos: + description: + - ToS value on which data packet filtering is based. + The value is an integer ranging from 0 to 15. + dscp: + description: + - Differentiated Services Code Point. + The value is an integer ranging from 0 to 63. + icmp_name: + description: + - ICMP name. + choices: ['unconfiged', 'echo', 'echo-reply', 'fragmentneed-DFset', 'host-redirect', + 'host-tos-redirect', 'host-unreachable', 'information-reply', 'information-request', + 'net-redirect', 'net-tos-redirect', 'net-unreachable', 'parameter-problem', + 'port-unreachable', 'protocol-unreachable', 'reassembly-timeout', 'source-quench', + 'source-route-failed', 'timestamp-reply', 'timestamp-request', 'ttl-exceeded', + 'address-mask-reply', 'address-mask-request', 'custom'] + icmp_type: + description: + - ICMP type. This parameter is available only when the packet protocol is ICMP. + The value is an integer ranging from 0 to 255. + icmp_code: + description: + - ICMP message code. Data packets can be filtered based on the ICMP message code. + The value is an integer ranging from 0 to 255. + ttl_expired: + description: + - Whether TTL Expired is matched, with the TTL value of 1. + type: bool + default: 'no' + vrf_name: + description: + - VPN instance name. + The value is a string of 1 to 31 characters.The default value is _public_. + syn_flag: + description: + - TCP flag value. + The value is an integer ranging from 0 to 63. + tcp_flag_mask: + description: + - TCP flag mask value. + The value is an integer ranging from 0 to 63. + established: + description: + - Match established connections. + type: bool + default: 'no' + time_range: + description: + - Name of a time range in which an ACL rule takes effect. + rule_description: + description: + - Description about an ACL rule. + igmp_type: + description: + - Internet Group Management Protocol. + choices: ['host-query', 'mrouter-adver', 'mrouter-solic', 'mrouter-termi', 'mtrace-resp', 'mtrace-route', + 'v1host-report', 'v2host-report', 'v2leave-group', 'v3host-report'] + log_flag: + description: + - Flag of logging matched data packets. + type: bool + default: 'no' +''' + +EXAMPLES = ''' + +- name: CloudEngine advance acl test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config ACL" + community.network.ce_acl_advance: + state: present + acl_name: 3200 + provider: "{{ cli }}" + + - name: "Undo ACL" + community.network.ce_acl_advance: + state: delete_acl + acl_name: 3200 + provider: "{{ cli }}" + + - name: "Config ACL advance rule" + community.network.ce_acl_advance: + state: present + acl_name: test + rule_name: test_rule + rule_id: 111 + rule_action: permit + protocol: tcp + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + provider: "{{ cli }}" + + - name: "Undo ACL advance rule" + community.network.ce_acl_advance: + state: absent + acl_name: test + rule_name: test_rule + rule_id: 111 + rule_action: permit + protocol: tcp + source_ip: 10.10.10.10 + src_mask: 24 + frag_type: fragment + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "test", "state": "delete_acl"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"aclNumOrName": "test", "aclType": "Advance"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: ["undo acl name test"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +# get acl +CE_GET_ACL_HEADER = """ + + + + + +""" +CE_GET_ACL_TAIL = """ + + + + +""" +# merge acl +CE_MERGE_ACL_HEADER = """ + + + + + %s +""" +CE_MERGE_ACL_TAIL = """ + + + + +""" +# delete acl +CE_DELETE_ACL_HEADER = """ + + + + + %s +""" +CE_DELETE_ACL_TAIL = """ + + + + +""" + +# get acl advance rule +CE_GET_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + +""" +CE_GET_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" +# merge acl advance rule +CE_MERGE_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_MERGE_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" +# delete acl advance rule +CE_DELETE_ACL_ADVANCE_RULE_HEADER = """ + + + + + %s + + + %s +""" +CE_DELETE_ACL_ADVANCE_RULE_TAIL = """ + + + + + + +""" + + +PROTOCOL_NUM = {"ip": "0", + "icmp": "1", + "igmp": "2", + "ipinip": "4", + "tcp": "6", + "udp": "17", + "gre": "47", + "ospf": "89"} + +IGMP_TYPE_NUM = {"host-query": "17", + "mrouter-adver": "48", + "mrouter-solic": "49", + "mrouter-termi": "50", + "mtrace-resp": "30", + "mtrace-route": "31", + "v1host-report": "18", + "v2host-report": "22", + "v2leave-group": "23", + "v3host-report": "34"} + + +def get_wildcard_mask(mask): + """ convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255 """ + + mask_int = ["255"] * 4 + value = int(mask) + + if value > 32: + return None + if value < 8: + mask_int[0] = str(int(~(0xFF << (8 - value % 8)) & 0xFF)) + if value >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (value % 16))) & 0xFF)) + if value >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (value % 24))) & 0xFF)) + if value >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (value % 32))) & 0xFF)) + if value == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + +class AdvanceAcl(object): + """ Manages advance acl configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] or None + self.acl_num = self.module.params['acl_num'] or None + self.acl_type = None + self.acl_step = self.module.params['acl_step'] or None + self.acl_description = self.module.params['acl_description'] or None + self.rule_name = self.module.params['rule_name'] or None + self.rule_id = self.module.params['rule_id'] or None + self.rule_action = self.module.params['rule_action'] or None + self.protocol = self.module.params['protocol'] or None + self.protocol_num = None + self.source_ip = self.module.params['source_ip'] or None + self.src_mask = self.module.params['src_mask'] or None + self.src_wild = None + self.src_pool_name = self.module.params['src_pool_name'] or None + self.dest_ip = self.module.params['dest_ip'] or None + self.dest_mask = self.module.params['dest_mask'] or None + self.dest_wild = None + self.dest_pool_name = self.module.params['dest_pool_name'] or None + self.src_port_op = self.module.params['src_port_op'] or None + self.src_port_begin = self.module.params['src_port_begin'] or None + self.src_port_end = self.module.params['src_port_end'] or None + self.src_port_pool_name = self.module.params[ + 'src_port_pool_name'] or None + self.dest_port_op = self.module.params['dest_port_op'] or None + self.dest_port_begin = self.module.params['dest_port_begin'] or None + self.dest_port_end = self.module.params['dest_port_end'] or None + self.dest_port_pool_name = self.module.params[ + 'dest_port_pool_name'] or None + self.frag_type = self.module.params['frag_type'] or None + self.precedence = self.module.params['precedence'] or None + self.tos = self.module.params['tos'] or None + self.dscp = self.module.params['dscp'] or None + self.icmp_name = self.module.params['icmp_name'] or None + self.icmp_type = self.module.params['icmp_type'] or None + self.icmp_code = self.module.params['icmp_code'] or None + self.ttl_expired = self.module.params['ttl_expired'] + self.vrf_name = self.module.params['vrf_name'] or None + self.syn_flag = self.module.params['syn_flag'] or None + self.tcp_flag_mask = self.module.params['tcp_flag_mask'] or None + self.established = self.module.params['established'] + self.time_range = self.module.params['time_range'] or None + self.rule_description = self.module.params['rule_description'] or None + self.igmp_type = self.module.params['igmp_type'] or None + self.igmp_type_num = None + self.log_flag = self.module.params['log_flag'] + + self.precedence_name = dict() + self.precedence_name["0"] = "routine" + self.precedence_name["1"] = "priority" + self.precedence_name["2"] = "immediate" + self.precedence_name["3"] = "flash" + self.precedence_name["4"] = "flash-override" + self.precedence_name["5"] = "critical" + self.precedence_name["6"] = "internet" + self.precedence_name["7"] = "network" + + # cur config + self.cur_acl_cfg = dict() + self.cur_advance_rule_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def get_protocol_num(self): + """ Get protocol num by name """ + + if self.protocol: + self.protocol_num = PROTOCOL_NUM.get(self.protocol) + + def get_igmp_type_num(self): + """ Get igmp type num by type """ + + if self.igmp_type: + self.igmp_type_num = IGMP_TYPE_NUM.get(self.igmp_type) + + def check_acl_args(self): + """ Check acl invalid args """ + + need_cfg = False + find_flag = False + self.cur_acl_cfg["acl_info"] = [] + + if self.acl_name: + + if self.acl_name.isdigit(): + if int(self.acl_name) < 3000 or int(self.acl_name) > 3999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [3000-3999] for advance ACL.') + + if self.acl_num: + self.module.fail_json( + msg='Error: The acl_name is digit, so should not input acl_num at the same time.') + else: + + self.acl_type = "Advance" + + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.state == "present": + if not self.acl_num and not self.acl_type and not self.rule_name: + self.module.fail_json( + msg='Error: Please input acl_num or acl_type when config ACL.') + + if self.acl_num: + if self.acl_num.isdigit(): + if int(self.acl_num) < 3000 or int(self.acl_num) > 3999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [3000-3999] for advance ACL.') + else: + self.module.fail_json( + msg='Error: The acl_num is not digit.') + + if self.acl_step: + if self.acl_step.isdigit(): + if int(self.acl_step) < 1 or int(self.acl_step) > 20: + self.module.fail_json( + msg='Error: The value of acl_step is out of [1 - 20].') + else: + self.module.fail_json( + msg='Error: The acl_step is not digit.') + + if self.acl_description: + if len(self.acl_description) < 1 or len(self.acl_description) > 127: + self.module.fail_json( + msg='Error: The len of acl_description is out of [1 - 127].') + + conf_str = CE_GET_ACL_HEADER + + if self.acl_type: + conf_str += "" + if self.acl_num or self.acl_name.isdigit(): + conf_str += "" + if self.acl_step: + conf_str += "" + if self.acl_description: + conf_str += "" + + conf_str += CE_GET_ACL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse acl + acl_info = root.findall( + "acl/aclGroups/aclGroup") + if acl_info: + for tmp in acl_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclNumOrName", "aclType", "aclNumber", "aclStep", "aclDescription"]: + tmp_dict[site.tag] = site.text + + self.cur_acl_cfg["acl_info"].append(tmp_dict) + + if self.cur_acl_cfg["acl_info"]: + find_list = list() + for tmp in self.cur_acl_cfg["acl_info"]: + cur_cfg_dict = dict() + exist_cfg_dict = dict() + + if self.acl_name: + if self.acl_name.isdigit() and tmp.get("aclNumber"): + cur_cfg_dict["aclNumber"] = self.acl_name + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + else: + cur_cfg_dict["aclNumOrName"] = self.acl_name + exist_cfg_dict["aclNumOrName"] = tmp.get("aclNumOrName") + if self.acl_type: + cur_cfg_dict["aclType"] = self.acl_type + exist_cfg_dict["aclType"] = tmp.get("aclType") + if self.acl_num: + cur_cfg_dict["aclNumber"] = self.acl_num + exist_cfg_dict["aclNumber"] = tmp.get("aclNumber") + if self.acl_step: + cur_cfg_dict["aclStep"] = self.acl_step + exist_cfg_dict["aclStep"] = tmp.get("aclStep") + if self.acl_description: + cur_cfg_dict["aclDescription"] = self.acl_description + exist_cfg_dict["aclDescription"] = tmp.get("aclDescription") + + if cur_cfg_dict == exist_cfg_dict: + find_bool = True + else: + find_bool = False + find_list.append(find_bool) + for mem in find_list: + if mem: + find_flag = True + break + else: + find_flag = False + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "delete_acl": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_acl_cfg["need_cfg"] = need_cfg + + def check_advance_rule_args(self): + """ Check advance rule invalid args """ + + need_cfg = False + find_flag = False + self.cur_advance_rule_cfg["adv_rule_info"] = [] + + if self.acl_name: + + if self.state == "absent": + if not self.rule_name: + self.module.fail_json( + msg='Error: Please input rule_name when state is absent.') + + # config rule + if self.rule_name: + if len(self.rule_name) < 1 or len(self.rule_name) > 32: + self.module.fail_json( + msg='Error: The len of rule_name is out of [1 - 32].') + + if self.state != "delete_acl" and not self.rule_id: + self.module.fail_json( + msg='Error: Please input rule_id.') + + if self.rule_id: + if self.rule_id.isdigit(): + if int(self.rule_id) < 0 or int(self.rule_id) > 4294967294: + self.module.fail_json( + msg='Error: The value of rule_id is out of [0 - 4294967294].') + else: + self.module.fail_json( + msg='Error: The rule_id is not digit.') + + if self.rule_action and not self.protocol: + self.module.fail_json( + msg='Error: The rule_action and the protocol must input at the same time.') + + if not self.rule_action and self.protocol: + self.module.fail_json( + msg='Error: The rule_action and the protocol must input at the same time.') + + if self.protocol: + self.get_protocol_num() + + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The source_ip %s is invalid.' % self.source_ip) + if not self.src_mask: + self.module.fail_json( + msg='Error: Please input src_mask.') + + if self.src_mask: + if self.src_mask.isdigit(): + if int(self.src_mask) < 1 or int(self.src_mask) > 32: + self.module.fail_json( + msg='Error: The value of src_mask is out of [1 - 32].') + self.src_wild = get_wildcard_mask(self.src_mask) + else: + self.module.fail_json( + msg='Error: The src_mask is not digit.') + + if self.src_pool_name: + if len(self.src_pool_name) < 1 or len(self.src_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of src_pool_name is out of [1 - 32].') + + if self.dest_ip: + if not check_ip_addr(self.dest_ip): + self.module.fail_json( + msg='Error: The dest_ip %s is invalid.' % self.dest_ip) + if not self.dest_mask: + self.module.fail_json( + msg='Error: Please input dest_mask.') + + if self.dest_mask: + if self.dest_mask.isdigit(): + if int(self.dest_mask) < 1 or int(self.dest_mask) > 32: + self.module.fail_json( + msg='Error: The value of dest_mask is out of [1 - 32].') + self.dest_wild = get_wildcard_mask(self.dest_mask) + else: + self.module.fail_json( + msg='Error: The dest_mask is not digit.') + + if self.dest_pool_name: + if len(self.dest_pool_name) < 1 or len(self.dest_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of dest_pool_name is out of [1 - 32].') + + if self.src_port_op: + if self.src_port_op == "lt": + if not self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_end must input.') + if self.src_port_begin: + self.module.fail_json( + msg='Error: The src_port_begin should not input.') + if self.src_port_op == "eq" or self.src_port_op == "gt": + if not self.src_port_begin: + self.module.fail_json( + msg='Error: The src_port_begin must input.') + if self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_end should not input.') + if self.src_port_op == "range": + if not self.src_port_begin or not self.src_port_end: + self.module.fail_json( + msg='Error: The src_port_begin and src_port_end must input.') + + if self.src_port_begin: + if self.src_port_begin.isdigit(): + if int(self.src_port_begin) < 0 or int(self.src_port_begin) > 65535: + self.module.fail_json( + msg='Error: The value of src_port_begin is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The src_port_begin is not digit.') + + if self.src_port_end: + if self.src_port_end.isdigit(): + if int(self.src_port_end) < 0 or int(self.src_port_end) > 65535: + self.module.fail_json( + msg='Error: The value of src_port_end is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The src_port_end is not digit.') + + if self.src_port_pool_name: + if len(self.src_port_pool_name) < 1 or len(self.src_port_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of src_port_pool_name is out of [1 - 32].') + + if self.dest_port_op: + if self.dest_port_op == "lt": + if not self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_end must input.') + if self.dest_port_begin: + self.module.fail_json( + msg='Error: The dest_port_begin should not input.') + if self.dest_port_op == "eq" or self.dest_port_op == "gt": + if not self.dest_port_begin: + self.module.fail_json( + msg='Error: The dest_port_begin must input.') + if self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_end should not input.') + if self.dest_port_op == "range": + if not self.dest_port_begin or not self.dest_port_end: + self.module.fail_json( + msg='Error: The dest_port_begin and dest_port_end must input.') + + if self.dest_port_begin: + if self.dest_port_begin.isdigit(): + if int(self.dest_port_begin) < 0 or int(self.dest_port_begin) > 65535: + self.module.fail_json( + msg='Error: The value of dest_port_begin is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The dest_port_begin is not digit.') + + if self.dest_port_end: + if self.dest_port_end.isdigit(): + if int(self.dest_port_end) < 0 or int(self.dest_port_end) > 65535: + self.module.fail_json( + msg='Error: The value of dest_port_end is out of [0 - 65535].') + else: + self.module.fail_json( + msg='Error: The dest_port_end is not digit.') + + if self.dest_port_pool_name: + if len(self.dest_port_pool_name) < 1 or len(self.dest_port_pool_name) > 32: + self.module.fail_json( + msg='Error: The len of dest_port_pool_name is out of [1 - 32].') + + if self.precedence: + if self.precedence.isdigit(): + if int(self.precedence) < 0 or int(self.precedence) > 7: + self.module.fail_json( + msg='Error: The value of precedence is out of [0 - 7].') + else: + self.module.fail_json( + msg='Error: The precedence is not digit.') + + if self.tos: + if self.tos.isdigit(): + if int(self.tos) < 0 or int(self.tos) > 15: + self.module.fail_json( + msg='Error: The value of tos is out of [0 - 15].') + else: + self.module.fail_json( + msg='Error: The tos is not digit.') + + if self.dscp: + if self.dscp.isdigit(): + if int(self.dscp) < 0 or int(self.dscp) > 63: + self.module.fail_json( + msg='Error: The value of dscp is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The dscp is not digit.') + + if self.icmp_type: + if self.icmp_type.isdigit(): + if int(self.icmp_type) < 0 or int(self.icmp_type) > 255: + self.module.fail_json( + msg='Error: The value of icmp_type is out of [0 - 255].') + else: + self.module.fail_json( + msg='Error: The icmp_type is not digit.') + + if self.icmp_code: + if self.icmp_code.isdigit(): + if int(self.icmp_code) < 0 or int(self.icmp_code) > 255: + self.module.fail_json( + msg='Error: The value of icmp_code is out of [0 - 255].') + else: + self.module.fail_json( + msg='Error: The icmp_code is not digit.') + + if self.vrf_name: + if len(self.vrf_name) < 1 or len(self.vrf_name) > 31: + self.module.fail_json( + msg='Error: The len of vrf_name is out of [1 - 31].') + + if self.syn_flag: + if self.syn_flag.isdigit(): + if int(self.syn_flag) < 0 or int(self.syn_flag) > 63: + self.module.fail_json( + msg='Error: The value of syn_flag is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The syn_flag is not digit.') + + if self.tcp_flag_mask: + if self.tcp_flag_mask.isdigit(): + if int(self.tcp_flag_mask) < 0 or int(self.tcp_flag_mask) > 63: + self.module.fail_json( + msg='Error: The value of tcp_flag_mask is out of [0 - 63].') + else: + self.module.fail_json( + msg='Error: The tcp_flag_mask is not digit.') + + if self.time_range: + if len(self.time_range) < 1 or len(self.time_range) > 32: + self.module.fail_json( + msg='Error: The len of time_range is out of [1 - 32].') + + if self.rule_description: + if len(self.rule_description) < 1 or len(self.rule_description) > 127: + self.module.fail_json( + msg='Error: The len of rule_description is out of [1 - 127].') + + if self.igmp_type: + self.get_igmp_type_num() + + conf_str = CE_GET_ACL_ADVANCE_RULE_HEADER % self.acl_name + + if self.rule_id: + conf_str += "" + if self.rule_action: + conf_str += "" + if self.protocol: + conf_str += "" + if self.source_ip: + conf_str += "" + if self.src_wild: + conf_str += "" + if self.src_pool_name: + conf_str += "" + if self.dest_ip: + conf_str += "" + if self.dest_wild: + conf_str += "" + if self.dest_pool_name: + conf_str += "" + if self.src_port_op: + conf_str += "" + if self.src_port_begin: + conf_str += "" + if self.src_port_end: + conf_str += "" + if self.src_port_pool_name: + conf_str += "" + if self.dest_port_op: + conf_str += "" + if self.dest_port_begin: + conf_str += "" + if self.dest_port_end: + conf_str += "" + if self.dest_port_pool_name: + conf_str += "" + if self.frag_type: + conf_str += "" + if self.precedence: + conf_str += "" + if self.tos: + conf_str += "" + if self.dscp: + conf_str += "" + if self.icmp_name: + conf_str += "" + if self.icmp_type: + conf_str += "" + if self.icmp_code: + conf_str += "" + conf_str += "" + if self.vrf_name: + conf_str += "" + if self.syn_flag: + conf_str += "" + if self.tcp_flag_mask: + conf_str += "" + conf_str += "" + if self.time_range: + conf_str += "" + if self.rule_description: + conf_str += "" + if self.igmp_type: + conf_str += "" + conf_str += "" + + conf_str += CE_GET_ACL_ADVANCE_RULE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # parse advance rule + adv_rule_info = root.findall( + "acl/aclGroups/aclGroup/aclRuleAdv4s/aclRuleAdv4") + if adv_rule_info: + for tmp in adv_rule_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["aclRuleName", "aclRuleID", "aclAction", "aclProtocol", "aclSourceIp", + "aclSrcWild", "aclSPoolName", "aclDestIp", "aclDestWild", + "aclDPoolName", "aclSrcPortOp", "aclSrcPortBegin", "aclSrcPortEnd", + "aclSPortPoolName", "aclDestPortOp", "aclDestPortB", "aclDestPortE", + "aclDPortPoolName", "aclFragType", "aclPrecedence", "aclTos", + "aclDscp", "aclIcmpName", "aclIcmpType", "aclIcmpCode", "aclTtlExpired", + "vrfName", "aclSynFlag", "aclTcpFlagMask", "aclEstablished", + "aclTimeName", "aclRuleDescription", "aclIgmpType", "aclLogFlag"]: + tmp_dict[site.tag] = site.text + + self.cur_advance_rule_cfg[ + "adv_rule_info"].append(tmp_dict) + + if self.cur_advance_rule_cfg["adv_rule_info"]: + for tmp in self.cur_advance_rule_cfg["adv_rule_info"]: + find_flag = True + + if self.rule_name and tmp.get("aclRuleName") != self.rule_name: + find_flag = False + if self.rule_id and tmp.get("aclRuleID") != self.rule_id: + find_flag = False + if self.rule_action and tmp.get("aclAction") != self.rule_action: + find_flag = False + if self.protocol and tmp.get("aclProtocol") != self.protocol_num: + find_flag = False + if self.source_ip: + tmp_src_ip = self.source_ip.split(".") + tmp_src_wild = self.src_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclSourceIp"): + find_flag = False + if self.src_wild and tmp.get("aclSrcWild") != self.src_wild: + find_flag = False + if self.src_pool_name and tmp.get("aclSPoolName") != self.src_pool_name: + find_flag = False + if self.dest_ip: + tmp_src_ip = self.dest_ip.split(".") + tmp_src_wild = self.dest_wild.split(".") + tmp_addr_item = [] + for idx in range(4): + item1 = 255 - int(tmp_src_wild[idx]) + item2 = item1 & int(tmp_src_ip[idx]) + tmp_addr_item.append(item2) + tmp_addr = "%s.%s.%s.%s" % (tmp_addr_item[0], tmp_addr_item[1], + tmp_addr_item[2], tmp_addr_item[3]) + if tmp_addr != tmp.get("aclDestIp"): + find_flag = False + if self.dest_wild and tmp.get("aclDestWild") != self.dest_wild: + find_flag = False + if self.dest_pool_name and tmp.get("aclDPoolName") != self.dest_pool_name: + find_flag = False + if self.src_port_op and tmp.get("aclSrcPortOp") != self.src_port_op: + find_flag = False + if self.src_port_begin and tmp.get("aclSrcPortBegin") != self.src_port_begin: + find_flag = False + if self.src_port_end and tmp.get("aclSrcPortEnd") != self.src_port_end: + find_flag = False + if self.src_port_pool_name and tmp.get("aclSPortPoolName") != self.src_port_pool_name: + find_flag = False + if self.dest_port_op and tmp.get("aclDestPortOp") != self.dest_port_op: + find_flag = False + if self.dest_port_begin and tmp.get("aclDestPortB") != self.dest_port_begin: + find_flag = False + if self.dest_port_end and tmp.get("aclDestPortE") != self.dest_port_end: + find_flag = False + if self.dest_port_pool_name and tmp.get("aclDPortPoolName") != self.dest_port_pool_name: + find_flag = False + frag_type = "clear_fragment" if tmp.get("aclFragType") is None else tmp.get("aclFragType") + if self.frag_type and frag_type != self.frag_type: + find_flag = False + if self.precedence and tmp.get("aclPrecedence") != self.precedence: + find_flag = False + if self.tos and tmp.get("aclTos") != self.tos: + find_flag = False + if self.dscp and tmp.get("aclDscp") != self.dscp: + find_flag = False + if self.icmp_name and tmp.get("aclIcmpName") != self.icmp_name: + find_flag = False + if self.icmp_type and tmp.get("aclIcmpType") != self.icmp_type: + find_flag = False + if self.icmp_code and tmp.get("aclIcmpCode") != self.icmp_code: + find_flag = False + if tmp.get("aclTtlExpired").lower() != str(self.ttl_expired).lower(): + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.syn_flag and tmp.get("aclSynFlag") != self.syn_flag: + find_flag = False + if self.tcp_flag_mask and tmp.get("aclTcpFlagMask") != self.tcp_flag_mask: + find_flag = False + if self.protocol == "tcp" and \ + tmp.get("aclEstablished").lower() != str(self.established).lower(): + find_flag = False + if self.time_range and tmp.get("aclTimeName") != self.time_range: + find_flag = False + if self.rule_description and tmp.get("aclRuleDescription") != self.rule_description: + find_flag = False + if self.igmp_type and tmp.get("aclIgmpType") != self.igmp_type_num: + find_flag = False + if tmp.get("aclLogFlag").lower() != str(self.log_flag).lower(): + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + else: + need_cfg = False + + self.cur_advance_rule_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + if self.acl_num: + self.proposed["acl_num"] = self.acl_num + if self.acl_step: + self.proposed["acl_step"] = self.acl_step + if self.acl_description: + self.proposed["acl_description"] = self.acl_description + if self.rule_name: + self.proposed["rule_name"] = self.rule_name + if self.rule_id: + self.proposed["rule_id"] = self.rule_id + if self.rule_action: + self.proposed["rule_action"] = self.rule_action + if self.protocol: + self.proposed["protocol"] = self.protocol + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.src_mask: + self.proposed["src_mask"] = self.src_mask + if self.src_pool_name: + self.proposed["src_pool_name"] = self.src_pool_name + if self.dest_ip: + self.proposed["dest_ip"] = self.dest_ip + if self.dest_mask: + self.proposed["dest_mask"] = self.dest_mask + if self.dest_pool_name: + self.proposed["dest_pool_name"] = self.dest_pool_name + if self.src_port_op: + self.proposed["src_port_op"] = self.src_port_op + if self.src_port_begin: + self.proposed["src_port_begin"] = self.src_port_begin + if self.src_port_end: + self.proposed["src_port_end"] = self.src_port_end + if self.src_port_pool_name: + self.proposed["src_port_pool_name"] = self.src_port_pool_name + if self.dest_port_op: + self.proposed["dest_port_op"] = self.dest_port_op + if self.dest_port_begin: + self.proposed["dest_port_begin"] = self.dest_port_begin + if self.dest_port_end: + self.proposed["dest_port_end"] = self.dest_port_end + if self.dest_port_pool_name: + self.proposed["dest_port_pool_name"] = self.dest_port_pool_name + if self.frag_type: + self.proposed["frag_type"] = self.frag_type + if self.precedence: + self.proposed["precedence"] = self.precedence + if self.tos: + self.proposed["tos"] = self.tos + if self.dscp: + self.proposed["dscp"] = self.dscp + if self.icmp_name: + self.proposed["icmp_name"] = self.icmp_name + if self.icmp_type: + self.proposed["icmp_type"] = self.icmp_type + if self.icmp_code: + self.proposed["icmp_code"] = self.icmp_code + if self.ttl_expired: + self.proposed["ttl_expired"] = self.ttl_expired + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.syn_flag: + self.proposed["syn_flag"] = self.syn_flag + if self.tcp_flag_mask: + self.proposed["tcp_flag_mask"] = self.tcp_flag_mask + self.proposed["established"] = self.established + if self.time_range: + self.proposed["time_range"] = self.time_range + if self.rule_description: + self.proposed["rule_description"] = self.rule_description + if self.igmp_type: + self.proposed["igmp_type"] = self.igmp_type + self.proposed["log_flag"] = self.log_flag + + def get_existing(self): + """ Get existing state """ + + self.existing["acl_info"] = self.cur_acl_cfg["acl_info"] + self.existing["adv_rule_info"] = self.cur_advance_rule_cfg[ + "adv_rule_info"] + + def get_end_state(self): + """ Get end state """ + + self.check_acl_args() + self.end_state["acl_info"] = self.cur_acl_cfg["acl_info"] + + self.check_advance_rule_args() + self.end_state["adv_rule_info"] = self.cur_advance_rule_cfg[ + "adv_rule_info"] + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def merge_acl(self): + """ Merge acl operation """ + + conf_str = CE_MERGE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_MERGE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl failed.') + + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + if self.acl_type and not self.acl_num: + cmd = "acl name %s %s" % (self.acl_name, self.acl_type.lower()) + elif self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + elif not self.acl_type and self.acl_num: + cmd = "acl name %s number %s" % (self.acl_name, self.acl_num) + self.updates_cmd.append(cmd) + + if self.acl_description: + cmd = "description %s" % self.acl_description + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "step %s" % self.acl_step + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_acl(self): + """ Delete acl operation """ + + conf_str = CE_DELETE_ACL_HEADER % self.acl_name + + if self.acl_type: + conf_str += "%s" % self.acl_type + if self.acl_num: + conf_str += "%s" % self.acl_num + if self.acl_step: + conf_str += "%s" % self.acl_step + if self.acl_description: + conf_str += "%s" % self.acl_description + + conf_str += CE_DELETE_ACL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl failed.') + + if self.acl_description: + cmd = "undo description" + self.updates_cmd.append(cmd) + + if self.acl_step: + cmd = "undo step" + self.updates_cmd.append(cmd) + + if self.acl_name.isdigit(): + cmd = "undo acl number %s" % self.acl_name + else: + cmd = "undo acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_adv_rule(self): + """ Merge advance rule operation """ + + conf_str = CE_MERGE_ACL_ADVANCE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.protocol: + conf_str += "%s" % self.protocol_num + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.src_pool_name: + conf_str += "%s" % self.src_pool_name + if self.dest_ip: + conf_str += "%s" % self.dest_ip + if self.dest_wild: + conf_str += "%s" % self.dest_wild + if self.dest_pool_name: + conf_str += "%s" % self.dest_pool_name + if self.src_port_op: + conf_str += "%s" % self.src_port_op + if self.src_port_begin: + conf_str += "%s" % self.src_port_begin + if self.src_port_end: + conf_str += "%s" % self.src_port_end + if self.src_port_pool_name: + conf_str += "%s" % self.src_port_pool_name + if self.dest_port_op: + conf_str += "%s" % self.dest_port_op + if self.dest_port_begin: + conf_str += "%s" % self.dest_port_begin + if self.dest_port_end: + conf_str += "%s" % self.dest_port_end + if self.dest_port_pool_name: + conf_str += "%s" % self.dest_port_pool_name + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.precedence: + conf_str += "%s" % self.precedence + if self.tos: + conf_str += "%s" % self.tos + if self.dscp: + conf_str += "%s" % self.dscp + if self.icmp_name: + conf_str += "%s" % self.icmp_name + if self.icmp_type: + conf_str += "%s" % self.icmp_type + if self.icmp_code: + conf_str += "%s" % self.icmp_code + conf_str += "%s" % str(self.ttl_expired).lower() + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.syn_flag: + conf_str += "%s" % self.syn_flag + if self.tcp_flag_mask: + conf_str += "%s" % self.tcp_flag_mask + if self.protocol == "tcp": + conf_str += "%s" % str(self.established).lower() + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + if self.igmp_type: + conf_str += "%s" % self.igmp_type_num + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_MERGE_ACL_ADVANCE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge acl base rule failed.') + + if self.rule_action and self.protocol: + cmd = "rule" + if self.rule_id: + cmd += " %s" % self.rule_id + cmd += " %s" % self.rule_action + cmd += " %s" % self.protocol + if self.dscp: + cmd += " dscp %s" % self.dscp + if self.tos: + cmd += " tos %s" % self.tos + if self.source_ip and self.src_wild: + cmd += " source %s %s" % (self.source_ip, self.src_wild) + if self.src_pool_name: + cmd += " source-pool %s" % self.src_pool_name + if self.src_port_op: + cmd += " source-port" + if self.src_port_op == "lt": + cmd += " lt %s" % self.src_port_end + elif self.src_port_op == "eq": + cmd += " eq %s" % self.src_port_begin + elif self.src_port_op == "gt": + cmd += " gt %s" % self.src_port_begin + elif self.src_port_op == "range": + cmd += " range %s %s" % (self.src_port_begin, + self.src_port_end) + if self.src_port_pool_name: + cmd += " source-port-pool %s" % self.src_port_pool_name + if self.dest_ip and self.dest_wild: + cmd += " destination %s %s" % (self.dest_ip, self.dest_wild) + if self.dest_pool_name: + cmd += " destination-pool %s" % self.dest_pool_name + if self.dest_port_op: + cmd += " destination-port" + if self.dest_port_op == "lt": + cmd += " lt %s" % self.dest_port_end + elif self.dest_port_op == "eq": + cmd += " eq %s" % self.dest_port_begin + elif self.dest_port_op == "gt": + cmd += " gt %s" % self.dest_port_begin + elif self.dest_port_op == "range": + cmd += " range %s %s" % (self.dest_port_begin, + self.dest_port_end) + if self.dest_port_pool_name: + cmd += " destination-port-pool %s" % self.dest_port_pool_name + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.precedence: + cmd += " precedence %s" % self.precedence_name[self.precedence] + + if self.protocol == "icmp": + if self.icmp_name: + cmd += " icmp-type %s" % self.icmp_name + elif self.icmp_type and self.icmp_code: + cmd += " icmp-type %s %s" % (self.icmp_type, self.icmp_code) + elif self.icmp_type: + cmd += " icmp-type %s" % self.icmp_type + if self.protocol == "tcp": + if self.syn_flag: + cmd += " tcp-flag %s" % self.syn_flag + if self.tcp_flag_mask: + cmd += " mask %s" % self.tcp_flag_mask + if self.established: + cmd += " established" + if self.protocol == "igmp": + if self.igmp_type: + cmd += " igmp-type %s" % self.igmp_type + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.ttl_expired: + cmd += " ttl-expired" + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + if self.rule_description: + cmd = "rule %s description %s" % ( + self.rule_id, self.rule_description) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_adv_rule(self): + """ Delete advance rule operation """ + + conf_str = CE_DELETE_ACL_ADVANCE_RULE_HEADER % ( + self.acl_name, self.rule_name) + + if self.rule_id: + conf_str += "%s" % self.rule_id + if self.rule_action: + conf_str += "%s" % self.rule_action + if self.protocol: + conf_str += "%s" % self.protocol_num + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.src_wild: + conf_str += "%s" % self.src_wild + if self.src_pool_name: + conf_str += "%s" % self.src_pool_name + if self.dest_ip: + conf_str += "%s" % self.dest_ip + if self.dest_wild: + conf_str += "%s" % self.dest_wild + if self.dest_pool_name: + conf_str += "%s" % self.dest_pool_name + if self.src_port_op: + conf_str += "%s" % self.src_port_op + if self.src_port_begin: + conf_str += "%s" % self.src_port_begin + if self.src_port_end: + conf_str += "%s" % self.src_port_end + if self.src_port_pool_name: + conf_str += "%s" % self.src_port_pool_name + if self.dest_port_op: + conf_str += "%s" % self.dest_port_op + if self.dest_port_begin: + conf_str += "%s" % self.dest_port_begin + if self.dest_port_end: + conf_str += "%s" % self.dest_port_end + if self.dest_port_pool_name: + conf_str += "%s" % self.dest_port_pool_name + if self.frag_type: + conf_str += "%s" % self.frag_type + if self.precedence: + conf_str += "%s" % self.precedence + if self.tos: + conf_str += "%s" % self.tos + if self.dscp: + conf_str += "%s" % self.dscp + if self.icmp_name: + conf_str += "%s" % self.icmp_name + if self.icmp_type: + conf_str += "%s" % self.icmp_type + if self.icmp_code: + conf_str += "%s" % self.icmp_code + conf_str += "%s" % str(self.ttl_expired).lower() + if self.vrf_name: + conf_str += "%s" % self.vrf_name + if self.syn_flag: + conf_str += "%s" % self.syn_flag + if self.tcp_flag_mask: + conf_str += "%s" % self.tcp_flag_mask + if self.protocol == "tcp": + conf_str += "%s" % str(self.established).lower() + if self.time_range: + conf_str += "%s" % self.time_range + if self.rule_description: + conf_str += "%s" % self.rule_description + if self.igmp_type: + conf_str += "%s" % self.igmp_type + conf_str += "%s" % str(self.log_flag).lower() + + conf_str += CE_DELETE_ACL_ADVANCE_RULE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete acl base rule failed.') + + if self.rule_description: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s description" % self.rule_id + self.updates_cmd.append(cmd) + + if self.rule_id: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule %s" % self.rule_id + self.updates_cmd.append(cmd) + elif self.rule_action and self.protocol: + if self.acl_name.isdigit(): + cmd = "acl number %s" % self.acl_name + else: + cmd = "acl name %s" % self.acl_name + self.updates_cmd.append(cmd) + + cmd = "undo rule" + cmd += " %s" % self.rule_action + cmd += " %s" % self.protocol + if self.dscp: + cmd += " dscp %s" % self.dscp + if self.tos: + cmd += " tos %s" % self.tos + if self.source_ip and self.src_mask: + cmd += " source %s %s" % (self.source_ip, self.src_mask) + if self.src_pool_name: + cmd += " source-pool %s" % self.src_pool_name + if self.src_port_op: + cmd += " source-port" + if self.src_port_op == "lt": + cmd += " lt %s" % self.src_port_end + elif self.src_port_op == "eq": + cmd += " eq %s" % self.src_port_begin + elif self.src_port_op == "gt": + cmd += " gt %s" % self.src_port_begin + elif self.src_port_op == "range": + cmd += " range %s %s" % (self.src_port_begin, + self.src_port_end) + if self.src_port_pool_name: + cmd += " source-port-pool %s" % self.src_port_pool_name + if self.dest_ip and self.dest_mask: + cmd += " destination %s %s" % (self.dest_ip, self.dest_mask) + if self.dest_pool_name: + cmd += " destination-pool %s" % self.dest_pool_name + if self.dest_port_op: + cmd += " destination-port" + if self.dest_port_op == "lt": + cmd += " lt %s" % self.dest_port_end + elif self.dest_port_op == "eq": + cmd += " eq %s" % self.dest_port_begin + elif self.dest_port_op == "gt": + cmd += " gt %s" % self.dest_port_begin + elif self.dest_port_op == "range": + cmd += " range %s %s" % (self.dest_port_begin, + self.dest_port_end) + if self.dest_port_pool_name: + cmd += " destination-port-pool %s" % self.dest_port_pool_name + if self.frag_type == "fragment": + cmd += " fragment-type fragment" + if self.precedence: + cmd += " precedence %s" % self.precedence_name[self.precedence] + if self.time_range: + cmd += " time-range %s" % self.time_range + if self.vrf_name: + cmd += " vpn-instance %s" % self.vrf_name + if self.ttl_expired: + cmd += " ttl-expired" + if self.log_flag: + cmd += " logging" + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_acl_args() + self.check_advance_rule_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_acl_cfg["need_cfg"]: + self.merge_acl() + if self.cur_advance_rule_cfg["need_cfg"]: + self.merge_adv_rule() + + elif self.state == "absent": + if self.cur_advance_rule_cfg["need_cfg"]: + self.delete_adv_rule() + + elif self.state == "delete_acl": + if self.cur_acl_cfg["need_cfg"]: + self.delete_acl() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent', + 'delete_acl'], default='present'), + acl_name=dict(type='str', required=True), + acl_num=dict(type='str'), + acl_step=dict(type='str'), + acl_description=dict(type='str'), + rule_name=dict(type='str'), + rule_id=dict(type='str'), + rule_action=dict(choices=['permit', 'deny']), + protocol=dict(choices=['ip', 'icmp', 'igmp', + 'ipinip', 'tcp', 'udp', 'gre', 'ospf']), + source_ip=dict(type='str'), + src_mask=dict(type='str'), + src_pool_name=dict(type='str'), + dest_ip=dict(type='str'), + dest_mask=dict(type='str'), + dest_pool_name=dict(type='str'), + src_port_op=dict(choices=['lt', 'eq', 'gt', 'range']), + src_port_begin=dict(type='str'), + src_port_end=dict(type='str'), + src_port_pool_name=dict(type='str'), + dest_port_op=dict(choices=['lt', 'eq', 'gt', 'range']), + dest_port_begin=dict(type='str'), + dest_port_end=dict(type='str'), + dest_port_pool_name=dict(type='str'), + frag_type=dict(choices=['fragment', 'clear_fragment']), + precedence=dict(type='str'), + tos=dict(type='str'), + dscp=dict(type='str'), + icmp_name=dict(choices=['unconfiged', 'echo', 'echo-reply', 'fragmentneed-DFset', 'host-redirect', + 'host-tos-redirect', 'host-unreachable', 'information-reply', 'information-request', + 'net-redirect', 'net-tos-redirect', 'net-unreachable', 'parameter-problem', + 'port-unreachable', 'protocol-unreachable', 'reassembly-timeout', 'source-quench', + 'source-route-failed', 'timestamp-reply', 'timestamp-request', 'ttl-exceeded', + 'address-mask-reply', 'address-mask-request', 'custom']), + icmp_type=dict(type='str'), + icmp_code=dict(type='str'), + ttl_expired=dict(required=False, default=False, type='bool'), + vrf_name=dict(type='str'), + syn_flag=dict(type='str'), + tcp_flag_mask=dict(type='str'), + established=dict(required=False, default=False, type='bool'), + time_range=dict(type='str'), + rule_description=dict(type='str'), + igmp_type=dict(choices=['host-query', 'mrouter-adver', 'mrouter-solic', 'mrouter-termi', 'mtrace-resp', + 'mtrace-route', 'v1host-report', 'v2host-report', 'v2leave-group', 'v3host-report']), + log_flag=dict(required=False, default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = AdvanceAcl(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_interface.py new file mode 100644 index 00000000..89f549e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_acl_interface.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_acl_interface +short_description: Manages applying ACLs to interfaces on HUAWEI CloudEngine switches. +description: + - Manages applying ACLs to interfaces on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_name: + description: + - ACL number or name. + For a numbered rule group, the value ranging from 2000 to 4999. + For a named rule group, the value is a string of 1 to 32 case-sensitive characters starting + with a letter, spaces not supported. + required: true + interface: + description: + - Interface name. + Only support interface full name, such as "40GE2/0/1". + required: true + direction: + description: + - Direction ACL to be applied in on the interface. + required: true + choices: ['inbound', 'outbound'] + state: + description: + - Determines whether the config should be present or not on the device. + required: false + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine acl interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Apply acl to interface" + community.network.ce_acl_interface: + state: present + acl_name: 2000 + interface: 40GE1/0/1 + direction: outbound + provider: "{{ cli }}" + + - name: "Undo acl from interface" + community.network.ce_acl_interface: + state: absent + acl_name: 2000 + interface: 40GE1/0/1 + direction: outbound + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_name": "2000", + "direction": "outbound", + "interface": "40GE2/0/1", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"acl interface": "traffic-filter acl lb inbound"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"acl interface": ["traffic-filter acl lb inbound", "traffic-filter acl 2000 outbound"]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface 40ge2/0/1", + "traffic-filter acl 2000 outbound"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_config, exec_command, cli_err_msg +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class AclInterface(object): + """ Manages acl interface configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + self.cur_cfg["acl interface"] = [] + + # module args + self.state = self.module.params['state'] + self.acl_name = self.module.params['acl_name'] + self.interface = self.module.params['interface'] + self.direction = self.module.params['direction'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check args """ + + if self.acl_name: + if self.acl_name.isdigit(): + if int(self.acl_name) < 2000 or int(self.acl_name) > 4999: + self.module.fail_json( + msg='Error: The value of acl_name is out of [2000 - 4999].') + else: + if len(self.acl_name) < 1 or len(self.acl_name) > 32: + self.module.fail_json( + msg='Error: The len of acl_name is out of [1 - 32].') + + if self.interface: + cmd = "display current-configuration | ignore-case section include interface %s" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + if not tmp: + self.module.fail_json( + msg='Error: The interface %s is not in the device.' % self.interface) + + def get_proposed(self): + """ Get proposed config """ + + self.proposed["state"] = self.state + + if self.acl_name: + self.proposed["acl_name"] = self.acl_name + + if self.interface: + self.proposed["interface"] = self.interface + + if self.direction: + self.proposed["direction"] = self.direction + + def get_existing(self): + """ Get existing config """ + + cmd = "display current-configuration | ignore-case section include interface %s | include traffic-filter" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + end = [] + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + for item in tmp: + end.append(item.strip()) + self.cur_cfg["acl interface"] = end + self.existing["acl interface"] = end + + def get_end_state(self): + """ Get config end state """ + + cmd = "display current-configuration | ignore-case section include interface %s | include traffic-filter" % self.interface + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + result = str(out).strip() + end = [] + if result: + tmp = result.split('\n') + if "display" in tmp[0]: + tmp.pop(0) + for item in tmp: + end.append(item.strip()) + self.end_state["acl interface"] = end + + def load_config(self, config): + """Sends configuration commands to the remote device""" + + rc, out, err = exec_command(self.module, 'mmi-mode enable') + if rc != 0: + self.module.fail_json(msg='unable to set mmi-mode enable', output=err) + rc, out, err = exec_command(self.module, 'system-view immediately') + if rc != 0: + self.module.fail_json(msg='unable to enter system-view', output=err) + + for cmd in config: + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + if "unrecognized command found" in err.lower(): + self.module.fail_json(msg="Error:The parameter is incorrect or the interface does not support this parameter.") + else: + self.module.fail_json(msg=cli_err_msg(cmd.strip(), err)) + + exec_command(self.module, 'return') + + def cli_load_config(self, commands): + """ Cli method to load config """ + + if not self.module.check_mode: + self.load_config(commands) + + def work(self): + """ Work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + cmds = list() + tmp_cmd = "traffic-filter acl %s %s" % (self.acl_name, self.direction) + undo_tmp_cmd = "undo traffic-filter acl %s %s" % ( + self.acl_name, self.direction) + + if self.state == "present": + if tmp_cmd not in self.cur_cfg["acl interface"]: + interface_cmd = "interface %s" % self.interface.lower() + cmds.append(interface_cmd) + cmds.append(tmp_cmd) + + self.cli_load_config(cmds) + + self.changed = True + self.updates_cmd.append(interface_cmd) + self.updates_cmd.append(tmp_cmd) + + else: + if tmp_cmd in self.cur_cfg["acl interface"]: + interface_cmd = "interface %s" % self.interface + cmds.append(interface_cmd) + cmds.append(undo_tmp_cmd) + self.cli_load_config(cmds) + + self.changed = True + self.updates_cmd.append(interface_cmd) + self.updates_cmd.append(undo_tmp_cmd) + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_name=dict(type='str', required=True), + interface=dict(type='str', required=True), + direction=dict(choices=['inbound', 'outbound'], required=True) + ) + + argument_spec.update(ce_argument_spec) + module = AclInterface(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_global.py new file mode 100644 index 00000000..7e3b2695 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_global.py @@ -0,0 +1,554 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_global +short_description: Manages BFD global configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD global configuration on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bfd_enable: + description: + - Enables the global Bidirectional Forwarding Detection (BFD) function. + choices: ['enable', 'disable'] + default_ip: + description: + - Specifies the default multicast IP address. + The value ranges from 224.0.0.107 to 224.0.0.250. + tos_exp_dynamic: + description: + - Indicates the priority of BFD control packets for dynamic BFD sessions. + The value is an integer ranging from 0 to 7. + The default priority is 7, which is the highest priority of BFD control packets. + tos_exp_static: + description: + - Indicates the priority of BFD control packets for static BFD sessions. + The value is an integer ranging from 0 to 7. + The default priority is 7, which is the highest priority of BFD control packets. + damp_init_wait_time: + description: + - Specifies an initial flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 2000. + damp_max_wait_time: + description: + - Specifies a maximum flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 15000. + damp_second_wait_time: + description: + - Specifies a secondary flapping suppression time for a BFD session. + The value is an integer ranging from 1 to 3600000, in milliseconds. + The default value is 5000. + delay_up_time: + description: + - Specifies the delay before a BFD session becomes Up. + The value is an integer ranging from 1 to 600, in seconds. + The default value is 0, indicating that a BFD session immediately becomes Up. + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Bfd global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Enable the global BFD function + community.network.ce_bfd_global: + bfd_enable: enable + provider: '{{ cli }}' + + - name: Set the default multicast IP address to 224.0.0.150 + community.network.ce_bfd_global: + bfd_enable: enable + default_ip: 224.0.0.150 + state: present + provider: '{{ cli }}' + + - name: Set the priority of BFD control packets for dynamic and static BFD sessions + community.network.ce_bfd_global: + bfd_enable: enable + tos_exp_dynamic: 5 + tos_exp_static: 6 + state: present + provider: '{{ cli }}' + + - name: Disable the global BFD function + community.network.ce_bfd_global: + bfd_enable: disable + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "bfd_enalbe": "enable", + "damp_init_wait_time": null, + "damp_max_wait_time": null, + "damp_second_wait_time": null, + "default_ip": null, + "delayUpTimer": null, + "state": "present", + "tos_exp_dynamic": null, + "tos_exp_static": null + } +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: { + "global": { + "bfdEnable": "false", + "dampInitWaitTime": "2000", + "dampMaxWaitTime": "12000", + "dampSecondWaitTime": "5000", + "defaultIp": "224.0.0.184", + "delayUpTimer": null, + "tosExp": "7", + "tosExpStatic": "7" + } + } +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: { + "global": { + "bfdEnable": "true", + "dampInitWaitTime": "2000", + "dampMaxWaitTime": "12000", + "dampSecondWaitTime": "5000", + "defaultIp": "224.0.0.184", + "delayUpTimer": null, + "tosExp": "7", + "tosExpStatic": "7" + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ "bfd" ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +CE_NC_GET_BFD = """ + + + %s + + +""" + +CE_NC_GET_BFD_GLB = """ + + + + + + + + + + +""" + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +class BfdGlobal(object): + """Manages BFD Global""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.bfd_enable = self.module.params['bfd_enable'] + self.default_ip = self.module.params['default_ip'] + self.tos_exp_dynamic = self.module.params['tos_exp_dynamic'] + self.tos_exp_static = self.module.params['tos_exp_static'] + self.damp_init_wait_time = self.module.params['damp_init_wait_time'] + self.damp_max_wait_time = self.module.params['damp_max_wait_time'] + self.damp_second_wait_time = self.module.params['damp_second_wait_time'] + self.delay_up_time = self.module.params['delay_up_time'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [('damp_init_wait_time', 'damp_max_wait_time', 'damp_second_wait_time')] + self.module = AnsibleModule(argument_spec=self.spec, + required_together=required_together, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + conf_str = CE_NC_GET_BFD % CE_NC_GET_BFD_GLB + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + if attr.text is not None: + bfd_dict["global"][attr.tag] = attr.text + + return bfd_dict + + def config_global(self): + """configures bfd global params""" + + xml_str = "" + damp_chg = False + + # bfd_enable + if self.bfd_enable: + if bool(self.bfd_dict["global"].get("bfdEnable", "false") == "true") != bool(self.bfd_enable == "enable"): + if self.bfd_enable == "enable": + xml_str = "true" + self.updates_cmd.append("bfd") + else: + xml_str = "false" + self.updates_cmd.append("undo bfd") + + # get bfd end state + bfd_state = "disable" + if self.bfd_enable: + bfd_state = self.bfd_enable + elif self.bfd_dict["global"].get("bfdEnable", "false") == "true": + bfd_state = "enable" + + # default_ip + if self.default_ip: + if bfd_state == "enable": + if self.state == "present" and self.default_ip != self.bfd_dict["global"].get("defaultIp"): + xml_str += "%s" % self.default_ip + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("default-ip-address %s" % self.default_ip) + elif self.state == "absent" and self.default_ip == self.bfd_dict["global"].get("defaultIp"): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo default-ip-address") + + # tos_exp_dynamic + if self.tos_exp_dynamic is not None: + if bfd_state == "enable": + if self.state == "present" and self.tos_exp_dynamic != int(self.bfd_dict["global"].get("tosExp", "7")): + xml_str += "%s" % self.tos_exp_dynamic + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("tos-exp %s dynamic" % self.tos_exp_dynamic) + elif self.state == "absent" and self.tos_exp_dynamic == int(self.bfd_dict["global"].get("tosExp", "7")): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo tos-exp dynamic") + + # tos_exp_static + if self.tos_exp_static is not None: + if bfd_state == "enable": + if self.state == "present" \ + and self.tos_exp_static != int(self.bfd_dict["global"].get("tosExpStatic", "7")): + xml_str += "%s" % self.tos_exp_static + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("tos-exp %s static" % self.tos_exp_static) + elif self.state == "absent" \ + and self.tos_exp_static == int(self.bfd_dict["global"].get("tosExpStatic", "7")): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo tos-exp static") + + # delay_up_time + if self.delay_up_time is not None: + if bfd_state == "enable": + delay_time = self.bfd_dict["global"].get("delayUpTimer", "0") + if not delay_time or not delay_time.isdigit(): + delay_time = "0" + if self.state == "present" \ + and self.delay_up_time != int(delay_time): + xml_str += "%s" % self.delay_up_time + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("delay-up %s" % self.delay_up_time) + elif self.state == "absent" \ + and self.delay_up_time == int(delay_time): + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo delay-up") + + # damp_init_wait_time damp_max_wait_time damp_second_wait_time + if self.damp_init_wait_time is not None and self.damp_second_wait_time is not None \ + and self.damp_second_wait_time is not None: + if bfd_state == "enable": + if self.state == "present": + if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): + xml_str += "%s" % self.damp_max_wait_time + damp_chg = True + if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): + xml_str += "%s" % self.damp_init_wait_time + damp_chg = True + if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): + xml_str += "%s" % self.damp_second_wait_time + damp_chg = True + if damp_chg: + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("dampening timer-interval maximum %s initial %s secondary %s" % ( + self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) + else: + damp_chg = True + if self.damp_max_wait_time != int(self.bfd_dict["global"].get("dampMaxWaitTime", "2000")): + damp_chg = False + if self.damp_init_wait_time != int(self.bfd_dict["global"].get("dampInitWaitTime", "12000")): + damp_chg = False + if self.damp_second_wait_time != int(self.bfd_dict["global"].get("dampSecondWaitTime", "5000")): + damp_chg = False + + if damp_chg: + xml_str += "" + if "bfd" not in self.updates_cmd: + self.updates_cmd.append("bfd") + self.updates_cmd.append("undo dampening timer-interval maximum %s initial %s secondary %s" % ( + self.damp_max_wait_time, self.damp_init_wait_time, self.damp_second_wait_time)) + if xml_str: + return '' + xml_str + '' + else: + return "" + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check default_ip + if self.default_ip: + if not check_default_ip(self.default_ip): + self.module.fail_json(msg="Error: Default ip is invalid.") + + # check tos_exp_dynamic + if self.tos_exp_dynamic is not None: + if self.tos_exp_dynamic < 0 or self.tos_exp_dynamic > 7: + self.module.fail_json(msg="Error: Session tos_exp_dynamic is not ranges from 0 to 7.") + + # check tos_exp_static + if self.tos_exp_static is not None: + if self.tos_exp_static < 0 or self.tos_exp_static > 7: + self.module.fail_json(msg="Error: Session tos_exp_static is not ranges from 0 to 7.") + + # check damp_init_wait_time + if self.damp_init_wait_time is not None: + if self.damp_init_wait_time < 1 or self.damp_init_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_init_wait_time is not ranges from 1 to 3600000.") + + # check damp_max_wait_time + if self.damp_max_wait_time is not None: + if self.damp_max_wait_time < 1 or self.damp_max_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_max_wait_time is not ranges from 1 to 3600000.") + + # check damp_second_wait_time + if self.damp_second_wait_time is not None: + if self.damp_second_wait_time < 1 or self.damp_second_wait_time > 3600000: + self.module.fail_json(msg="Error: Session damp_second_wait_time is not ranges from 1 to 3600000.") + + # check delay_up_time + if self.delay_up_time is not None: + if self.delay_up_time < 1 or self.delay_up_time > 600: + self.module.fail_json(msg="Error: Session delay_up_time is not ranges from 1 to 600.") + + def get_proposed(self): + """get proposed info""" + + self.proposed["bfd_enalbe"] = self.bfd_enable + self.proposed["default_ip"] = self.default_ip + self.proposed["tos_exp_dynamic"] = self.tos_exp_dynamic + self.proposed["tos_exp_static"] = self.tos_exp_static + self.proposed["damp_init_wait_time"] = self.damp_init_wait_time + self.proposed["damp_max_wait_time"] = self.damp_max_wait_time + self.proposed["damp_second_wait_time"] = self.damp_second_wait_time + self.proposed["delay_up_time"] = self.delay_up_time + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["global"] = self.bfd_dict.get("global") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["global"] = bfd_dict.get("global") + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = self.config_global() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bfd_enable=dict(required=False, type='str', choices=['enable', 'disable']), + default_ip=dict(required=False, type='str'), + tos_exp_dynamic=dict(required=False, type='int'), + tos_exp_static=dict(required=False, type='int'), + damp_init_wait_time=dict(required=False, type='int'), + damp_max_wait_time=dict(required=False, type='int'), + damp_second_wait_time=dict(required=False, type='int'), + delay_up_time=dict(required=False, type='int'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = BfdGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_session.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_session.py new file mode 100644 index 00000000..ae175e38 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_session.py @@ -0,0 +1,654 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_session +short_description: Manages BFD session configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD session configuration, creates a BFD session or deletes a specified BFD session + on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + session_name: + description: + - Specifies the name of a BFD session. + The value is a string of 1 to 15 case-sensitive characters without spaces. + required: true + create_type: + description: + - BFD session creation mode, the currently created BFD session + only supports static or static auto-negotiation mode. + choices: ['static', 'auto'] + default: static + addr_type: + description: + - Specifies the peer IP address type. + choices: ['ipv4'] + out_if_name: + description: + - Specifies the type and number of the interface bound to the BFD session. + dest_addr: + description: + - Specifies the peer IP address bound to the BFD session. + src_addr: + description: + - Indicates the source IP address carried in BFD packets. + local_discr: + description: + - The BFD session local identifier does not need to be configured when the mode is auto. + remote_discr: + description: + - The BFD session remote identifier does not need to be configured when the mode is auto. + vrf_name: + description: + - Specifies the name of a Virtual Private Network (VPN) instance that is bound to a BFD session. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value _public_ is reserved and cannot be used as the VPN instance name. + use_default_ip: + description: + - Indicates the default multicast IP address that is bound to a BFD session. + By default, BFD uses the multicast IP address 224.0.0.184. + You can set the multicast IP address by running the default-ip-address command. + The value is a bool type. + type: bool + default: 'no' + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Bfd session module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Configuring Single-hop BFD for Detecting Faults on a Layer 2 Link + community.network.ce_bfd_session: + session_name: bfd_l2link + use_default_ip: true + out_if_name: 10GE1/0/1 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' + + - name: Configuring Single-Hop BFD on a VLANIF Interface + community.network.ce_bfd_session: + session_name: bfd_vlanif + dest_addr: 10.1.1.6 + out_if_name: Vlanif100 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' + + - name: Configuring Multi-Hop BFD + community.network.ce_bfd_session: + session_name: bfd_multi_hop + dest_addr: 10.1.1.1 + local_discr: 163 + remote_discr: 163 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "addr_type": null, + "create_type": null, + "dest_addr": null, + "out_if_name": "10GE1/0/1", + "session_name": "bfd_l2link", + "src_addr": null, + "state": "present", + "use_default_ip": true, + "vrf_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +CE_NC_GET_BFD = """ + + + + + + + + + %s + + + + + + + + + + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class BfdSession(object): + """Manages BFD Session""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.session_name = self.module.params['session_name'] + self.create_type = self.module.params['create_type'] + self.addr_type = self.module.params['addr_type'] + self.out_if_name = self.module.params['out_if_name'] + self.dest_addr = self.module.params['dest_addr'] + self.src_addr = self.module.params['src_addr'] + self.vrf_name = self.module.params['vrf_name'] + self.use_default_ip = self.module.params['use_default_ip'] + self.state = self.module.params['state'] + self.local_discr = self.module.params['local_discr'] + self.remote_discr = self.module.params['remote_discr'] + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + mutually_exclusive = [('use_default_ip', 'dest_addr')] + self.module = AnsibleModule(argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + bfd_dict["session"] = dict() + conf_str = CE_NC_GET_BFD % self.session_name + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + bfd_dict["global"][attr.tag] = attr.text + + # get bfd session info + sess = root.find("bfd/bfdCfgSessions/bfdCfgSession") + if sess: + for attr in sess: + bfd_dict["session"][attr.tag] = attr.text + + return bfd_dict + + def is_session_match(self): + """is bfd session match""" + + if not self.bfd_dict["session"] or not self.session_name: + return False + + session = self.bfd_dict["session"] + if self.session_name != session.get("sessName", ""): + return False + + if self.create_type and self.create_type.upper() not in session.get("createType", "").upper(): + return False + + if self.addr_type and self.addr_type != session.get("addrType").lower(): + return False + + if self.dest_addr and self.dest_addr != session.get("destAddr"): + return False + + if self.src_addr and self.src_addr != session.get("srcAddr"): + return False + + if self.out_if_name: + if not session.get("outIfName"): + return False + if self.out_if_name.replace(" ", "").lower() != session.get("outIfName").replace(" ", "").lower(): + return False + + if self.vrf_name and self.vrf_name != session.get("vrfName"): + return False + + if str(self.use_default_ip).lower() != session.get("useDefaultIp"): + return False + + if self.create_type == "static" and self.state == "present": + if str(self.local_discr).lower() != session.get("localDiscr", ""): + return False + if str(self.remote_discr).lower() != session.get("remoteDiscr", ""): + return False + + return True + + def config_session(self): + """configures bfd session""" + + xml_str = "" + cmd_list = list() + discr = list() + + if not self.session_name: + return xml_str + + if self.bfd_dict["global"].get("bfdEnable", "false") != "true": + self.module.fail_json(msg="Error: Please enable BFD globally first.") + + xml_str = "%s" % self.session_name + cmd_session = "bfd %s" % self.session_name + + if self.state == "present": + if not self.bfd_dict["session"]: + # Parameter check + if not self.dest_addr and not self.use_default_ip: + self.module.fail_json( + msg="Error: dest_addr or use_default_ip must be set when bfd session is creating.") + + # Creates a BFD session + if self.create_type == "auto": + xml_str += "SESS_%s" % self.create_type.upper() + else: + xml_str += "SESS_STATIC" + xml_str += "IP" + cmd_session += " bind" + if self.addr_type: + xml_str += "%s" % self.addr_type.upper() + else: + xml_str += "IPV4" + if self.dest_addr: + xml_str += "%s" % self.dest_addr + cmd_session += " peer-%s %s" % ("ipv6" if self.addr_type == "ipv6" else "ip", self.dest_addr) + if self.use_default_ip: + xml_str += "%s" % str(self.use_default_ip).lower() + cmd_session += " peer-ip default-ip" + if self.vrf_name: + xml_str += "%s" % self.vrf_name + cmd_session += " vpn-instance %s" % self.vrf_name + if self.out_if_name: + xml_str += "%s" % self.out_if_name + cmd_session += " interface %s" % self.out_if_name.lower() + if self.src_addr: + xml_str += "%s" % self.src_addr + cmd_session += " source-%s %s" % ("ipv6" if self.addr_type == "ipv6" else "ip", self.src_addr) + + if self.create_type == "auto": + cmd_session += " auto" + else: + xml_str += "%s" % self.local_discr + discr.append("discriminator local %s" % self.local_discr) + xml_str += "%s" % self.remote_discr + discr.append("discriminator remote %s" % self.remote_discr) + + elif not self.is_session_match(): + # Bfd session is not match + self.module.fail_json(msg="Error: The specified BFD configuration view has been created.") + else: + pass + else: # absent + if not self.bfd_dict["session"]: + self.module.fail_json(msg="Error: BFD session is not exist.") + if not self.is_session_match(): + self.module.fail_json(msg="Error: BFD session parameter is invalid.") + + if self.state == "present": + if xml_str.endswith(""): + # no config update + return "" + else: + cmd_list.insert(0, cmd_session) + cmd_list.extend(discr) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + else: # absent + cmd_list.append("undo " + cmd_session) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check session_name + if not self.session_name: + self.module.fail_json(msg="Error: Missing required arguments: session_name.") + + if self.session_name: + if len(self.session_name) < 1 or len(self.session_name) > 15: + self.module.fail_json(msg="Error: Session name is invalid.") + + # check local_discr + # check remote_discr + + if self.local_discr: + if self.local_discr < 1 or self.local_discr > 16384: + self.module.fail_json(msg="Error: Session local_discr is not ranges from 1 to 16384.") + if self.remote_discr: + if self.remote_discr < 1 or self.remote_discr > 4294967295: + self.module.fail_json(msg="Error: Session remote_discr is not ranges from 1 to 4294967295.") + + if self.state == "present" and self.create_type == "static": + if not self.local_discr: + self.module.fail_json(msg="Error: Missing required arguments: local_discr.") + if not self.remote_discr: + self.module.fail_json(msg="Error: Missing required arguments: remote_discr.") + + # check out_if_name + if self.out_if_name: + if not get_interface_type(self.out_if_name): + self.module.fail_json(msg="Error: Session out_if_name is invalid.") + + # check dest_addr + if self.dest_addr: + if not check_ip_addr(self.dest_addr): + self.module.fail_json(msg="Error: Session dest_addr is invalid.") + + # check src_addr + if self.src_addr: + if not check_ip_addr(self.src_addr): + self.module.fail_json(msg="Error: Session src_addr is invalid.") + + # check vrf_name + if self.vrf_name: + if not is_valid_ip_vpn(self.vrf_name): + self.module.fail_json(msg="Error: Session vrf_name is invalid.") + if not self.dest_addr: + self.module.fail_json(msg="Error: vrf_name and dest_addr must set at the same time.") + + # check use_default_ip + if self.use_default_ip and not self.out_if_name: + self.module.fail_json(msg="Error: use_default_ip and out_if_name must set at the same time.") + + def get_proposed(self): + """get proposed info""" + + # base config + self.proposed["session_name"] = self.session_name + self.proposed["create_type"] = self.create_type + self.proposed["addr_type"] = self.addr_type + self.proposed["out_if_name"] = self.out_if_name + self.proposed["dest_addr"] = self.dest_addr + self.proposed["src_addr"] = self.src_addr + self.proposed["vrf_name"] = self.vrf_name + self.proposed["use_default_ip"] = self.use_default_ip + self.proposed["state"] = self.state + self.proposed["local_discr"] = self.local_discr + self.proposed["remote_discr"] = self.remote_discr + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["session"] = self.bfd_dict.get("session") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["session"] = bfd_dict.get("session") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.session_name: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + session_name=dict(required=True, type='str'), + create_type=dict(required=False, default='static', type='str', choices=['static', 'auto']), + addr_type=dict(required=False, type='str', choices=['ipv4']), + out_if_name=dict(required=False, type='str'), + dest_addr=dict(required=False, type='str'), + src_addr=dict(required=False, type='str'), + vrf_name=dict(required=False, type='str'), + use_default_ip=dict(required=False, type='bool', default=False), + state=dict(required=False, default='present', choices=['present', 'absent']), + local_discr=dict(required=False, type='int'), + remote_discr=dict(required=False, type='int') + ) + + argument_spec.update(ce_argument_spec) + module = BfdSession(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_view.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_view.py new file mode 100644 index 00000000..9a170570 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bfd_view.py @@ -0,0 +1,561 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bfd_view +short_description: Manages BFD session view configuration on HUAWEI CloudEngine devices. +description: + - Manages BFD session view configuration on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + session_name: + description: + - Specifies the name of a BFD session. + The value is a string of 1 to 15 case-sensitive characters without spaces. + required: true + local_discr: + description: + - Specifies the local discriminator of a BFD session. + The value is an integer that ranges from 1 to 16384. + remote_discr: + description: + - Specifies the remote discriminator of a BFD session. + The value is an integer that ranges from 1 to 4294967295. + min_tx_interval: + description: + - Specifies the minimum interval for receiving BFD packets. + The value is an integer that ranges from 50 to 1000, in milliseconds. + min_rx_interval: + description: + - Specifies the minimum interval for sending BFD packets. + The value is an integer that ranges from 50 to 1000, in milliseconds. + detect_multi: + description: + - Specifies the local detection multiplier of a BFD session. + The value is an integer that ranges from 3 to 50. + wtr_interval: + description: + - Specifies the WTR time of a BFD session. + The value is an integer that ranges from 1 to 60, in minutes. + The default value is 0. + tos_exp: + description: + - Specifies a priority for BFD control packets. + The value is an integer ranging from 0 to 7. + The default value is 7, which is the highest priority. + admin_down: + description: + - Enables the BFD session to enter the AdminDown state. + By default, a BFD session is enabled. + The default value is bool type. + type: bool + default: 'no' + description: + description: + - Specifies the description of a BFD session. + The value is a string of 1 to 51 case-sensitive characters with spaces. + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: ['present', 'absent'] +extends_documentation_fragment: +- community.network.ce + +''' + +EXAMPLES = ''' +- name: Bfd view module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Set the local discriminator of a BFD session to 80 and the remote discriminator to 800 + community.network.ce_bfd_view: + session_name: atob + local_discr: 80 + remote_discr: 800 + state: present + provider: '{{ cli }}' + + - name: Set the minimum interval for receiving BFD packets to 500 ms + community.network.ce_bfd_view: + session_name: atob + min_rx_interval: 500 + state: present + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "admin_down": false, + "description": null, + "detect_multi": null, + "local_discr": 80, + "min_rx_interval": null, + "min_tx_interval": null, + "remote_discr": 800, + "session_name": "atob", + "state": "present", + "tos_exp": null, + "wtr_interval": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": { + "adminDown": "false", + "createType": "SESS_STATIC", + "description": null, + "detectMulti": "3", + "localDiscr": null, + "minRxInt": null, + "minTxInt": null, + "remoteDiscr": null, + "sessName": "atob", + "tosExp": null, + "wtrTimerInt": null + } + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "adminDown": "false", + "createType": "SESS_STATIC", + "description": null, + "detectMulti": "3", + "localDiscr": "80", + "minRxInt": null, + "minTxInt": null, + "remoteDiscr": "800", + "sessName": "atob", + "tosExp": null, + "wtrTimerInt": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd atob", + "discriminator local 80", + "discriminator remote 800" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_BFD = """ + + + %s + + +""" + +CE_NC_GET_BFD_GLB = """ + + + +""" + +CE_NC_GET_BFD_SESSION = """ + + + %s + + + + + + + + + + + + +""" + + +class BfdView(object): + """Manages BFD View""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.session_name = self.module.params['session_name'] + self.local_discr = self.module.params['local_discr'] + self.remote_discr = self.module.params['remote_discr'] + self.min_tx_interval = self.module.params['min_tx_interval'] + self.min_rx_interval = self.module.params['min_rx_interval'] + self.detect_multi = self.module.params['detect_multi'] + self.wtr_interval = self.module.params['wtr_interval'] + self.tos_exp = self.module.params['tos_exp'] + self.admin_down = self.module.params['admin_down'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.bfd_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, + supports_check_mode=True) + + def get_bfd_dict(self): + """bfd config dict""" + + bfd_dict = dict() + bfd_dict["global"] = dict() + bfd_dict["session"] = dict() + conf_str = CE_NC_GET_BFD % (CE_NC_GET_BFD_GLB + (CE_NC_GET_BFD_SESSION % self.session_name)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return bfd_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + glb = root.find("bfd/bfdSchGlobal") + if glb: + for attr in glb: + bfd_dict["global"][attr.tag] = attr.text + + # get bfd session info + sess = root.find("bfd/bfdCfgSessions/bfdCfgSession") + if sess: + for attr in sess: + bfd_dict["session"][attr.tag] = attr.text + + return bfd_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + cmd_list = list() + cmd_session = "" + + if not self.session_name: + return xml_str + + if self.bfd_dict["global"].get("bfdEnable", "false") != "true": + self.module.fail_json(msg="Error: Please enable BFD globally first.") + + if not self.bfd_dict["session"]: + self.module.fail_json(msg="Error: BFD session is not exist.") + + session = self.bfd_dict["session"] + xml_str = "%s" % self.session_name + cmd_session = "bfd %s" % self.session_name + + # BFD session view + if self.local_discr is not None: + if self.state == "present" and str(self.local_discr) != session.get("localDiscr"): + xml_str += "%s" % self.local_discr + cmd_list.append("discriminator local %s" % self.local_discr) + elif self.state == "absent" and str(self.local_discr) == session.get("localDiscr"): + xml_str += "" + cmd_list.append("undo discriminator local") + + if self.remote_discr is not None: + if self.state == "present" and str(self.remote_discr) != session.get("remoteDiscr"): + xml_str += "%s" % self.remote_discr + cmd_list.append("discriminator remote %s" % self.remote_discr) + elif self.state == "absent" and str(self.remote_discr) == session.get("remoteDiscr"): + xml_str += "" + cmd_list.append("undo discriminator remote") + + if self.min_tx_interval is not None: + if self.state == "present" and str(self.min_tx_interval) != session.get("minTxInt"): + xml_str += "%s" % self.min_tx_interval + cmd_list.append("min-tx-interval %s" % self.min_tx_interval) + elif self.state == "absent" and str(self.min_tx_interval) == session.get("minTxInt"): + xml_str += "" + cmd_list.append("undo min-tx-interval") + + if self.min_rx_interval is not None: + if self.state == "present" and str(self.min_rx_interval) != session.get("minRxInt"): + xml_str += "%s" % self.min_rx_interval + cmd_list.append("min-rx-interval %s" % self.min_rx_interval) + elif self.state == "absent" and str(self.min_rx_interval) == session.get("minRxInt"): + xml_str += "" + cmd_list.append("undo min-rx-interval") + + if self.detect_multi is not None: + if self.state == "present" and str(self.detect_multi) != session.get("detectMulti"): + xml_str += " %s" % self.detect_multi + cmd_list.append("detect-multiplier %s" % self.detect_multi) + elif self.state == "absent" and str(self.detect_multi) == session.get("detectMulti"): + xml_str += " " + cmd_list.append("undo detect-multiplier") + + if self.wtr_interval is not None: + if self.state == "present" and str(self.wtr_interval) != session.get("wtrTimerInt"): + xml_str += " %s" % self.wtr_interval + cmd_list.append("wtr %s" % self.wtr_interval) + elif self.state == "absent" and str(self.wtr_interval) == session.get("wtrTimerInt"): + xml_str += " " + cmd_list.append("undo wtr") + + if self.tos_exp is not None: + if self.state == "present" and str(self.tos_exp) != session.get("tosExp"): + xml_str += " %s" % self.tos_exp + cmd_list.append("tos-exp %s" % self.tos_exp) + elif self.state == "absent" and str(self.tos_exp) == session.get("tosExp"): + xml_str += " " + cmd_list.append("undo tos-exp") + + if self.admin_down and session.get("adminDown", "false") == "false": + xml_str += " true" + cmd_list.append("shutdown") + elif not self.admin_down and session.get("adminDown", "false") == "true": + xml_str += " false" + cmd_list.append("undo shutdown") + + if self.description: + if self.state == "present" and self.description != session.get("description"): + xml_str += "%s" % self.description + cmd_list.append("description %s" % self.description) + elif self.state == "absent" and self.description == session.get("description"): + xml_str += "" + cmd_list.append("undo description") + + if xml_str.endswith(""): + # no config update + return "" + else: + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + return '' + xml_str\ + + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + set_nc_config(self.min_rx_interval, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check session_name + if not self.session_name: + self.module.fail_json(msg="Error: Missing required arguments: session_name.") + + if self.session_name: + if len(self.session_name) < 1 or len(self.session_name) > 15: + self.module.fail_json(msg="Error: Session name is invalid.") + + # check local_discr + if self.local_discr is not None: + if self.local_discr < 1 or self.local_discr > 16384: + self.module.fail_json(msg="Error: Session local_discr is not ranges from 1 to 16384.") + + # check remote_discr + if self.remote_discr is not None: + if self.remote_discr < 1 or self.remote_discr > 4294967295: + self.module.fail_json(msg="Error: Session remote_discr is not ranges from 1 to 4294967295.") + + # check min_tx_interval + if self.min_tx_interval is not None: + if self.min_tx_interval < 50 or self.min_tx_interval > 1000: + self.module.fail_json(msg="Error: Session min_tx_interval is not ranges from 50 to 1000.") + + # check min_rx_interval + if self.min_rx_interval is not None: + if self.min_rx_interval < 50 or self.min_rx_interval > 1000: + self.module.fail_json(msg="Error: Session min_rx_interval is not ranges from 50 to 1000.") + + # check detect_multi + if self.detect_multi is not None: + if self.detect_multi < 3 or self.detect_multi > 50: + self.module.fail_json(msg="Error: Session detect_multi is not ranges from 3 to 50.") + + # check wtr_interval + if self.wtr_interval is not None: + if self.wtr_interval < 1 or self.wtr_interval > 60: + self.module.fail_json(msg="Error: Session wtr_interval is not ranges from 1 to 60.") + + # check tos_exp + if self.tos_exp is not None: + if self.tos_exp < 0 or self.tos_exp > 7: + self.module.fail_json(msg="Error: Session tos_exp is not ranges from 0 to 7.") + + # check description + if self.description: + if len(self.description) < 1 or len(self.description) > 51: + self.module.fail_json(msg="Error: Session description is invalid.") + + def get_proposed(self): + """get proposed info""" + + # base config + self.proposed["session_name"] = self.session_name + self.proposed["local_discr"] = self.local_discr + self.proposed["remote_discr"] = self.remote_discr + self.proposed["min_tx_interval"] = self.min_tx_interval + self.proposed["min_rx_interval"] = self.min_rx_interval + self.proposed["detect_multi"] = self.detect_multi + self.proposed["wtr_interval"] = self.wtr_interval + self.proposed["tos_exp"] = self.tos_exp + self.proposed["admin_down"] = self.admin_down + self.proposed["description"] = self.description + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.bfd_dict: + return + + self.existing["session"] = self.bfd_dict.get("session") + + def get_end_state(self): + """get end state info""" + + bfd_dict = self.get_bfd_dict() + if not bfd_dict: + return + + self.end_state["session"] = bfd_dict.get("session") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.bfd_dict = self.get_bfd_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.session_name: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + session_name=dict(required=True, type='str'), + local_discr=dict(required=False, type='int'), + remote_discr=dict(required=False, type='int'), + min_tx_interval=dict(required=False, type='int'), + min_rx_interval=dict(required=False, type='int'), + detect_multi=dict(required=False, type='int'), + wtr_interval=dict(required=False, type='int'), + tos_exp=dict(required=False, type='int'), + admin_down=dict(required=False, type='bool', default=False), + description=dict(required=False, type='str'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = BfdView(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp.py new file mode 100644 index 00000000..0ff7c57d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp.py @@ -0,0 +1,2327 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp +short_description: Manages BGP configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + as_number: + description: + - Local AS number. + The value is a string of 1 to 11 characters. + graceful_restart: + description: + - Enable GR of the BGP speaker in the specified address family, peer address, or peer group. + default: no_use + choices: ['no_use','true','false'] + time_wait_for_rib: + description: + - Period of waiting for the End-Of-RIB flag. + The value is an integer ranging from 3 to 3000. The default value is 600. + as_path_limit: + description: + - Maximum number of AS numbers in the AS_Path attribute. The default value is 255. + check_first_as: + description: + - Check the first AS in the AS_Path of the update messages from EBGP peers. + default: no_use + choices: ['no_use','true','false'] + confed_id_number: + description: + - Confederation ID. + The value is a string of 1 to 11 characters. + confed_nonstanded: + description: + - Configure the device to be compatible with devices in a nonstandard confederation. + default: no_use + choices: ['no_use','true','false'] + bgp_rid_auto_sel: + description: + - The function to automatically select router IDs for all VPN BGP instances is enabled. + default: no_use + choices: ['no_use','true','false'] + keep_all_routes: + description: + - If the value is true, the system stores all route update messages received from all peers (groups) after + BGP connection setup. + If the value is false, the system stores only BGP update messages that are received from peers and pass + the configured import policy. + default: no_use + choices: ['no_use','true','false'] + memory_limit: + description: + - Support BGP RIB memory protection. + default: no_use + choices: ['no_use','true','false'] + gr_peer_reset: + description: + - Peer disconnection through GR. + default: no_use + choices: ['no_use','true','false'] + is_shutdown: + description: + - Interrupt BGP all neighbor. + default: no_use + choices: ['no_use','true','false'] + suppress_interval: + description: + - Suppress interval. + hold_interval: + description: + - Hold interval. + clear_interval: + description: + - Clear interval. + confed_peer_as_num: + description: + - Confederation AS number, in two-byte or four-byte format. + The value is a string of 1 to 11 characters. + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + vrf_rid_auto_sel: + description: + - If the value is true, VPN BGP instances are enabled to automatically select router IDs. + If the value is false, VPN BGP instances are disabled from automatically selecting router IDs. + default: no_use + choices: ['no_use','true','false'] + router_id: + description: + - ID of a router that is in IPv4 address format. + keepalive_time: + description: + - If the value of a timer changes, the BGP peer relationship between the routers is disconnected. + The value is an integer ranging from 0 to 21845. The default value is 60. + hold_time: + description: + - Hold time, in seconds. The value of the hold time can be 0 or range from 3 to 65535. + min_hold_time: + description: + - Min hold time, in seconds. The value of the hold time can be 0 or range from 20 to 65535. + conn_retry_time: + description: + - ConnectRetry interval. The value is an integer, in seconds. The default value is 32s. + ebgp_if_sensitive: + description: + - If the value is true, After the fast EBGP interface awareness function is enabled, EBGP sessions on + an interface are deleted immediately when the interface goes Down. + If the value is false, After the fast EBGP interface awareness function is enabled, EBGP sessions + on an interface are not deleted immediately when the interface goes Down. + default: no_use + choices: ['no_use','true','false'] + default_af_type: + description: + - Type of a created address family, which can be IPv4 unicast or IPv6 unicast. + The default type is IPv4 unicast. + choices: ['ipv4uni','ipv6uni'] +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Enable BGP" + community.network.ce_bgp: + state: present + as_number: 100 + confed_id_number: 250 + provider: "{{ cli }}" + + - name: "Disable BGP" + community.network.ce_bgp: + state: absent + as_number: 100 + confed_id_number: 250 + provider: "{{ cli }}" + + - name: "Create confederation peer AS num" + community.network.ce_bgp: + state: present + confed_peer_as_num: 260 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"as_number": "100", state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp_enable": [["100"], ["true"]]} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp_enable": [["100"], ["true"]]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["bgp 100"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +SUCCESS = """success""" +FAILED = """failed""" + + +# get bgp enable +CE_GET_BGP_ENABLE = """ + + + + + + + + + + +""" + +CE_GET_BGP_ENABLE_HEADER = """ + + + + +""" + +CE_GET_BGP_ENABLE_TAIL = """ + + + + +""" + +# merge bgp enable +CE_MERGE_BGP_ENABLE_HEADER = """ + + + + +""" +CE_MERGE_BGP_ENABLE_TAIL = """ + + + + +""" + +# get bgp confederation peer as +CE_GET_BGP_CONFED_PEER_AS = """ + + + + + + + + + + + +""" + +# merge bgp confederation peer as +CE_MERGE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# create bgp confederation peer as +CE_CREATE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# delete bgp confederation peer as +CE_DELETE_BGP_CONFED_PEER_AS = """ + + + + + + %s + + + + + +""" + +# get bgp instance +CE_GET_BGP_INSTANCE = """ + + + + + + + + + + + +""" + +# get bgp instance +CE_GET_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_GET_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# merge bgp instance +CE_MERGE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_MERGE_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# create bgp instance +CE_CREATE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_CREATE_BGP_INSTANCE_TAIL = """ + + + + + +""" + +# delete bgp instance +CE_DELETE_BGP_INSTANCE_HEADER = """ + + + + + +""" +CE_DELETE_BGP_INSTANCE_TAIL = """ + + + + + +""" + + +def check_ip_addr(**kwargs): + """ check_ip_addr """ + + ipaddr = kwargs["ipaddr"] + + addr = ipaddr.strip().split('.') + + if len(addr) != 4: + return FAILED + + for i in range(4): + addr[i] = int(addr[i]) + + if addr[i] <= 255 and addr[i] >= 0: + pass + else: + return FAILED + return SUCCESS + + +def check_bgp_enable_args(**kwargs): + """ check_bgp_enable_args """ + + module = kwargs["module"] + + need_cfg = False + + as_number = module.params['as_number'] + if as_number: + if len(as_number) > 11 or len(as_number) == 0: + module.fail_json( + msg='Error: The len of as_number %s is out of [1 - 11].' % as_number) + else: + need_cfg = True + + return need_cfg + + +def check_bgp_confed_args(**kwargs): + """ check_bgp_confed_args """ + + module = kwargs["module"] + + need_cfg = False + + confed_peer_as_num = module.params['confed_peer_as_num'] + if confed_peer_as_num: + if len(confed_peer_as_num) > 11 or len(confed_peer_as_num) == 0: + module.fail_json( + msg='Error: The len of confed_peer_as_num %s is out of [1 - 11].' % confed_peer_as_num) + else: + need_cfg = True + + return need_cfg + + +class Bgp(object): + """ Manages BGP configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_enable_other_args(self, **kwargs): + """ check_bgp_enable_other_args """ + + module = kwargs["module"] + state = module.params['state'] + result = dict() + need_cfg = False + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["graceful_restart"] = re_find + if re_find[0] != graceful_restart: + need_cfg = True + else: + need_cfg = True + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + if int(time_wait_for_rib) > 3000 or int(time_wait_for_rib) < 3: + module.fail_json( + msg='Error: The time_wait_for_rib %s is out of [3 - 3000].' % time_wait_for_rib) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["time_wait_for_rib"] = re_find + if re_find[0] != time_wait_for_rib: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["time_wait_for_rib"] = re_find + if re_find[0] == time_wait_for_rib: + need_cfg = True + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + if int(as_path_limit) > 2000 or int(as_path_limit) < 1: + module.fail_json( + msg='Error: The as_path_limit %s is out of [1 - 2000].' % as_path_limit) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_limit"] = re_find + if re_find[0] != as_path_limit: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_limit"] = re_find + if re_find[0] == as_path_limit: + need_cfg = True + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["check_first_as"] = re_find + if re_find[0] != check_first_as: + need_cfg = True + else: + need_cfg = True + + confed_id_number = module.params['confed_id_number'] + if confed_id_number: + if len(confed_id_number) > 11 or len(confed_id_number) == 0: + module.fail_json( + msg='Error: The len of confed_id_number %s is out of [1 - 11].' % confed_id_number) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_id_number"] = re_find + if re_find[0] != confed_id_number: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_id_number"] = re_find + if re_find[0] == confed_id_number: + need_cfg = True + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["confed_nonstanded"] = re_find + if re_find[0] != confed_nonstanded: + need_cfg = True + else: + need_cfg = True + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["bgp_rid_auto_sel"] = re_find + if re_find[0] != bgp_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_all_routes"] = re_find + if re_find[0] != keep_all_routes: + need_cfg = True + else: + need_cfg = True + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["memory_limit"] = re_find + if re_find[0] != memory_limit: + need_cfg = True + else: + need_cfg = True + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["gr_peer_reset"] = re_find + if re_find[0] != gr_peer_reset: + need_cfg = True + else: + need_cfg = True + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_shutdown"] = re_find + if re_find[0] != is_shutdown: + need_cfg = True + else: + need_cfg = True + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + + if not hold_interval or not clear_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(suppress_interval) > 65535 or int(suppress_interval) < 1: + module.fail_json( + msg='Error: The suppress_interval %s is out of [1 - 65535].' % suppress_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["suppress_interval"] = re_find + if re_find[0] != suppress_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["suppress_interval"] = re_find + if re_find[0] == suppress_interval: + need_cfg = True + + if hold_interval: + + if not suppress_interval or not clear_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(hold_interval) > 65535 or int(hold_interval) < 1: + module.fail_json( + msg='Error: The hold_interval %s is out of [1 - 65535].' % hold_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_interval"] = re_find + if re_find[0] != hold_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_interval"] = re_find + if re_find[0] == hold_interval: + need_cfg = True + + if clear_interval: + + if not suppress_interval or not hold_interval: + module.fail_json( + msg='Error: Please input suppress_interval hold_interval clear_interval at the same time.') + + if int(clear_interval) > 65535 or int(clear_interval) < 1: + module.fail_json( + msg='Error: The clear_interval %s is out of [1 - 65535].' % clear_interval) + else: + conf_str = CE_GET_BGP_ENABLE_HEADER + \ + "" + CE_GET_BGP_ENABLE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["clear_interval"] = re_find + if re_find[0] != clear_interval: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["clear_interval"] = re_find + if re_find[0] == clear_interval: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_instance_args(self, **kwargs): + """ check_bgp_instance_args """ + + module = kwargs["module"] + state = module.params['state'] + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='the len of vrf_name %s is out of [1 - 31].' % vrf_name) + conf_str = CE_GET_BGP_INSTANCE_HEADER + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + check_vrf_name = vrf_name + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if check_vrf_name not in re_find: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if check_vrf_name in re_find: + need_cfg = True + + return need_cfg + + def check_bgp_instance_other_args(self, **kwargs): + """ check_bgp_instance_other_args """ + + module = kwargs["module"] + state = module.params['state'] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + + router_id = module.params['router_id'] + if router_id: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if check_ip_addr(ipaddr=router_id) == FAILED: + module.fail_json( + msg='Error: The router_id %s is invalid.' % router_id) + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + if re_find[0] == router_id: + need_cfg = True + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vrf_rid_auto_sel"] = re_find + + if re_find[0] != vrf_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(keepalive_time) > 21845 or int(keepalive_time) < 0: + module.fail_json( + msg='keepalive_time %s is out of [0 - 21845].' % keepalive_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keepalive_time"] = re_find + if re_find[0] != keepalive_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keepalive_time"] = re_find + if re_find[0] == keepalive_time: + need_cfg = True + + hold_time = module.params['hold_time'] + if hold_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(hold_time) > 65535 or int(hold_time) < 3: + module.fail_json( + msg='hold_time %s is out of [3 - 65535].' % hold_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] != hold_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] == hold_time: + need_cfg = True + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(min_hold_time) != 0 and (int(min_hold_time) > 65535 or int(min_hold_time) < 20): + module.fail_json( + msg='min_hold_time %s is out of [0, or 20 - 65535].' % min_hold_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] != min_hold_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] == min_hold_time: + need_cfg = True + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + if int(conn_retry_time) > 65535 or int(conn_retry_time) < 1: + module.fail_json( + msg='conn_retry_time %s is out of [1 - 65535].' % conn_retry_time) + else: + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config( + module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] != conn_retry_time: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] == conn_retry_time: + need_cfg = True + else: + pass + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + if re_find[0] == ebgp_if_sensitive: + need_cfg = True + else: + pass + + default_af_type = module.params['default_af_type'] + if default_af_type: + + if not vrf_name: + module.fail_json( + msg='Error: Please input vrf_name.') + + conf_str = CE_GET_BGP_INSTANCE_HEADER + "%s" % vrf_name + \ + "" + CE_GET_BGP_INSTANCE_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_af_type"] = re_find + if re_find[0] != default_af_type: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_af_type"] = re_find + if re_find[0] == default_af_type: + need_cfg = True + else: + pass + + result["need_cfg"] = need_cfg + return result + + def get_bgp_enable(self, **kwargs): + """ get_bgp_enable """ + + module = kwargs["module"] + + conf_str = CE_GET_BGP_ENABLE + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_enable(self, **kwargs): + """ merge_bgp_enable """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + state = module.params['state'] + + if state == "present": + conf_str += "true" + else: + conf_str += "false" + + as_number = module.params['as_number'] + if as_number: + conf_str += "%s" % as_number + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp enable failed.') + + cmds = [] + if state == "present": + cmd = "bgp %s" % as_number + else: + cmd = "undo bgp %s" % as_number + cmds.append(cmd) + + return cmds + + def merge_bgp_enable_other(self, **kwargs): + """ merge_bgp_enable_other """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + cmds = [] + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + conf_str += "%s" % graceful_restart + + if graceful_restart == "true": + cmd = "graceful-restart" + else: + cmd = "undo graceful-restart" + cmds.append(cmd) + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + conf_str += "%s" % time_wait_for_rib + + cmd = "graceful-restart timer wait-for-rib %s" % time_wait_for_rib + cmds.append(cmd) + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + conf_str += "%s" % as_path_limit + + cmd = "as-path-limit %s" % as_path_limit + cmds.append(cmd) + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str += "%s" % check_first_as + + if check_first_as == "true": + cmd = "check-first-as" + else: + cmd = "undo check-first-as" + cmds.append(cmd) + + confed_id_number = module.params['confed_id_number'] + if confed_id_number: + conf_str += "%s" % confed_id_number + + cmd = "confederation id %s" % confed_id_number + cmds.append(cmd) + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str += "%s" % confed_nonstanded + + if confed_nonstanded == "true": + cmd = "confederation nonstandard" + else: + cmd = "undo confederation nonstandard" + cmds.append(cmd) + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str += "%s" % bgp_rid_auto_sel + + if bgp_rid_auto_sel == "true": + cmd = "router-id vpn-instance auto-select" + else: + cmd = "undo router-id" + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "keep-all-routes" + else: + cmd = "undo keep-all-routes" + cmds.append(cmd) + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str += "%s" % memory_limit + + if memory_limit == "true": + cmd = "prefix memory-limit" + else: + cmd = "undo prefix memory-limit" + cmds.append(cmd) + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str += "%s" % gr_peer_reset + + if gr_peer_reset == "true": + cmd = "graceful-restart peer-reset" + else: + cmd = "undo graceful-restart peer-reset" + cmds.append(cmd) + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str += "%s" % is_shutdown + + if is_shutdown == "true": + cmd = "shutdown" + else: + cmd = "undo shutdown" + cmds.append(cmd) + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + conf_str += "%s" % suppress_interval + + cmd = "nexthop recursive-lookup restrain suppress-interval %s hold-interval %s " \ + "clear-interval %s" % (suppress_interval, hold_interval, clear_interval) + cmds.append(cmd) + + if hold_interval: + conf_str += "%s" % hold_interval + + if clear_interval: + conf_str += "%s" % clear_interval + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp enable failed.') + + return cmds + + def delete_bgp_enable_other(self, **kwargs): + """ delete bgp enable other args """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_ENABLE_HEADER + + cmds = [] + + graceful_restart = module.params['graceful_restart'] + if graceful_restart != 'no_use': + conf_str += "%s" % graceful_restart + + if graceful_restart == "true": + cmd = "graceful-restart" + else: + cmd = "undo graceful-restart" + cmds.append(cmd) + + time_wait_for_rib = module.params['time_wait_for_rib'] + if time_wait_for_rib: + conf_str += "600" + + cmd = "undo graceful-restart timer wait-for-rib" + cmds.append(cmd) + + as_path_limit = module.params['as_path_limit'] + if as_path_limit: + conf_str += "255" + + cmd = "undo as-path-limit" + cmds.append(cmd) + + check_first_as = module.params['check_first_as'] + if check_first_as != 'no_use': + conf_str += "%s" % check_first_as + + if check_first_as == "true": + cmd = "check-first-as" + else: + cmd = "undo check-first-as" + cmds.append(cmd) + + confed_id_number = module.params['confed_id_number'] + confed_peer_as_num = module.params['confed_peer_as_num'] + if confed_id_number and not confed_peer_as_num: + conf_str += "" + + cmd = "undo confederation id" + cmds.append(cmd) + + confed_nonstanded = module.params['confed_nonstanded'] + if confed_nonstanded != 'no_use': + conf_str += "%s" % confed_nonstanded + + if confed_nonstanded == "true": + cmd = "confederation nonstandard" + else: + cmd = "undo confederation nonstandard" + cmds.append(cmd) + + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + if bgp_rid_auto_sel != 'no_use': + conf_str += "%s" % bgp_rid_auto_sel + + if bgp_rid_auto_sel == "true": + cmd = "router-id vpn-instance auto-select" + else: + cmd = "undo router-id" + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "keep-all-routes" + else: + cmd = "undo keep-all-routes" + cmds.append(cmd) + + memory_limit = module.params['memory_limit'] + if memory_limit != 'no_use': + conf_str += "%s" % memory_limit + + if memory_limit == "true": + cmd = "prefix memory-limit" + else: + cmd = "undo prefix memory-limit" + cmds.append(cmd) + + gr_peer_reset = module.params['gr_peer_reset'] + if gr_peer_reset != 'no_use': + conf_str += "%s" % gr_peer_reset + + if gr_peer_reset == "true": + cmd = "graceful-restart peer-reset" + else: + cmd = "undo graceful-restart peer-reset" + cmds.append(cmd) + + is_shutdown = module.params['is_shutdown'] + if is_shutdown != 'no_use': + conf_str += "%s" % is_shutdown + + if is_shutdown == "true": + cmd = "shutdown" + else: + cmd = "undo shutdown" + cmds.append(cmd) + + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + if suppress_interval: + conf_str += "60" + + cmd = "undo nexthop recursive-lookup restrain suppress-interval hold-interval clear-interval" + cmds.append(cmd) + + if hold_interval: + conf_str += "120" + + if clear_interval: + conf_str += "600" + + conf_str += CE_MERGE_BGP_ENABLE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp enable failed.') + + return cmds + + def get_bgp_confed_peer_as(self, **kwargs): + """ get_bgp_confed_peer_as """ + + module = kwargs["module"] + + conf_str = CE_GET_BGP_CONFED_PEER_AS + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_confed_peer_as(self, **kwargs): + """ merge_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_MERGE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp confed peer as failed.') + + cmds = [] + cmd = "confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def create_bgp_confed_peer_as(self, **kwargs): + """ create_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_CREATE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp confed peer as failed.') + + cmds = [] + cmd = "confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def delete_bgp_confed_peer_as(self, **kwargs): + """ delete_bgp_confed_peer_as """ + + module = kwargs["module"] + confed_peer_as_num = module.params['confed_peer_as_num'] + + conf_str = CE_DELETE_BGP_CONFED_PEER_AS % confed_peer_as_num + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp confed peer as failed.') + + cmds = [] + cmd = "undo confederation peer-as %s" % confed_peer_as_num + cmds.append(cmd) + + return cmds + + def get_bgp_instance(self, **kwargs): + """ get_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_GET_BGP_INSTANCE + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_instance(self, **kwargs): + """ merge_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp instance failed.') + + cmds = [] + + if vrf_name != "_public_": + cmd = "ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def create_bgp_instance(self, **kwargs): + """ create_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_CREATE_BGP_INSTANCE_HEADER + + cmds = [] + + vrf_name = module.params['vrf_name'] + if vrf_name: + if vrf_name == "_public_": + return cmds + conf_str += "%s" % vrf_name + + conf_str += CE_CREATE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp instance failed.') + + if vrf_name != "_public_": + cmd = "ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def delete_bgp_instance(self, **kwargs): + """ delete_bgp_instance """ + + module = kwargs["module"] + conf_str = CE_DELETE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + if vrf_name: + conf_str += "%s" % vrf_name + + conf_str += CE_DELETE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp instance failed.') + + cmds = [] + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + return cmds + + def merge_bgp_instance_other(self, **kwargs): + """ merge_bgp_instance_other """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % default_af_type + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + + if vrf_rid_auto_sel == "true": + cmd = "router-id auto-select" + else: + cmd = "undo router-id auto-select" + cmds.append(cmd) + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "router-id %s" % router_id + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % keepalive_time + + cmd = "timer keepalive %s" % keepalive_time + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "timer hold %s" % hold_time + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "timer min-holdtime %s" % min_hold_time + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "timer connect-retry %s" % conn_retry_time + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + if ebgp_if_sensitive == "true": + cmd = "ebgp-interface-sensitive" + else: + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp instance other failed.') + + return cmds + + def delete_bgp_instance_other_comm(self, **kwargs): + """ delete_bgp_instance_other_comm """ + + module = kwargs["module"] + conf_str = CE_DELETE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "undo router-id" + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + + cmd = "undo router-id vpn-instance auto-select" + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % keepalive_time + + cmd = "undo timer keepalive" + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "undo timer hold" + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "undo timer min-holdtime" + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "undo timer connect-retry" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % default_af_type + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + conf_str += CE_DELETE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete common vpn bgp instance other args failed.') + + return cmds + + def delete_instance_other_public(self, **kwargs): + """ delete_instance_other_public """ + + module = kwargs["module"] + conf_str = CE_MERGE_BGP_INSTANCE_HEADER + + vrf_name = module.params['vrf_name'] + conf_str += "%s" % vrf_name + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "" + + cmd = "undo router-id" + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % "false" + + cmd = "undo router-id vpn-instance auto-select" + cmds.append(cmd) + + keepalive_time = module.params['keepalive_time'] + if keepalive_time: + conf_str += "%s" % "60" + + cmd = "undo timer keepalive" + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % "180" + + cmd = "undo timer hold" + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % "0" + + cmd = "undo timer min-holdtime" + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % "32" + + cmd = "undo timer connect-retry" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % "true" + + cmd = "ebgp-interface-sensitive" + cmds.append(cmd) + + default_af_type = module.params['default_af_type'] + if default_af_type: + conf_str += "%s" % "ipv4uni" + + if vrf_name != "_public_": + if default_af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + else: + if vrf_name != "_public_": + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_INSTANCE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Delete default vpn bgp instance other args failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + as_number=dict(type='str'), + graceful_restart=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + time_wait_for_rib=dict(type='str'), + as_path_limit=dict(type='str'), + check_first_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + confed_id_number=dict(type='str'), + confed_nonstanded=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + bgp_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + keep_all_routes=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + memory_limit=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + gr_peer_reset=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_shutdown=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + suppress_interval=dict(type='str'), + hold_interval=dict(type='str'), + clear_interval=dict(type='str'), + confed_peer_as_num=dict(type='str'), + vrf_name=dict(type='str'), + vrf_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id=dict(type='str'), + keepalive_time=dict(type='str'), + hold_time=dict(type='str'), + min_hold_time=dict(type='str'), + conn_retry_time=dict(type='str'), + ebgp_if_sensitive=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_af_type=dict(type='str', choices=['ipv4uni', 'ipv6uni']) + ) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + as_number = module.params['as_number'] + graceful_restart = module.params['graceful_restart'] + time_wait_for_rib = module.params['time_wait_for_rib'] + as_path_limit = module.params['as_path_limit'] + check_first_as = module.params['check_first_as'] + confed_id_number = module.params['confed_id_number'] + confed_nonstanded = module.params['confed_nonstanded'] + bgp_rid_auto_sel = module.params['bgp_rid_auto_sel'] + keep_all_routes = module.params['keep_all_routes'] + memory_limit = module.params['memory_limit'] + gr_peer_reset = module.params['gr_peer_reset'] + is_shutdown = module.params['is_shutdown'] + suppress_interval = module.params['suppress_interval'] + hold_interval = module.params['hold_interval'] + clear_interval = module.params['clear_interval'] + confed_peer_as_num = module.params['confed_peer_as_num'] + router_id = module.params['router_id'] + vrf_name = module.params['vrf_name'] + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + keepalive_time = module.params['keepalive_time'] + hold_time = module.params['hold_time'] + min_hold_time = module.params['min_hold_time'] + conn_retry_time = module.params['conn_retry_time'] + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + default_af_type = module.params['default_af_type'] + + ce_bgp_obj = Bgp() + + if not ce_bgp_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if as_number: + proposed["as_number"] = as_number + if graceful_restart != 'no_use': + proposed["graceful_restart"] = graceful_restart + if time_wait_for_rib: + proposed["time_wait_for_rib"] = time_wait_for_rib + if as_path_limit: + proposed["as_path_limit"] = as_path_limit + if check_first_as != 'no_use': + proposed["check_first_as"] = check_first_as + if confed_id_number: + proposed["confed_id_number"] = confed_id_number + if confed_nonstanded != 'no_use': + proposed["confed_nonstanded"] = confed_nonstanded + if bgp_rid_auto_sel != 'no_use': + proposed["bgp_rid_auto_sel"] = bgp_rid_auto_sel + if keep_all_routes != 'no_use': + proposed["keep_all_routes"] = keep_all_routes + if memory_limit != 'no_use': + proposed["memory_limit"] = memory_limit + if gr_peer_reset != 'no_use': + proposed["gr_peer_reset"] = gr_peer_reset + if is_shutdown != 'no_use': + proposed["is_shutdown"] = is_shutdown + if suppress_interval: + proposed["suppress_interval"] = suppress_interval + if hold_interval: + proposed["hold_interval"] = hold_interval + if clear_interval: + proposed["clear_interval"] = clear_interval + if confed_peer_as_num: + proposed["confed_peer_as_num"] = confed_peer_as_num + if router_id: + proposed["router_id"] = router_id + if vrf_name: + proposed["vrf_name"] = vrf_name + if vrf_rid_auto_sel != 'no_use': + proposed["vrf_rid_auto_sel"] = vrf_rid_auto_sel + if keepalive_time: + proposed["keepalive_time"] = keepalive_time + if hold_time: + proposed["hold_time"] = hold_time + if min_hold_time: + proposed["min_hold_time"] = min_hold_time + if conn_retry_time: + proposed["conn_retry_time"] = conn_retry_time + if ebgp_if_sensitive != 'no_use': + proposed["ebgp_if_sensitive"] = ebgp_if_sensitive + if default_af_type: + proposed["default_af_type"] = default_af_type + + need_bgp_enable = check_bgp_enable_args(module=module) + need_bgp_enable_other_rst = ce_bgp_obj.check_bgp_enable_other_args( + module=module) + need_bgp_confed = check_bgp_confed_args(module=module) + need_bgp_instance = ce_bgp_obj.check_bgp_instance_args(module=module) + need_bgp_instance_other_rst = ce_bgp_obj.check_bgp_instance_other_args( + module=module) + + router_id_exist = ce_bgp_obj.get_bgp_instance(module=module) + existing["bgp instance"] = router_id_exist + + # bgp enable/disable + if need_bgp_enable: + + bgp_enable_exist = ce_bgp_obj.get_bgp_enable(module=module) + existing["bgp enable"] = bgp_enable_exist + if bgp_enable_exist: + asnumber_exist = bgp_enable_exist[0][1] + bgpenable_exist = bgp_enable_exist[0][0] + else: + asnumber_exist = None + bgpenable_exist = None + + if state == "present": + bgp_enable_new = ("true", as_number) + + if bgp_enable_new in bgp_enable_exist: + pass + elif bgpenable_exist == "true" and asnumber_exist != as_number: + module.fail_json( + msg='Error: BGP is already running. The AS is %s.' % asnumber_exist) + else: + cmd = ce_bgp_obj.merge_bgp_enable(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if need_bgp_enable_other_rst["need_cfg"] or need_bgp_confed or \ + need_bgp_instance_other_rst["need_cfg"] or need_bgp_instance: + pass + elif bgpenable_exist == "false": + pass + elif bgpenable_exist == "true" and asnumber_exist == as_number: + cmd = ce_bgp_obj.merge_bgp_enable(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + module.fail_json( + msg='Error: BGP is already running. The AS is %s.' % asnumber_exist) + + bgp_enable_end = ce_bgp_obj.get_bgp_enable(module=module) + end_state["bgp enable"] = bgp_enable_end + + # bgp enable/disable other args + exist_tmp = dict() + for item in need_bgp_enable_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_enable_other_rst[item] + + if exist_tmp: + existing["bgp enable other"] = exist_tmp + + if need_bgp_enable_other_rst["need_cfg"]: + if state == "present": + cmd = ce_bgp_obj.merge_bgp_enable_other(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_obj.delete_bgp_enable_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_enable_other_rst = ce_bgp_obj.check_bgp_enable_other_args( + module=module) + + end_tmp = dict() + for item in need_bgp_enable_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_enable_other_rst[item] + + if end_tmp: + end_state["bgp enable other"] = end_tmp + + # bgp confederation peer as + if need_bgp_confed: + confed_exist = ce_bgp_obj.get_bgp_confed_peer_as(module=module) + existing["confederation peer as"] = confed_exist + confed_new = (confed_peer_as_num) + + if state == "present": + if len(confed_exist) == 0: + cmd = ce_bgp_obj.create_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + elif confed_new not in confed_exist: + cmd = ce_bgp_obj.merge_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if len(confed_exist) == 0: + pass + + elif confed_new not in confed_exist: + pass + + else: + cmd = ce_bgp_obj.delete_bgp_confed_peer_as(module=module) + changed = True + for item in cmd: + updates.append(item) + + confed_end = ce_bgp_obj.get_bgp_confed_peer_as(module=module) + end_state["confederation peer as"] = confed_end + + # bgp instance + if need_bgp_instance and default_af_type != "ipv6uni": + router_id_new = vrf_name + + if state == "present": + if len(router_id_exist) == 0: + cmd = ce_bgp_obj.create_bgp_instance(module=module) + changed = True + updates.extend(cmd) + elif router_id_new not in router_id_exist: + cmd = ce_bgp_obj.merge_bgp_instance(module=module) + changed = True + updates.extend(cmd) + else: + if not need_bgp_instance_other_rst["need_cfg"]: + if vrf_name != "_public_": + if len(router_id_exist) == 0: + pass + elif router_id_new not in router_id_exist: + pass + else: + cmd = ce_bgp_obj.delete_bgp_instance(module=module) + changed = True + for item in cmd: + updates.append(item) + + # bgp instance other + exist_tmp = dict() + for item in need_bgp_instance_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_instance_other_rst[item] + + if exist_tmp: + existing["bgp instance other"] = exist_tmp + + if need_bgp_instance_other_rst["need_cfg"]: + if state == "present": + cmd = ce_bgp_obj.merge_bgp_instance_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if vrf_name == "_public_": + cmd = ce_bgp_obj.delete_instance_other_public( + module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_obj.delete_bgp_instance_other_comm(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_instance_other_rst = ce_bgp_obj.check_bgp_instance_other_args( + module=module) + + router_id_end = ce_bgp_obj.get_bgp_instance(module=module) + end_state["bgp instance"] = router_id_end + + end_tmp = dict() + for item in need_bgp_instance_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_instance_other_rst[item] + + if end_tmp: + end_state["bgp instance other"] = end_tmp + if end_state == existing: + changed = False + updates = list() + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_af.py new file mode 100644 index 00000000..26ac5cf1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_af.py @@ -0,0 +1,3430 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_af +short_description: Manages BGP Address-family configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP Address-family configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + The value is a string of 1 to 31 case-sensitive characters. + required: true + af_type: + description: + - Address family type of a BGP instance. + required: true + choices: ['ipv4uni','ipv4multi', 'ipv4vpn', 'ipv6uni', 'ipv6vpn', 'evpn'] + max_load_ibgp_num: + description: + - Specify the maximum number of equal-cost IBGP routes. + The value is an integer ranging from 1 to 65535. + ibgp_ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in IBGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself in + IBGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + max_load_ebgp_num: + description: + - Specify the maximum number of equal-cost EBGP routes. + The value is an integer ranging from 1 to 65535. + ebgp_ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in EBGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself in + EBGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + maximum_load_balance: + description: + - Specify the maximum number of equal-cost routes in the BGP routing table. + The value is an integer ranging from 1 to 65535. + ecmp_nexthop_changed: + description: + - If the value is true, the next hop of an advertised route is changed to the advertiser itself in BGP + load-balancing scenarios. + If the value is false, the next hop of an advertised route is not changed to the advertiser itself + in BGP load-balancing scenarios. + choices: ['no_use','true','false'] + default: no_use + default_local_pref: + description: + - Set the Local-Preference attribute. The value is an integer. + The value is an integer ranging from 0 to 4294967295. + default_med: + description: + - Specify the Multi-Exit-Discriminator (MED) of BGP routes. + The value is an integer ranging from 0 to 4294967295. + default_rt_import_enable: + description: + - If the value is true, importing default routes to the BGP routing table is allowed. + If the value is false, importing default routes to the BGP routing table is not allowed. + choices: ['no_use','true','false'] + default: no_use + router_id: + description: + - ID of a router that is in IPv4 address format. + The value is a string of 0 to 255 characters. + The value is in dotted decimal notation. + vrf_rid_auto_sel: + description: + - If the value is true, VPN BGP instances are enabled to automatically select router IDs. + If the value is false, VPN BGP instances are disabled from automatically selecting router IDs. + choices: ['no_use','true','false'] + default: no_use + nexthop_third_party: + description: + - If the value is true, the third-party next hop function is enabled. + If the value is false, the third-party next hop function is disabled. + choices: ['no_use','true','false'] + default: no_use + summary_automatic: + description: + - If the value is true, automatic aggregation is enabled for locally imported routes. + If the value is false, automatic aggregation is disabled for locally imported routes. + choices: ['no_use','true','false'] + default: no_use + auto_frr_enable: + description: + - If the value is true, BGP auto FRR is enabled. + If the value is false, BGP auto FRR is disabled. + choices: ['no_use','true','false'] + default: no_use + load_balancing_as_path_ignore: + description: + - Load balancing as path ignore. + choices: ['no_use','true','false'] + default: no_use + rib_only_enable: + description: + - If the value is true, BGP routes cannot be advertised to the IP routing table. + If the value is false, Routes preferred by BGP are advertised to the IP routing table. + choices: ['no_use','true','false'] + default: no_use + rib_only_policy_name: + description: + - Specify the name of a routing policy. + The value is a string of 1 to 40 characters. + active_route_advertise: + description: + - If the value is true, BGP is enabled to advertise only optimal routes in the RM to peers. + If the value is false, BGP is not enabled to advertise only optimal routes in the RM to peers. + choices: ['no_use','true','false'] + default: no_use + as_path_neglect: + description: + - If the value is true, the AS path attribute is ignored when BGP selects an optimal route. + If the value is false, the AS path attribute is not ignored when BGP selects an optimal route. + An AS path with a smaller length has a higher priority. + choices: ['no_use','true','false'] + default: no_use + med_none_as_maximum: + description: + - If the value is true, when BGP selects an optimal route, the system uses 4294967295 as the + MED value of a route if the route's attribute does not carry a MED value. + If the value is false, the system uses 0 as the MED value of a route if the route's attribute + does not carry a MED value. + choices: ['no_use','true','false'] + default: no_use + router_id_neglect: + description: + - If the value is true, the router ID attribute is ignored when BGP selects the optimal route. + If the value is false, the router ID attribute is not ignored when BGP selects the optimal route. + choices: ['no_use','true','false'] + default: no_use + igp_metric_ignore: + description: + - If the value is true, the metrics of next-hop IGP routes are not compared when BGP selects + an optimal route. + If the value is false, the metrics of next-hop IGP routes are not compared when BGP selects + an optimal route. + A route with a smaller metric has a higher priority. + choices: ['no_use','true','false'] + default: no_use + always_compare_med: + description: + - If the value is true, the MEDs of routes learned from peers in different autonomous systems + are compared when BGP selects an optimal route. + If the value is false, the MEDs of routes learned from peers in different autonomous systems + are not compared when BGP selects an optimal route. + choices: ['no_use','true','false'] + default: no_use + determin_med: + description: + - If the value is true, BGP deterministic-MED is enabled. + If the value is false, BGP deterministic-MED is disabled. + choices: ['no_use','true','false'] + default: no_use + preference_external: + description: + - Set the protocol priority of EBGP routes. + The value is an integer ranging from 1 to 255. + preference_internal: + description: + - Set the protocol priority of IBGP routes. + The value is an integer ranging from 1 to 255. + preference_local: + description: + - Set the protocol priority of a local BGP route. + The value is an integer ranging from 1 to 255. + prefrence_policy_name: + description: + - Set a routing policy to filter routes so that a configured priority is applied to + the routes that match the specified policy. + The value is a string of 1 to 40 characters. + reflect_between_client: + description: + - If the value is true, route reflection is enabled between clients. + If the value is false, route reflection is disabled between clients. + choices: ['no_use','true','false'] + default: no_use + reflector_cluster_id: + description: + - Set a cluster ID. Configuring multiple RRs in a cluster can enhance the stability of the network. + The value is an integer ranging from 1 to 4294967295. + reflector_cluster_ipv4: + description: + - Set a cluster ipv4 address. The value is expressed in the format of an IPv4 address. + rr_filter_number: + description: + - Set the number of the extended community filter supported by an RR group. + The value is a string of 1 to 51 characters. + policy_vpn_target: + description: + - If the value is true, VPN-Target filtering function is performed for received VPN routes. + If the value is false, VPN-Target filtering function is not performed for received VPN routes. + choices: ['no_use','true','false'] + default: no_use + next_hop_sel_depend_type: + description: + - Next hop select depend type. + choices: ['default','dependTunnel', 'dependIp'] + default: default + nhp_relay_route_policy_name: + description: + - Specify the name of a route-policy for route iteration. + The value is a string of 1 to 40 characters. + ebgp_if_sensitive: + description: + - If the value is true, after the fast EBGP interface awareness function is enabled, + EBGP sessions on an interface are deleted immediately when the interface goes Down. + If the value is false, after the fast EBGP interface awareness function is enabled, + EBGP sessions on an interface are not deleted immediately when the interface goes Down. + choices: ['no_use','true','false'] + default: no_use + reflect_chg_path: + description: + - If the value is true, the route reflector is enabled to modify route path attributes + based on an export policy. + If the value is false, the route reflector is disabled from modifying route path attributes + based on an export policy. + choices: ['no_use','true','false'] + default: no_use + add_path_sel_num: + description: + - Number of Add-Path routes. + The value is an integer ranging from 2 to 64. + route_sel_delay: + description: + - Route selection delay. + The value is an integer ranging from 0 to 3600. + allow_invalid_as: + description: + - Allow routes with BGP origin AS validation result Invalid to be selected. + If the value is true, invalid routes can participate in route selection. + If the value is false, invalid routes cannot participate in route selection. + choices: ['no_use','true','false'] + default: no_use + policy_ext_comm_enable: + description: + - If the value is true, modifying extended community attributes is allowed. + If the value is false, modifying extended community attributes is not allowed. + choices: ['no_use','true','false'] + default: no_use + supernet_uni_adv: + description: + - If the value is true, the function to advertise supernetwork unicast routes is enabled. + If the value is false, the function to advertise supernetwork unicast routes is disabled. + choices: ['no_use','true','false'] + default: no_use + supernet_label_adv: + description: + - If the value is true, the function to advertise supernetwork label is enabled. + If the value is false, the function to advertise supernetwork label is disabled. + choices: ['no_use','true','false'] + default: no_use + ingress_lsp_policy_name: + description: + - Ingress lsp policy name. + originator_prior: + description: + - Originator prior. + choices: ['no_use','true','false'] + default: no_use + lowest_priority: + description: + - If the value is true, enable reduce priority to advertise route. + If the value is false, disable reduce priority to advertise route. + choices: ['no_use','true','false'] + default: no_use + relay_delay_enable: + description: + - If the value is true, relay delay enable. + If the value is false, relay delay disable. + choices: ['no_use','true','false'] + default: no_use + import_protocol: + description: + - Routing protocol from which routes can be imported. + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'ospfv3', 'ripng'] + import_process_id: + description: + - Process ID of an imported routing protocol. + The value is an integer ranging from 0 to 4294967295. + network_address: + description: + - Specify the IP address advertised by BGP. + The value is a string of 0 to 255 characters. + mask_len: + description: + - Specify the mask length of an IP address. + The value is an integer ranging from 0 to 128. +''' + +EXAMPLES = ''' +- name: CloudEngine BGP address family test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + tasks: + - name: "Config BGP Address_Family" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + provider: "{{ cli }}" + - name: "Undo BGP Address_Family" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + provider: "{{ cli }}" + - name: "Config import route" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + import_protocol: ospf + import_process_id: 123 + provider: "{{ cli }}" + - name: "Undo import route" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + import_protocol: ospf + import_process_id: 123 + provider: "{{ cli }}" + - name: "Config network route" + community.network.ce_bgp_af: + state: present + vrf_name: js + af_type: ipv4uni + network_address: 1.1.1.1 + mask_len: 24 + provider: "{{ cli }}" + - name: "Undo network route" + community.network.ce_bgp_af: + state: absent + vrf_name: js + af_type: ipv4uni + network_address: 1.1.1.1 + mask_len: 24 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"af_type": "ipv4uni", + "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"af_type": "ipv4uni", "vrf_name": "js"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["ipv4-family vpn-instance js"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get bgp address family +CE_GET_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_GET_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# merge bgp address family +CE_MERGE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_MERGE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# create bgp address family +CE_CREATE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_CREATE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# delete bgp address family +CE_DELETE_BGP_ADDRESS_FAMILY_HEADER = """ + + + + + + %s + + + %s +""" +CE_DELETE_BGP_ADDRESS_FAMILY_TAIL = """ + + + + + + + +""" + +# get bgp import route +CE_GET_BGP_IMPORT_AND_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + + + + + + + + + + + + + + + + + +""" + +# merge bgp import route +CE_MERGE_BGP_IMPORT_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + %s + %s +""" +CE_MERGE_BGP_IMPORT_ROUTE_TAIL = """ + + + + + + + + + +""" + +# create bgp import route +CE_CREATE_BGP_IMPORT_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# delete bgp import route +CE_DELETE_BGP_IMPORT_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# get bgp network route +CE_GET_BGP_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + + +""" +CE_GET_BGP_NETWORK_ROUTE_TAIL = """ + + + + + + + + + +""" + +# merge bgp network route +CE_MERGE_BGP_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s + + + %s + %s +""" +CE_MERGE_BGP_NETWORK_ROUTE_TAIL = """ + + + + + + + + + +""" + +# create bgp network route +CE_CREATE_BGP_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# delete bgp network route +CE_DELETE_BGP_NETWORK_ROUTE = """ + + + + + + %s + + + %s + + + %s + %s + + + + + + + + + +""" + +# bgp import and network route header +CE_BGP_IMPORT_NETWORK_ROUTE_HEADER = """ + + + + + + %s + + + %s +""" +CE_BGP_IMPORT_NETWORK_ROUTE_TAIL = """ + + + + + + + +""" +CE_BGP_MERGE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_CREATE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_DELETE_IMPORT_UNIT = """ + + + %s + %s + + +""" +CE_BGP_MERGE_NETWORK_UNIT = """ + + + %s + %s + + +""" +CE_BGP_CREATE_NETWORK_UNIT = """ + + + %s + %s + + +""" +CE_BGP_DELETE_NETWORK_UNIT = """ + + + %s + %s + + +""" + + +class BgpAf(object): + """ Manages BGP Address-family configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_af_args(self, **kwargs): + """ check_bgp_af_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + else: + module.fail_json(msg='Error: Please input vrf_name.') + + state = module.params['state'] + af_type = module.params['af_type'] + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["af_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != af_type: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["af_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] == af_type: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_af_other_can_del(self, **kwargs): + """ check_bgp_af_other_can_del """ + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + router_id = module.params['router_id'] + if router_id: + if len(router_id) > 255: + module.fail_json( + msg='Error: The len of router_id %s is out of [0 - 255].' % router_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == router_id: + need_cfg = True + else: + pass + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != determin_med: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == determin_med: + need_cfg = True + else: + pass + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == ebgp_if_sensitive: + need_cfg = True + else: + pass + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] != relay_delay_enable: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + if re_find[0] == relay_delay_enable: + need_cfg = True + else: + pass + + result["need_cfg"] = need_cfg + return result + + def check_bgp_af_other_args(self, **kwargs): + """ check_bgp_af_other_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + max_load_ibgp_num = module.params['max_load_ibgp_num'] + if max_load_ibgp_num: + if int(max_load_ibgp_num) > 65535 or int(max_load_ibgp_num) < 1: + module.fail_json( + msg='Error: The value of max_load_ibgp_num %s is out of [1 - 65535].' % max_load_ibgp_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["max_load_ibgp_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != max_load_ibgp_num: + need_cfg = True + else: + need_cfg = True + + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + if ibgp_ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ibgp_ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ibgp_ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + max_load_ebgp_num = module.params['max_load_ebgp_num'] + if max_load_ebgp_num: + if int(max_load_ebgp_num) > 65535 or int(max_load_ebgp_num) < 1: + module.fail_json( + msg='Error: The value of max_load_ebgp_num %s is out of [1 - 65535].' % max_load_ebgp_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["max_load_ebgp_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != max_load_ebgp_num: + need_cfg = True + else: + need_cfg = True + + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + if ebgp_ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ebgp_ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + maximum_load_balance = module.params['maximum_load_balance'] + if maximum_load_balance: + if int(maximum_load_balance) > 65535 or int(maximum_load_balance) < 1: + module.fail_json( + msg='Error: The value of maximum_load_balance %s is out of [1 - 65535].' % maximum_load_balance) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["maximum_load_balance"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != maximum_load_balance: + need_cfg = True + else: + need_cfg = True + + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + if ecmp_nexthop_changed != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ecmp_nexthop_changed"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ecmp_nexthop_changed: + need_cfg = True + else: + need_cfg = True + + default_local_pref = module.params['default_local_pref'] + if default_local_pref: + if int(default_local_pref) < 0: + module.fail_json( + msg='Error: The value of default_local_pref %s is out of [0 - 4294967295].' % default_local_pref) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_local_pref"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_local_pref: + need_cfg = True + else: + need_cfg = True + + default_med = module.params['default_med'] + if default_med: + if int(default_med) < 0: + module.fail_json( + msg='Error: The value of default_med %s is out of [0 - 4294967295].' % default_med) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_med: + need_cfg = True + else: + need_cfg = True + + default_rt_import_enable = module.params['default_rt_import_enable'] + if default_rt_import_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_import_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != default_rt_import_enable: + need_cfg = True + else: + need_cfg = True + + router_id = module.params['router_id'] + if router_id: + if len(router_id) > 255: + module.fail_json( + msg='Error: The len of router_id %s is out of [0 - 255].' % router_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != router_id: + need_cfg = True + else: + need_cfg = True + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vrf_rid_auto_sel"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != vrf_rid_auto_sel: + need_cfg = True + else: + need_cfg = True + + nexthop_third_party = module.params['nexthop_third_party'] + if nexthop_third_party != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["nexthop_third_party"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != nexthop_third_party: + need_cfg = True + else: + need_cfg = True + + summary_automatic = module.params['summary_automatic'] + if summary_automatic != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["summary_automatic"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != summary_automatic: + need_cfg = True + else: + need_cfg = True + + auto_frr_enable = module.params['auto_frr_enable'] + if auto_frr_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["auto_frr_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != auto_frr_enable: + need_cfg = True + else: + need_cfg = True + + load_balancing_as_path_ignore = module.params['load_balancing_as_path_ignore'] + if load_balancing_as_path_ignore != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["load_balancing_as_path_ignore"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != load_balancing_as_path_ignore: + need_cfg = True + else: + need_cfg = True + + rib_only_enable = module.params['rib_only_enable'] + if rib_only_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rib_only_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rib_only_enable: + need_cfg = True + else: + need_cfg = True + + rib_only_policy_name = module.params['rib_only_policy_name'] + if rib_only_policy_name: + if len(rib_only_policy_name) > 40 or len(rib_only_policy_name) < 1: + module.fail_json( + msg='Error: The len of rib_only_policy_name %s is out of [1 - 40].' % rib_only_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rib_only_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rib_only_policy_name: + need_cfg = True + else: + need_cfg = True + + active_route_advertise = module.params['active_route_advertise'] + if active_route_advertise != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["active_route_advertise"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != active_route_advertise: + need_cfg = True + else: + need_cfg = True + + as_path_neglect = module.params['as_path_neglect'] + if as_path_neglect != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["as_path_neglect"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != as_path_neglect: + need_cfg = True + else: + need_cfg = True + + med_none_as_maximum = module.params['med_none_as_maximum'] + if med_none_as_maximum != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["med_none_as_maximum"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != med_none_as_maximum: + need_cfg = True + else: + need_cfg = True + + router_id_neglect = module.params['router_id_neglect'] + if router_id_neglect != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["router_id_neglect"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != router_id_neglect: + need_cfg = True + else: + need_cfg = True + + igp_metric_ignore = module.params['igp_metric_ignore'] + if igp_metric_ignore != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["igp_metric_ignore"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != igp_metric_ignore: + need_cfg = True + else: + need_cfg = True + + always_compare_med = module.params['always_compare_med'] + if always_compare_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["always_compare_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != always_compare_med: + need_cfg = True + else: + need_cfg = True + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["determin_med"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != determin_med: + need_cfg = True + else: + need_cfg = True + + preference_external = module.params['preference_external'] + if preference_external: + if int(preference_external) > 255 or int(preference_external) < 1: + module.fail_json( + msg='Error: The value of preference_external %s is out of [1 - 255].' % preference_external) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_external"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_external: + need_cfg = True + else: + need_cfg = True + + preference_internal = module.params['preference_internal'] + if preference_internal: + if int(preference_internal) > 255 or int(preference_internal) < 1: + module.fail_json( + msg='Error: The value of preference_internal %s is out of [1 - 255].' % preference_internal) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_internal"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_internal: + need_cfg = True + else: + need_cfg = True + + preference_local = module.params['preference_local'] + if preference_local: + if int(preference_local) > 255 or int(preference_local) < 1: + module.fail_json( + msg='Error: The value of preference_local %s is out of [1 - 255].' % preference_local) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preference_local"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != preference_local: + need_cfg = True + else: + need_cfg = True + + prefrence_policy_name = module.params['prefrence_policy_name'] + if prefrence_policy_name: + if len(prefrence_policy_name) > 40 or len(prefrence_policy_name) < 1: + module.fail_json( + msg='Error: The len of prefrence_policy_name %s is out of [1 - 40].' % prefrence_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prefrence_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != prefrence_policy_name: + need_cfg = True + else: + need_cfg = True + + reflect_between_client = module.params['reflect_between_client'] + if reflect_between_client != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_between_client"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflect_between_client: + need_cfg = True + else: + need_cfg = True + + reflector_cluster_id = module.params['reflector_cluster_id'] + if reflector_cluster_id: + if int(reflector_cluster_id) < 0: + module.fail_json( + msg='Error: The value of reflector_cluster_id %s is out of ' + '[1 - 4294967295].' % reflector_cluster_id) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflector_cluster_id"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflector_cluster_id: + need_cfg = True + else: + need_cfg = True + + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + if reflector_cluster_ipv4: + if len(reflector_cluster_ipv4) > 255: + module.fail_json( + msg='Error: The len of reflector_cluster_ipv4 %s is out of [0 - 255].' % reflector_cluster_ipv4) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflector_cluster_ipv4"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflector_cluster_ipv4: + need_cfg = True + else: + need_cfg = True + + rr_filter_number = module.params['rr_filter_number'] + if rr_filter_number: + if len(rr_filter_number) > 51 or len(rr_filter_number) < 1: + module.fail_json( + msg='Error: The len of rr_filter_number %s is out of [1 - 51].' % rr_filter_number) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rr_filter_number"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != rr_filter_number: + need_cfg = True + else: + need_cfg = True + + policy_vpn_target = module.params['policy_vpn_target'] + if policy_vpn_target != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["policy_vpn_target"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != policy_vpn_target: + need_cfg = True + else: + need_cfg = True + + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + if next_hop_sel_depend_type: + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["next_hop_sel_depend_type"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != next_hop_sel_depend_type: + need_cfg = True + else: + need_cfg = True + + nhp_relay_route_policy_name = module.params[ + 'nhp_relay_route_policy_name'] + if nhp_relay_route_policy_name: + if len(nhp_relay_route_policy_name) > 40 or len(nhp_relay_route_policy_name) < 1: + module.fail_json( + msg='Error: The len of nhp_relay_route_policy_name %s is ' + 'out of [1 - 40].' % nhp_relay_route_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + \ + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["nhp_relay_route_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != nhp_relay_route_policy_name: + need_cfg = True + else: + need_cfg = True + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_if_sensitive"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ebgp_if_sensitive: + need_cfg = True + else: + need_cfg = True + + reflect_chg_path = module.params['reflect_chg_path'] + if reflect_chg_path != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_chg_path"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != reflect_chg_path: + need_cfg = True + else: + need_cfg = True + + add_path_sel_num = module.params['add_path_sel_num'] + if add_path_sel_num: + if int(add_path_sel_num) > 64 or int(add_path_sel_num) < 2: + module.fail_json( + msg='Error: The value of add_path_sel_num %s is out of [2 - 64].' % add_path_sel_num) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["add_path_sel_num"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != add_path_sel_num: + need_cfg = True + else: + need_cfg = True + + route_sel_delay = module.params['route_sel_delay'] + if route_sel_delay: + if int(route_sel_delay) > 3600 or int(route_sel_delay) < 0: + module.fail_json( + msg='Error: The value of route_sel_delay %s is out of [0 - 3600].' % route_sel_delay) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_sel_delay"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != route_sel_delay: + need_cfg = True + else: + need_cfg = True + + allow_invalid_as = module.params['allow_invalid_as'] + if allow_invalid_as != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_invalid_as"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != allow_invalid_as: + need_cfg = True + else: + need_cfg = True + + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + if policy_ext_comm_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["policy_ext_comm_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != policy_ext_comm_enable: + need_cfg = True + else: + need_cfg = True + + supernet_uni_adv = module.params['supernet_uni_adv'] + if supernet_uni_adv != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["supernet_uni_adv"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != supernet_uni_adv: + need_cfg = True + else: + need_cfg = True + + supernet_label_adv = module.params['supernet_label_adv'] + if supernet_label_adv != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["supernet_label_adv"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != supernet_label_adv: + need_cfg = True + else: + need_cfg = True + + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + if ingress_lsp_policy_name: + if len(ingress_lsp_policy_name) > 40 or len(ingress_lsp_policy_name) < 1: + module.fail_json( + msg='Error: The len of ingress_lsp_policy_name %s is out of [1 - 40].' % ingress_lsp_policy_name) + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ingress_lsp_policy_name"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != ingress_lsp_policy_name: + need_cfg = True + else: + need_cfg = True + + originator_prior = module.params['originator_prior'] + if originator_prior != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["originator_prior"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != originator_prior: + need_cfg = True + else: + need_cfg = True + + lowest_priority = module.params['lowest_priority'] + if lowest_priority != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["lowest_priority"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != lowest_priority: + need_cfg = True + else: + need_cfg = True + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + + conf_str = CE_GET_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + \ + "" + CE_GET_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["relay_delay_enable"] = re_find + result["vrf_name"] = vrf_name + if re_find[0] != relay_delay_enable: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_import_network_route(self, **kwargs): + """ check_bgp_import_network_route """ + + module = kwargs["module"] + result = dict() + import_need_cfg = False + network_need_cfg = False + + vrf_name = module.params['vrf_name'] + + state = module.params['state'] + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol and (import_protocol != "direct" and import_protocol != "static"): + if not import_process_id: + module.fail_json( + msg='Error: Please input import_protocol and import_process_id value at the same time.') + else: + if int(import_process_id) < 0: + module.fail_json( + msg='Error: The value of import_process_id %s is out of [0 - 4294967295].' % import_process_id) + + if import_process_id: + if not import_protocol: + module.fail_json( + msg='Error: Please input import_protocol and import_process_id value at the same time.') + + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + if network_address: + if not mask_len: + module.fail_json( + msg='Error: Please input network_address and mask_len value at the same time.') + if mask_len: + if not network_address: + module.fail_json( + msg='Error: Please input network_address and mask_len value at the same time.') + + conf_str = CE_GET_BGP_IMPORT_AND_NETWORK_ROUTE % (vrf_name, af_type) + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if import_protocol: + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + else: + if not import_process_id or import_process_id == "0": + module.fail_json( + msg='Error: Please input import_process_id not 0 when import_protocol is ' + '[ospf, isis, rip, ospfv3, ripng].') + + bgp_import_route_new = (import_protocol, import_process_id) + + if state == "present": + if "" in recv_xml: + import_need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', + recv_xml) + + if re_find: + result["bgp_import_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_import_route_new not in re_find: + import_need_cfg = True + else: + import_need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', + recv_xml) + + if re_find: + result["bgp_import_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_import_route_new in re_find: + import_need_cfg = True + + if network_address and mask_len: + + bgp_network_route_new = (network_address, mask_len) + + if not check_ip_addr(ipaddr=network_address): + module.fail_json( + msg='Error: The network_address %s is invalid.' % network_address) + + if len(mask_len) > 128: + module.fail_json( + msg='Error: The len of mask_len %s is out of [0 - 128].' % mask_len) + + if state == "present": + if "" in recv_xml: + network_need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', recv_xml) + + if re_find: + result["bgp_network_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_network_route_new not in re_find: + network_need_cfg = True + else: + network_need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', recv_xml) + + if re_find: + result["bgp_network_route"] = re_find + result["vrf_name"] = vrf_name + if bgp_network_route_new in re_find: + network_need_cfg = True + + result["import_need_cfg"] = import_need_cfg + result["network_need_cfg"] = network_need_cfg + return result + + def merge_bgp_af(self, **kwargs): + """ merge_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp address family failed.') + + cmds = [] + + cmd = "ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def create_bgp_af(self, **kwargs): + """ create_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_CREATE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_CREATE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp address family failed.') + + cmds = [] + + cmd = "ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def delete_bgp_af(self, **kwargs): + """ delete_bgp_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + + conf_str = CE_DELETE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + CE_DELETE_BGP_ADDRESS_FAMILY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp address family failed.') + + cmds = [] + + cmd = "undo ipv4-family vpn-instance %s" % vrf_name + + if af_type == "ipv4multi": + cmd = "undo ipv4-family multicast" + elif af_type == "ipv4vpn": + cmd = "undo ipv4-family vpnv4" + elif af_type == "ipv6uni": + cmd = "undo ipv6-family vpn-instance %s" % vrf_name + if vrf_name == "_public_": + cmd = "undo ipv6-family unicast" + elif af_type == "ipv6vpn": + cmd = "undo ipv6-family vpnv6" + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + cmds.append(cmd) + + return cmds + + def merge_bgp_af_other(self, **kwargs): + """ merge_bgp_af_other """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + + cmds = [] + + max_load_ibgp_num = module.params['max_load_ibgp_num'] + if max_load_ibgp_num: + conf_str += "%s" % max_load_ibgp_num + + cmd = "maximum load-balancing ibgp %s" % max_load_ibgp_num + cmds.append(cmd) + + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + if ibgp_ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ibgp_ecmp_nexthop_changed + + if ibgp_ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing ibgp %s ecmp-nexthop-changed" % max_load_ibgp_num + cmds.append(cmd) + else: + cmd = "undo maximum load-balancing ibgp %s ecmp-nexthop-changed" % max_load_ibgp_num + cmds.append(cmd) + max_load_ebgp_num = module.params['max_load_ebgp_num'] + if max_load_ebgp_num: + conf_str += "%s" % max_load_ebgp_num + + cmd = "maximum load-balancing ebgp %s" % max_load_ebgp_num + cmds.append(cmd) + + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + if ebgp_ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ebgp_ecmp_nexthop_changed + + if ebgp_ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing ebgp %s ecmp-nexthop-changed" % max_load_ebgp_num + else: + cmd = "undo maximum load-balancing ebgp %s ecmp-nexthop-changed" % max_load_ebgp_num + cmds.append(cmd) + + maximum_load_balance = module.params['maximum_load_balance'] + if maximum_load_balance: + conf_str += "%s" % maximum_load_balance + + cmd = "maximum load-balancing %s" % maximum_load_balance + cmds.append(cmd) + + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + if ecmp_nexthop_changed != 'no_use': + conf_str += "%s" % ecmp_nexthop_changed + + if ecmp_nexthop_changed == "true": + cmd = "maximum load-balancing %s ecmp-nexthop-changed" % maximum_load_balance + else: + cmd = "undo maximum load-balancing %s ecmp-nexthop-changed" % maximum_load_balance + cmds.append(cmd) + + default_local_pref = module.params['default_local_pref'] + if default_local_pref: + conf_str += "%s" % default_local_pref + + cmd = "default local-preference %s" % default_local_pref + cmds.append(cmd) + + default_med = module.params['default_med'] + if default_med: + conf_str += "%s" % default_med + + cmd = "default med %s" % default_med + cmds.append(cmd) + + default_rt_import_enable = module.params['default_rt_import_enable'] + if default_rt_import_enable != 'no_use': + conf_str += "%s" % default_rt_import_enable + + if default_rt_import_enable == "true": + cmd = "default-route imported" + else: + cmd = "undo default-route imported" + cmds.append(cmd) + + router_id = module.params['router_id'] + if router_id: + conf_str += "%s" % router_id + + cmd = "router-id %s" % router_id + cmds.append(cmd) + + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + if vrf_rid_auto_sel != 'no_use': + conf_str += "%s" % vrf_rid_auto_sel + family = "ipv4-family" + if af_type == "ipv6uni": + family = "ipv6-family" + if vrf_rid_auto_sel == "true": + cmd = "%s vpn-instance %s" % (family, vrf_name) + cmds.append(cmd) + cmd = "router-id auto-select" + cmds.append(cmd) + else: + cmd = "%s vpn-instance %s" % (family, vrf_name) + cmds.append(cmd) + cmd = "undo router-id auto-select" + cmds.append(cmd) + + nexthop_third_party = module.params['nexthop_third_party'] + if nexthop_third_party != 'no_use': + conf_str += "%s" % nexthop_third_party + + if nexthop_third_party == "true": + cmd = "nexthop third-party" + else: + cmd = "undo nexthop third-party" + cmds.append(cmd) + + summary_automatic = module.params['summary_automatic'] + if summary_automatic != 'no_use': + conf_str += "%s" % summary_automatic + + if summary_automatic == "true": + cmd = "summary automatic" + else: + cmd = "undo summary automatic" + cmds.append(cmd) + + auto_frr_enable = module.params['auto_frr_enable'] + if auto_frr_enable != 'no_use': + conf_str += "%s" % auto_frr_enable + + if auto_frr_enable == "true": + cmd = "auto-frr" + else: + cmd = "undo auto-frr" + cmds.append(cmd) + + load_balancing_as_path_ignore = module.params[ + 'load_balancing_as_path_ignore'] + if load_balancing_as_path_ignore != 'no_use': + conf_str += "%s" % load_balancing_as_path_ignore + + if load_balancing_as_path_ignore == "true": + cmd = "load-balancing as-path-ignore" + else: + cmd = "undo load-balancing as-path-ignore" + cmds.append(cmd) + + rib_only_enable = module.params['rib_only_enable'] + if rib_only_enable != 'no_use': + conf_str += "%s" % rib_only_enable + + if rib_only_enable == "true": + cmd = "routing-table rib-only" + else: + cmd = "undo routing-table rib-only" + cmds.append(cmd) + + rib_only_policy_name = module.params['rib_only_policy_name'] + if rib_only_policy_name and rib_only_enable == "true": + conf_str += "%s" % rib_only_policy_name + + cmd = "routing-table rib-only route-policy %s" % rib_only_policy_name + cmds.append(cmd) + + active_route_advertise = module.params['active_route_advertise'] + if active_route_advertise != 'no_use': + conf_str += "%s" % active_route_advertise + + if active_route_advertise == "true": + cmd = "active-route-advertise" + else: + cmd = "undo active-route-advertise" + cmds.append(cmd) + + as_path_neglect = module.params['as_path_neglect'] + if as_path_neglect != 'no_use': + conf_str += "%s" % as_path_neglect + + if as_path_neglect == "true": + cmd = "bestroute as-path-ignore" + else: + cmd = "undo bestroute as-path-ignore" + cmds.append(cmd) + + med_none_as_maximum = module.params['med_none_as_maximum'] + if med_none_as_maximum != 'no_use': + conf_str += "%s" % med_none_as_maximum + + if med_none_as_maximum == "true": + cmd = "bestroute med-none-as-maximum" + else: + cmd = "undo bestroute med-none-as-maximum" + cmds.append(cmd) + + router_id_neglect = module.params['router_id_neglect'] + if router_id_neglect != 'no_use': + conf_str += "%s" % router_id_neglect + + if router_id_neglect == "true": + cmd = "bestroute router-id-ignore" + else: + cmd = "undo bestroute router-id-ignore" + cmds.append(cmd) + + igp_metric_ignore = module.params['igp_metric_ignore'] + if igp_metric_ignore != 'no_use': + conf_str += "%s" % igp_metric_ignore + + if igp_metric_ignore == "true": + cmd = "bestroute igp-metric-ignore" + cmds.append(cmd) + else: + cmd = "undo bestroute igp-metric-ignore" + cmds.append(cmd) + always_compare_med = module.params['always_compare_med'] + if always_compare_med != 'no_use': + conf_str += "%s" % always_compare_med + + if always_compare_med == "true": + cmd = "compare-different-as-med" + cmds.append(cmd) + else: + cmd = "undo compare-different-as-med" + cmds.append(cmd) + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + conf_str += "%s" % determin_med + + if determin_med == "true": + cmd = "deterministic-med" + cmds.append(cmd) + else: + cmd = "undo deterministic-med" + cmds.append(cmd) + + preference_external = module.params['preference_external'] + preference_internal = module.params['preference_internal'] + preference_local = module.params['preference_local'] + if any([preference_external, preference_internal, preference_local]): + preference_external = preference_external or "255" + preference_internal = preference_internal or "255" + preference_local = preference_local or "255" + + conf_str += "%s" % preference_external + conf_str += "%s" % preference_internal + conf_str += "%s" % preference_local + + cmd = "preference %s %s %s" % ( + preference_external, preference_internal, preference_local) + cmds.append(cmd) + + prefrence_policy_name = module.params['prefrence_policy_name'] + if prefrence_policy_name: + conf_str += "%s" % prefrence_policy_name + + cmd = "preference route-policy %s" % prefrence_policy_name + cmds.append(cmd) + + reflect_between_client = module.params['reflect_between_client'] + if reflect_between_client != 'no_use': + conf_str += "%s" % reflect_between_client + + if reflect_between_client == "true": + cmd = "reflect between-clients" + else: + cmd = "undo reflect between-clients" + cmds.append(cmd) + + reflector_cluster_id = module.params['reflector_cluster_id'] + if reflector_cluster_id: + conf_str += "%s" % reflector_cluster_id + + cmd = "reflector cluster-id %s" % reflector_cluster_id + cmds.append(cmd) + + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + if reflector_cluster_ipv4: + conf_str += "%s" % reflector_cluster_ipv4 + + cmd = "reflector cluster-id %s" % reflector_cluster_ipv4 + cmds.append(cmd) + + rr_filter_number = module.params['rr_filter_number'] + if rr_filter_number: + conf_str += "%s" % rr_filter_number + cmd = 'rr-filter %s' % rr_filter_number + cmds.append(cmd) + + policy_vpn_target = module.params['policy_vpn_target'] + if policy_vpn_target != 'no_use': + conf_str += "%s" % policy_vpn_target + if policy_vpn_target == 'true': + cmd = 'policy vpn-target' + else: + cmd = 'undo policy vpn-target' + cmds.append(cmd) + + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + if next_hop_sel_depend_type: + conf_str += "%s" % next_hop_sel_depend_type + + nhp_relay_route_policy_name = module.params[ + 'nhp_relay_route_policy_name'] + if nhp_relay_route_policy_name: + conf_str += "%s" % nhp_relay_route_policy_name + + cmd = "nexthop recursive-lookup route-policy %s" % nhp_relay_route_policy_name + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "%s" % ebgp_if_sensitive + + if ebgp_if_sensitive == "true": + cmd = "ebgp-interface-sensitive" + else: + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + reflect_chg_path = module.params['reflect_chg_path'] + if reflect_chg_path != 'no_use': + conf_str += "%s" % reflect_chg_path + + if reflect_chg_path == "true": + cmd = "reflect change-path-attribute" + else: + cmd = "undo reflect change-path-attribute" + cmds.append(cmd) + + add_path_sel_num = module.params['add_path_sel_num'] + if add_path_sel_num: + conf_str += "%s" % add_path_sel_num + + cmd = "bestroute add-path path-number %s" % add_path_sel_num + cmds.append(cmd) + + route_sel_delay = module.params['route_sel_delay'] + if route_sel_delay: + conf_str += "%s" % route_sel_delay + + cmd = "route-select delay %s" % route_sel_delay + cmds.append(cmd) + + allow_invalid_as = module.params['allow_invalid_as'] + if allow_invalid_as != 'no_use': + conf_str += "%s" % allow_invalid_as + + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + if policy_ext_comm_enable != 'no_use': + conf_str += "%s" % policy_ext_comm_enable + + if policy_ext_comm_enable == "true": + cmd = "ext-community-change enable" + else: + cmd = "undo ext-community-change enable" + cmds.append(cmd) + + supernet_uni_adv = module.params['supernet_uni_adv'] + if supernet_uni_adv != 'no_use': + conf_str += "%s" % supernet_uni_adv + + if supernet_uni_adv == "true": + cmd = "supernet unicast advertise enable" + else: + cmd = "undo supernet unicast advertise enable" + cmds.append(cmd) + + supernet_label_adv = module.params['supernet_label_adv'] + if supernet_label_adv != 'no_use': + conf_str += "%s" % supernet_label_adv + + if supernet_label_adv == "true": + cmd = "supernet label-route advertise enable" + else: + cmd = "undo supernet label-route advertise enable" + cmds.append(cmd) + + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + if ingress_lsp_policy_name: + conf_str += "%s" % ingress_lsp_policy_name + cmd = "ingress-lsp trigger route-policy %s" % ingress_lsp_policy_name + cmds.append(cmd) + + originator_prior = module.params['originator_prior'] + if originator_prior != 'no_use': + conf_str += "%s" % originator_prior + if originator_prior == "true": + cmd = "bestroute routerid-prior-clusterlist" + else: + cmd = "undo bestroute routerid-prior-clusterlist" + cmds.append(cmd) + + lowest_priority = module.params['lowest_priority'] + if lowest_priority != 'no_use': + conf_str += "%s" % lowest_priority + + if lowest_priority == "true": + cmd = "advertise lowest-priority on-startup" + else: + cmd = "undo advertise lowest-priority on-startup" + cmds.append(cmd) + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + conf_str += "%s" % relay_delay_enable + + if relay_delay_enable == "true": + cmd = "nexthop recursive-lookup restrain enable" + else: + cmd = "nexthop recursive-lookup restrain disable" + cmds.append(cmd) + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge bgp address family other agrus failed.') + + return cmds + + def delete_bgp_af_other(self, **kwargs): + """ delete_bgp_af_other """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % (vrf_name, af_type) + + cmds = [] + + router_id = module.params['router_id'] + if router_id: + conf_str += "" + + cmd = "undo router-id %s" % router_id + cmds.append(cmd) + + determin_med = module.params['determin_med'] + if determin_med != 'no_use': + conf_str += "" + + cmd = "undo deterministic-med" + cmds.append(cmd) + + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + if ebgp_if_sensitive != 'no_use': + conf_str += "" + + cmd = "undo ebgp-interface-sensitive" + cmds.append(cmd) + + relay_delay_enable = module.params['relay_delay_enable'] + if relay_delay_enable != 'no_use': + conf_str += "" + + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Merge bgp address family other agrus failed.') + + return cmds + + def merge_bgp_import_route(self, **kwargs): + """ merge_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_MERGE_BGP_IMPORT_ROUTE_HEADER % ( + vrf_name, af_type, import_protocol, import_process_id) + CE_MERGE_BGP_IMPORT_ROUTE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp import route failed.') + + cmds = [] + cmd = "import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def create_bgp_import_route(self, **kwargs): + """ create_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_CREATE_BGP_IMPORT_ROUTE % ( + vrf_name, af_type, import_protocol, import_process_id) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp import route failed.') + + cmds = [] + cmd = "import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def delete_bgp_import_route(self, **kwargs): + """ delete_bgp_import_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + + if import_protocol == "direct" or import_protocol == "static": + import_process_id = "0" + + conf_str = CE_DELETE_BGP_IMPORT_ROUTE % ( + vrf_name, af_type, import_protocol, import_process_id) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp import route failed.') + + cmds = [] + cmd = "undo import-route %s %s" % (import_protocol, import_process_id) + if import_protocol == "direct" or import_protocol == "static": + cmd = "undo import-route %s" % import_protocol + cmds.append(cmd) + + return cmds + + def merge_bgp_network_route(self, **kwargs): + """ merge_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_MERGE_BGP_NETWORK_ROUTE_HEADER % ( + vrf_name, af_type, network_address, mask_len) + CE_MERGE_BGP_NETWORK_ROUTE_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp network route failed.') + + cmds = [] + cmd = "network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + def create_bgp_network_route(self, **kwargs): + """ create_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_CREATE_BGP_NETWORK_ROUTE % ( + vrf_name, af_type, network_address, mask_len) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp network route failed.') + + cmds = [] + cmd = "network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + def delete_bgp_network_route(self, **kwargs): + """ delete_bgp_network_route """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + af_type = module.params['af_type'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + conf_str = CE_DELETE_BGP_NETWORK_ROUTE % ( + vrf_name, af_type, network_address, mask_len) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp network route failed.') + + cmds = [] + cmd = "undo network %s %s" % (network_address, mask_len) + cmds.append(cmd) + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + af_type=dict(choices=['ipv4uni', 'ipv4multi', 'ipv4vpn', + 'ipv6uni', 'ipv6vpn', 'evpn'], required=True), + max_load_ibgp_num=dict(type='str'), + ibgp_ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + max_load_ebgp_num=dict(type='str'), + ebgp_ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + maximum_load_balance=dict(type='str'), + ecmp_nexthop_changed=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_local_pref=dict(type='str'), + default_med=dict(type='str'), + default_rt_import_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id=dict(type='str'), + vrf_rid_auto_sel=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + nexthop_third_party=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + summary_automatic=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + auto_frr_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + load_balancing_as_path_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rib_only_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rib_only_policy_name=dict(type='str'), + active_route_advertise=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + as_path_neglect=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + med_none_as_maximum=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + router_id_neglect=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + igp_metric_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + always_compare_med=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + determin_med=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + preference_external=dict(type='str'), + preference_internal=dict(type='str'), + preference_local=dict(type='str'), + prefrence_policy_name=dict(type='str'), + reflect_between_client=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + reflector_cluster_id=dict(type='str'), + reflector_cluster_ipv4=dict(type='str'), + rr_filter_number=dict(type='str'), + policy_vpn_target=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + next_hop_sel_depend_type=dict( + choices=['default', 'dependTunnel', 'dependIp']), + nhp_relay_route_policy_name=dict(type='str'), + ebgp_if_sensitive=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + reflect_chg_path=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + add_path_sel_num=dict(type='str'), + route_sel_delay=dict(type='str'), + allow_invalid_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + policy_ext_comm_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + supernet_uni_adv=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + supernet_label_adv=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + ingress_lsp_policy_name=dict(type='str'), + originator_prior=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + lowest_priority=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + relay_delay_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + import_protocol=dict( + choices=['direct', 'ospf', 'isis', 'static', 'rip', 'ospfv3', 'ripng']), + import_process_id=dict(type='str'), + network_address=dict(type='str'), + mask_len=dict(type='str')) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + max_load_ibgp_num = module.params['max_load_ibgp_num'] + ibgp_ecmp_nexthop_changed = module.params['ibgp_ecmp_nexthop_changed'] + max_load_ebgp_num = module.params['max_load_ebgp_num'] + ebgp_ecmp_nexthop_changed = module.params['ebgp_ecmp_nexthop_changed'] + maximum_load_balance = module.params['maximum_load_balance'] + ecmp_nexthop_changed = module.params['ecmp_nexthop_changed'] + default_local_pref = module.params['default_local_pref'] + default_med = module.params['default_med'] + default_rt_import_enable = module.params['default_rt_import_enable'] + router_id = module.params['router_id'] + vrf_rid_auto_sel = module.params['vrf_rid_auto_sel'] + nexthop_third_party = module.params['nexthop_third_party'] + summary_automatic = module.params['summary_automatic'] + auto_frr_enable = module.params['auto_frr_enable'] + load_balancing_as_path_ignore = module.params[ + 'load_balancing_as_path_ignore'] + rib_only_enable = module.params['rib_only_enable'] + rib_only_policy_name = module.params['rib_only_policy_name'] + active_route_advertise = module.params['active_route_advertise'] + as_path_neglect = module.params['as_path_neglect'] + med_none_as_maximum = module.params['med_none_as_maximum'] + router_id_neglect = module.params['router_id_neglect'] + igp_metric_ignore = module.params['igp_metric_ignore'] + always_compare_med = module.params['always_compare_med'] + determin_med = module.params['determin_med'] + preference_external = module.params['preference_external'] + preference_internal = module.params['preference_internal'] + preference_local = module.params['preference_local'] + prefrence_policy_name = module.params['prefrence_policy_name'] + reflect_between_client = module.params['reflect_between_client'] + reflector_cluster_id = module.params['reflector_cluster_id'] + reflector_cluster_ipv4 = module.params['reflector_cluster_ipv4'] + rr_filter_number = module.params['rr_filter_number'] + policy_vpn_target = module.params['policy_vpn_target'] + next_hop_sel_depend_type = module.params['next_hop_sel_depend_type'] + nhp_relay_route_policy_name = module.params['nhp_relay_route_policy_name'] + ebgp_if_sensitive = module.params['ebgp_if_sensitive'] + reflect_chg_path = module.params['reflect_chg_path'] + add_path_sel_num = module.params['add_path_sel_num'] + route_sel_delay = module.params['route_sel_delay'] + allow_invalid_as = module.params['allow_invalid_as'] + policy_ext_comm_enable = module.params['policy_ext_comm_enable'] + supernet_uni_adv = module.params['supernet_uni_adv'] + supernet_label_adv = module.params['supernet_label_adv'] + ingress_lsp_policy_name = module.params['ingress_lsp_policy_name'] + originator_prior = module.params['originator_prior'] + lowest_priority = module.params['lowest_priority'] + relay_delay_enable = module.params['relay_delay_enable'] + import_protocol = module.params['import_protocol'] + import_process_id = module.params['import_process_id'] + network_address = module.params['network_address'] + mask_len = module.params['mask_len'] + + ce_bgp_af_obj = BgpAf() + + if not ce_bgp_af_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if af_type: + proposed["af_type"] = af_type + if max_load_ibgp_num: + proposed["max_load_ibgp_num"] = max_load_ibgp_num + if ibgp_ecmp_nexthop_changed != 'no_use': + proposed["ibgp_ecmp_nexthop_changed"] = ibgp_ecmp_nexthop_changed + if max_load_ebgp_num: + proposed["max_load_ebgp_num"] = max_load_ebgp_num + if ebgp_ecmp_nexthop_changed != 'no_use': + proposed["ebgp_ecmp_nexthop_changed"] = ebgp_ecmp_nexthop_changed + if maximum_load_balance: + proposed["maximum_load_balance"] = maximum_load_balance + if ecmp_nexthop_changed != 'no_use': + proposed["ecmp_nexthop_changed"] = ecmp_nexthop_changed + if default_local_pref: + proposed["default_local_pref"] = default_local_pref + if default_med: + proposed["default_med"] = default_med + if default_rt_import_enable != 'no_use': + proposed["default_rt_import_enable"] = default_rt_import_enable + if router_id: + proposed["router_id"] = router_id + if vrf_rid_auto_sel != 'no_use': + proposed["vrf_rid_auto_sel"] = vrf_rid_auto_sel + if nexthop_third_party != 'no_use': + proposed["nexthop_third_party"] = nexthop_third_party + if summary_automatic != 'no_use': + proposed["summary_automatic"] = summary_automatic + if auto_frr_enable != 'no_use': + proposed["auto_frr_enable"] = auto_frr_enable + if load_balancing_as_path_ignore != 'no_use': + proposed["load_balancing_as_path_ignore"] = load_balancing_as_path_ignore + if rib_only_enable != 'no_use': + proposed["rib_only_enable"] = rib_only_enable + if rib_only_policy_name: + proposed["rib_only_policy_name"] = rib_only_policy_name + if active_route_advertise != 'no_use': + proposed["active_route_advertise"] = active_route_advertise + if as_path_neglect != 'no_use': + proposed["as_path_neglect"] = as_path_neglect + if med_none_as_maximum != 'no_use': + proposed["med_none_as_maximum"] = med_none_as_maximum + if router_id_neglect != 'no_use': + proposed["router_id_neglect"] = router_id_neglect + if igp_metric_ignore != 'no_use': + proposed["igp_metric_ignore"] = igp_metric_ignore + if always_compare_med != 'no_use': + proposed["always_compare_med"] = always_compare_med + if determin_med != 'no_use': + proposed["determin_med"] = determin_med + if preference_external: + proposed["preference_external"] = preference_external + if preference_internal: + proposed["preference_internal"] = preference_internal + if preference_local: + proposed["preference_local"] = preference_local + if prefrence_policy_name: + proposed["prefrence_policy_name"] = prefrence_policy_name + if reflect_between_client != 'no_use': + proposed["reflect_between_client"] = reflect_between_client + if reflector_cluster_id: + proposed["reflector_cluster_id"] = reflector_cluster_id + if reflector_cluster_ipv4: + proposed["reflector_cluster_ipv4"] = reflector_cluster_ipv4 + if rr_filter_number: + proposed["rr_filter_number"] = rr_filter_number + if policy_vpn_target != 'no_use': + proposed["policy_vpn_target"] = policy_vpn_target + if next_hop_sel_depend_type: + proposed["next_hop_sel_depend_type"] = next_hop_sel_depend_type + if nhp_relay_route_policy_name: + proposed["nhp_relay_route_policy_name"] = nhp_relay_route_policy_name + if ebgp_if_sensitive != 'no_use': + proposed["ebgp_if_sensitive"] = ebgp_if_sensitive + if reflect_chg_path != 'no_use': + proposed["reflect_chg_path"] = reflect_chg_path + if add_path_sel_num: + proposed["add_path_sel_num"] = add_path_sel_num + if route_sel_delay: + proposed["route_sel_delay"] = route_sel_delay + if allow_invalid_as != 'no_use': + proposed["allow_invalid_as"] = allow_invalid_as + if policy_ext_comm_enable != 'no_use': + proposed["policy_ext_comm_enable"] = policy_ext_comm_enable + if supernet_uni_adv != 'no_use': + proposed["supernet_uni_adv"] = supernet_uni_adv + if supernet_label_adv != 'no_use': + proposed["supernet_label_adv"] = supernet_label_adv + if ingress_lsp_policy_name: + proposed["ingress_lsp_policy_name"] = ingress_lsp_policy_name + if originator_prior != 'no_use': + proposed["originator_prior"] = originator_prior + if lowest_priority != 'no_use': + proposed["lowest_priority"] = lowest_priority + if relay_delay_enable != 'no_use': + proposed["relay_delay_enable"] = relay_delay_enable + if import_protocol: + proposed["import_protocol"] = import_protocol + if import_process_id: + proposed["import_process_id"] = import_process_id + if network_address: + proposed["network_address"] = network_address + if mask_len: + proposed["mask_len"] = mask_len + + bgp_af_rst = ce_bgp_af_obj.check_bgp_af_args(module=module) + bgp_af_other_rst = ce_bgp_af_obj.check_bgp_af_other_args(module=module) + bgp_af_other_can_del_rst = ce_bgp_af_obj.check_bgp_af_other_can_del( + module=module) + bgp_import_network_route_rst = ce_bgp_af_obj.check_bgp_import_network_route( + module=module) + + # state exist bgp address family config + exist_tmp = dict() + for item in bgp_af_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_af_rst[item] + + if exist_tmp: + existing["bgp af"] = exist_tmp + # state exist bgp address family other config + exist_tmp = dict() + for item in bgp_af_other_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_af_other_rst[item] + if exist_tmp: + existing["bgp af other"] = exist_tmp + # state exist bgp import route config + exist_tmp = dict() + for item in bgp_import_network_route_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_import_network_route_rst[item] + + if exist_tmp: + existing["bgp import & network route"] = exist_tmp + + if state == "present": + if bgp_af_rst["need_cfg"] and bgp_import_network_route_rst["import_need_cfg"] and \ + bgp_import_network_route_rst["network_need_cfg"]: + changed = True + if "af_type" in bgp_af_rst.keys(): + conf_str = CE_MERGE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + else: + conf_str = CE_CREATE_BGP_ADDRESS_FAMILY_HEADER % ( + vrf_name, af_type) + + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_IMPORT_UNIT % ( + import_protocol, import_process_id) + else: + conf_str += CE_BGP_CREATE_IMPORT_UNIT % ( + import_protocol, import_process_id) + + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_NETWORK_UNIT % ( + network_address, mask_len) + else: + conf_str += CE_BGP_CREATE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_MERGE_BGP_ADDRESS_FAMILY_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Present bgp af_type import and network route failed.') + + cmd = "import-route %s %s" % (import_protocol, import_process_id) + updates.append(cmd) + cmd = "network %s %s" % (network_address, mask_len) + updates.append(cmd) + + elif bgp_import_network_route_rst["import_need_cfg"] and bgp_import_network_route_rst["network_need_cfg"]: + changed = True + conf_str = CE_BGP_IMPORT_NETWORK_ROUTE_HEADER % (vrf_name, af_type) + + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_IMPORT_UNIT % ( + import_protocol, import_process_id) + else: + conf_str += CE_BGP_CREATE_IMPORT_UNIT % ( + import_protocol, import_process_id) + + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + conf_str += CE_BGP_MERGE_NETWORK_UNIT % ( + network_address, mask_len) + else: + conf_str += CE_BGP_CREATE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_BGP_IMPORT_NETWORK_ROUTE_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Present bgp import and network route failed.') + + cmd = "import-route %s %s" % (import_protocol, import_process_id) + updates.append(cmd) + cmd = "network %s %s" % (network_address, mask_len) + updates.append(cmd) + + else: + if bgp_af_rst["need_cfg"]: + if "af_type" in bgp_af_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_rst["need_cfg"]: + cmd = ce_bgp_af_obj.merge_bgp_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["import_need_cfg"]: + if "bgp_import_route" in bgp_import_network_route_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["network_need_cfg"]: + if "bgp_network_route" in bgp_import_network_route_rst.keys(): + cmd = ce_bgp_af_obj.merge_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_af_obj.create_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if bgp_import_network_route_rst["import_need_cfg"] and bgp_import_network_route_rst["network_need_cfg"]: + changed = True + conf_str = CE_BGP_IMPORT_NETWORK_ROUTE_HEADER % (vrf_name, af_type) + conf_str += CE_BGP_DELETE_IMPORT_UNIT % ( + import_protocol, import_process_id) + conf_str += CE_BGP_DELETE_NETWORK_UNIT % ( + network_address, mask_len) + + conf_str += CE_BGP_IMPORT_NETWORK_ROUTE_TAIL + recv_xml = ce_bgp_af_obj.netconf_set_config( + module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json( + msg='Error: Absent bgp import and network route failed.') + + cmd = "undo import-route %s %s" % (import_protocol, + import_process_id) + updates.append(cmd) + cmd = "undo network %s %s" % (network_address, mask_len) + updates.append(cmd) + + else: + if bgp_import_network_route_rst["import_need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_import_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_import_network_route_rst["network_need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_network_route(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_can_del_rst["need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_rst["need_cfg"] and not bgp_af_other_can_del_rst["need_cfg"]: + cmd = ce_bgp_af_obj.delete_bgp_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_af_other_rst["need_cfg"]: + pass + + # state end bgp address family config + bgp_af_rst = ce_bgp_af_obj.check_bgp_af_args(module=module) + end_tmp = dict() + for item in bgp_af_rst: + if item != "need_cfg": + end_tmp[item] = bgp_af_rst[item] + if end_tmp: + end_state["bgp af"] = end_tmp + # state end bgp address family other config + bgp_af_other_rst = ce_bgp_af_obj.check_bgp_af_other_args(module=module) + end_tmp = dict() + for item in bgp_af_other_rst: + if item != "need_cfg": + end_tmp[item] = bgp_af_other_rst[item] + if end_tmp: + end_state["bgp af other"] = end_tmp + # state end bgp import route config + bgp_import_network_route_rst = ce_bgp_af_obj.check_bgp_import_network_route( + module=module) + end_tmp = dict() + for item in bgp_import_network_route_rst: + if item != "need_cfg": + end_tmp[item] = bgp_import_network_route_rst[item] + if end_tmp: + end_state["bgp import & network route"] = end_tmp + if end_state == existing: + changed = False + updates = list() + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor.py new file mode 100644 index 00000000..b4a940f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor.py @@ -0,0 +1,2047 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_neighbor +short_description: Manages BGP peer configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP peer configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + required: true + peer_addr: + description: + - Connection address of a peer, which can be an IPv4 or IPv6 address. + required: true + remote_as: + description: + - AS number of a peer. + The value is a string of 1 to 11 characters. + required: true + description: + description: + - Description of a peer, which can be letters or digits. + The value is a string of 1 to 80 characters. + fake_as: + description: + - Fake AS number that is specified for a local peer. + The value is a string of 1 to 11 characters. + dual_as: + description: + - If the value is true, the EBGP peer can use either a fake AS number or the actual AS number. + If the value is false, the EBGP peer can only use a fake AS number. + choices: ['no_use','true','false'] + default: no_use + conventional: + description: + - If the value is true, the router has all extended capabilities. + If the value is false, the router does not have all extended capabilities. + choices: ['no_use','true','false'] + default: no_use + route_refresh: + description: + - If the value is true, BGP is enabled to advertise REFRESH packets. + If the value is false, the route refresh function is enabled. + choices: ['no_use','true','false'] + default: no_use + is_ignore: + description: + - If the value is true, the session with a specified peer is torn down and all related + routing entries are cleared. + If the value is false, the session with a specified peer is retained. + choices: ['no_use','true','false'] + default: no_use + local_if_name: + description: + - Name of a source interface that sends BGP packets. + The value is a string of 1 to 63 characters. + ebgp_max_hop: + description: + - Maximum number of hops in an indirect EBGP connection. + The value is an ranging from 1 to 255. + valid_ttl_hops: + description: + - Enable GTSM on a peer or peer group. + The valid-TTL-Value parameter is used to specify the number of TTL hops to be detected. + The value is an integer ranging from 1 to 255. + connect_mode: + description: + - The value can be Connect-only, Listen-only, or Both. + is_log_change: + description: + - If the value is true, BGP is enabled to record peer session status and event information. + If the value is false, BGP is disabled from recording peer session status and event information. + choices: ['no_use','true','false'] + default: no_use + pswd_type: + description: + - Enable BGP peers to establish a TCP connection and perform the Message Digest 5 (MD5) + authentication for BGP messages. + choices: ['null','cipher','simple'] + pswd_cipher_text: + description: + - The character string in a password identifies the contents of the password, spaces not supported. + The value is a string of 1 to 255 characters. + keep_alive_time: + description: + - Specify the Keepalive time of a peer or peer group. + The value is an integer ranging from 0 to 21845. The default value is 60. + hold_time: + description: + - Specify the Hold time of a peer or peer group. + The value is 0 or an integer ranging from 3 to 65535. + min_hold_time: + description: + - Specify the Min hold time of a peer or peer group. + key_chain_name: + description: + - Specify the Keychain authentication name used when BGP peers establish a TCP connection. + The value is a string of 1 to 47 case-insensitive characters. + conn_retry_time: + description: + - ConnectRetry interval. + The value is an integer ranging from 1 to 65535. + tcp_MSS: + description: + - Maximum TCP MSS value used for TCP connection establishment for a peer. + The value is an integer ranging from 176 to 4096. + mpls_local_ifnet_disable: + description: + - If the value is true, peer create MPLS Local IFNET disable. + If the value is false, peer create MPLS Local IFNET enable. + choices: ['no_use','true','false'] + default: no_use + prepend_global_as: + description: + - Add the global AS number to the Update packets to be advertised. + choices: ['no_use','true','false'] + default: no_use + prepend_fake_as: + description: + - Add the Fake AS number to received Update packets. + choices: ['no_use','true','false'] + default: no_use + is_bfd_block: + description: + - If the value is true, peers are enabled to inherit the BFD function from the peer group. + If the value is false, peers are disabled to inherit the BFD function from the peer group. + choices: ['no_use','true','false'] + default: no_use + multiplier: + description: + - Specify the detection multiplier. The default value is 3. + The value is an integer ranging from 3 to 50. + is_bfd_enable: + description: + - If the value is true, BFD is enabled. + If the value is false, BFD is disabled. + choices: ['no_use','true','false'] + default: no_use + rx_interval: + description: + - Specify the minimum interval at which BFD packets are received. + The value is an integer ranging from 50 to 1000, in milliseconds. + tx_interval: + description: + - Specify the minimum interval at which BFD packets are sent. + The value is an integer ranging from 50 to 1000, in milliseconds. + is_single_hop: + description: + - If the value is true, the system is enabled to preferentially use the single-hop mode for + BFD session setup between IBGP peers. + If the value is false, the system is disabled from preferentially using the single-hop + mode for BFD session setup between IBGP peers. + choices: ['no_use','true','false'] + default: no_use +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP neighbor test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config bgp peer" + community.network.ce_bgp_neighbor: + state: present + vrf_name: js + peer_addr: 192.168.10.10 + remote_as: 500 + provider: "{{ cli }}" + + - name: "Config bgp route id" + community.network.ce_bgp_neighbor: + state: absent + vrf_name: js + peer_addr: 192.168.10.10 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"peer_addr": "192.168.10.10", "remote_as": "500", "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp peer": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp peer": [["192.168.10.10", "500"]]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 192.168.10.10 as-number 500"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + + +# get bgp peer +CE_GET_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_GET_BGP_PEER_TAIL = """ + + + + + + + +""" + +# merge bgp peer +CE_MERGE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_MERGE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# create bgp peer +CE_CREATE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_CREATE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# delete bgp peer +CE_DELETE_BGP_PEER_HEADER = """ + + + + + + %s + + + %s +""" +CE_DELETE_BGP_PEER_TAIL = """ + + + + + + + +""" + +# get peer bfd +CE_GET_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_GET_PEER_BFD_TAIL = """ + + + + + + + + +""" + +# merge peer bfd +CE_MERGE_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_MERGE_PEER_BFD_TAIL = """ + + + + + + + + +""" + +# delete peer bfd +CE_DELETE_PEER_BFD_HEADER = """ + + + + + + %s + + + %s + +""" +CE_DELETE_PEER_BFD_TAIL = """ + + + + + + + + +""" + + +class BgpNeighbor(object): + """ Manages BGP peer configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_peer_args(self, **kwargs): + """ check_bgp_peer_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + if peer_addr: + if not check_ip_addr(ipaddr=peer_addr): + module.fail_json( + msg='Error: The peer_addr %s is invalid.' % peer_addr) + + need_cfg = True + + remote_as = module.params['remote_as'] + if remote_as: + if len(remote_as) > 11 or len(remote_as) < 1: + module.fail_json( + msg='Error: The len of remote_as %s is out of [1 - 11].' % remote_as) + + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_peer_other_args(self, **kwargs): + """ check_bgp_peer_other_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + description = module.params['description'] + if description: + if len(description) > 80 or len(description) < 1: + module.fail_json( + msg='Error: The len of description %s is out of [1 - 80].' % description) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["description"] = re_find + if re_find[0] != description: + need_cfg = True + else: + need_cfg = True + + fake_as = module.params['fake_as'] + if fake_as: + if len(fake_as) > 11 or len(fake_as) < 1: + module.fail_json( + msg='Error: The len of fake_as %s is out of [1 - 11].' % fake_as) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["fake_as"] = re_find + if re_find[0] != fake_as: + need_cfg = True + else: + need_cfg = True + + dual_as = module.params['dual_as'] + if dual_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["dual_as"] = re_find + if re_find[0] != dual_as: + need_cfg = True + else: + need_cfg = True + + conventional = module.params['conventional'] + if conventional != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conventional"] = re_find + if re_find[0] != conventional: + need_cfg = True + else: + need_cfg = True + + route_refresh = module.params['route_refresh'] + if route_refresh != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_refresh"] = re_find + if re_find[0] != route_refresh: + need_cfg = True + else: + need_cfg = True + + four_byte_as = module.params['four_byte_as'] + if four_byte_as != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["four_byte_as"] = re_find + if re_find[0] != four_byte_as: + need_cfg = True + else: + need_cfg = True + + is_ignore = module.params['is_ignore'] + if is_ignore != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_ignore"] = re_find + if re_find[0] != is_ignore: + need_cfg = True + else: + need_cfg = True + + local_if_name = module.params['local_if_name'] + if local_if_name: + if len(local_if_name) > 63 or len(local_if_name) < 1: + module.fail_json( + msg='Error: The len of local_if_name %s is out of [1 - 63].' % local_if_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["local_if_name"] = re_find + if re_find[0].lower() != local_if_name.lower(): + need_cfg = True + else: + need_cfg = True + + ebgp_max_hop = module.params['ebgp_max_hop'] + if ebgp_max_hop: + if int(ebgp_max_hop) > 255 or int(ebgp_max_hop) < 1: + module.fail_json( + msg='Error: The value of ebgp_max_hop %s is out of [1 - 255].' % ebgp_max_hop) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ebgp_max_hop"] = re_find + if re_find[0] != ebgp_max_hop: + need_cfg = True + else: + need_cfg = True + + valid_ttl_hops = module.params['valid_ttl_hops'] + if valid_ttl_hops: + if int(valid_ttl_hops) > 255 or int(valid_ttl_hops) < 1: + module.fail_json( + msg='Error: The value of valid_ttl_hops %s is out of [1 - 255].' % valid_ttl_hops) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["valid_ttl_hops"] = re_find + if re_find[0] != valid_ttl_hops: + need_cfg = True + else: + need_cfg = True + + connect_mode = module.params['connect_mode'] + if connect_mode: + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["connect_mode"] = re_find + if re_find[0] != connect_mode: + need_cfg = True + else: + need_cfg = True + + is_log_change = module.params['is_log_change'] + if is_log_change != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_log_change"] = re_find + if re_find[0] != is_log_change: + need_cfg = True + else: + need_cfg = True + + pswd_type = module.params['pswd_type'] + if pswd_type: + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["pswd_type"] = re_find + if re_find[0] != pswd_type: + need_cfg = True + else: + need_cfg = True + + pswd_cipher_text = module.params['pswd_cipher_text'] + if pswd_cipher_text: + if len(pswd_cipher_text) > 255 or len(pswd_cipher_text) < 1: + module.fail_json( + msg='Error: The len of pswd_cipher_text %s is out of [1 - 255].' % pswd_cipher_text) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["pswd_cipher_text"] = re_find + if re_find[0] != pswd_cipher_text: + need_cfg = True + else: + need_cfg = True + + keep_alive_time = module.params['keep_alive_time'] + if keep_alive_time: + if int(keep_alive_time) > 21845 or len(keep_alive_time) < 0: + module.fail_json( + msg='Error: The len of keep_alive_time %s is out of [0 - 21845].' % keep_alive_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_alive_time"] = re_find + if re_find[0] != keep_alive_time: + need_cfg = True + else: + need_cfg = True + + hold_time = module.params['hold_time'] + if hold_time: + if int(hold_time) != 0 and (int(hold_time) > 65535 or int(hold_time) < 3): + module.fail_json( + msg='Error: The value of hold_time %s is out of [0 or 3 - 65535].' % hold_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["hold_time"] = re_find + if re_find[0] != hold_time: + need_cfg = True + else: + need_cfg = True + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + if int(min_hold_time) != 0 and (int(min_hold_time) > 65535 or int(min_hold_time) < 20): + module.fail_json( + msg='Error: The value of min_hold_time %s is out of [0 or 20 - 65535].' % min_hold_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["min_hold_time"] = re_find + if re_find[0] != min_hold_time: + need_cfg = True + else: + need_cfg = True + + key_chain_name = module.params['key_chain_name'] + if key_chain_name: + if len(key_chain_name) > 47 or len(key_chain_name) < 1: + module.fail_json( + msg='Error: The len of key_chain_name %s is out of [1 - 47].' % key_chain_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["key_chain_name"] = re_find + if re_find[0] != key_chain_name: + need_cfg = True + else: + need_cfg = True + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + if int(conn_retry_time) > 65535 or int(conn_retry_time) < 1: + module.fail_json( + msg='Error: The value of conn_retry_time %s is out of [1 - 65535].' % conn_retry_time) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["conn_retry_time"] = re_find + if re_find[0] != conn_retry_time: + need_cfg = True + else: + need_cfg = True + + tcp_mss = module.params['tcp_MSS'] + if tcp_mss: + if int(tcp_mss) > 4096 or int(tcp_mss) < 176: + module.fail_json( + msg='Error: The value of tcp_mss %s is out of [176 - 4096].' % tcp_mss) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tcp_MSS"] = re_find + if re_find[0] != tcp_mss: + need_cfg = True + else: + need_cfg = True + + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + if mpls_local_ifnet_disable != 'no_use': + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["mpls_local_ifnet_disable"] = re_find + if re_find[0] != mpls_local_ifnet_disable: + need_cfg = True + else: + need_cfg = True + + prepend_global_as = module.params['prepend_global_as'] + if prepend_global_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prepend_global_as"] = re_find + if re_find[0] != prepend_global_as: + need_cfg = True + else: + need_cfg = True + + prepend_fake_as = module.params['prepend_fake_as'] + if prepend_fake_as != 'no_use': + if not fake_as: + module.fail_json(msg='fake_as must exist.') + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["prepend_fake_as"] = re_find + if re_find[0] != prepend_fake_as: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_peer_bfd_merge_args(self, **kwargs): + """ check_peer_bfd_merge_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + if state == "absent": + result["need_cfg"] = need_cfg + return result + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_block"] = re_find + if re_find[0] != is_bfd_block: + need_cfg = True + else: + need_cfg = True + + multiplier = module.params['multiplier'] + if multiplier: + if int(multiplier) > 50 or int(multiplier) < 3: + module.fail_json( + msg='Error: The value of multiplier %s is out of [3 - 50].' % multiplier) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["multiplier"] = re_find + if re_find[0] != multiplier: + need_cfg = True + else: + need_cfg = True + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_enable"] = re_find + if re_find[0] != is_bfd_enable: + need_cfg = True + else: + need_cfg = True + + rx_interval = module.params['rx_interval'] + if rx_interval: + if int(rx_interval) > 1000 or int(rx_interval) < 50: + module.fail_json( + msg='Error: The value of rx_interval %s is out of [50 - 1000].' % rx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rx_interval"] = re_find + if re_find[0] != rx_interval: + need_cfg = True + else: + need_cfg = True + + tx_interval = module.params['tx_interval'] + if tx_interval: + if int(tx_interval) > 1000 or int(tx_interval) < 50: + module.fail_json( + msg='Error: The value of tx_interval %s is out of [50 - 1000].' % tx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tx_interval"] = re_find + if re_find[0] != tx_interval: + need_cfg = True + else: + need_cfg = True + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_single_hop"] = re_find + if re_find[0] != is_single_hop: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_peer_bfd_delete_args(self, **kwargs): + """ check_peer_bfd_delete_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + if state == "present": + result["need_cfg"] = need_cfg + return result + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + peer_addr = module.params['peer_addr'] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_block"] = re_find + if re_find[0] == is_bfd_block: + need_cfg = True + + multiplier = module.params['multiplier'] + if multiplier: + if int(multiplier) > 50 or int(multiplier) < 3: + module.fail_json( + msg='Error: The value of multiplier %s is out of [3 - 50].' % multiplier) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["multiplier"] = re_find + if re_find[0] == multiplier: + need_cfg = True + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_bfd_enable"] = re_find + if re_find[0] == is_bfd_enable: + need_cfg = True + + rx_interval = module.params['rx_interval'] + if rx_interval: + if int(rx_interval) > 1000 or int(rx_interval) < 50: + module.fail_json( + msg='Error: The value of rx_interval %s is out of [50 - 1000].' % rx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rx_interval"] = re_find + if re_find[0] == rx_interval: + need_cfg = True + + tx_interval = module.params['tx_interval'] + if tx_interval: + if int(tx_interval) > 1000 or int(tx_interval) < 50: + module.fail_json( + msg='Error: The value of tx_interval %s is out of [50 - 1000].' % tx_interval) + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["tx_interval"] = re_find + if re_find[0] == tx_interval: + need_cfg = True + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + + conf_str = CE_GET_PEER_BFD_HEADER % ( + vrf_name, peer_addr) + "" + CE_GET_PEER_BFD_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_single_hop"] = re_find + if re_find[0] == is_single_hop: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def get_bgp_peer(self, **kwargs): + """ get_bgp_peer """ + + module = kwargs["module"] + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + \ + "" + CE_GET_BGP_PEER_TAIL + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*\s.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def get_bgp_del_peer(self, **kwargs): + """ get_bgp_del_peer """ + + module = kwargs["module"] + peerip = module.params['peer_addr'] + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + conf_str = CE_GET_BGP_PEER_HEADER % (vrf_name, peerip) + CE_GET_BGP_PEER_TAIL + + xml_str = self.netconf_get_config(module=module, conf_str=conf_str) + + result = list() + + if "" in xml_str: + return result + else: + re_find = re.findall( + r'.*(.*).*', xml_str) + + if re_find: + return re_find + else: + return result + + def merge_bgp_peer(self, **kwargs): + """ merge_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + + conf_str = CE_MERGE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + "%s" % remote_as + CE_MERGE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer failed.') + + cmds = [] + cmd = "peer %s as-number %s" % (peer_addr, remote_as) + cmds.append(cmd) + + return cmds + + def create_bgp_peer(self, **kwargs): + """ create_bgp_peer """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + + conf_str = CE_CREATE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + "%s" % remote_as + CE_CREATE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp peer failed.') + + cmds = [] + cmd = "peer %s as-number %s" % (peer_addr, remote_as) + cmds.append(cmd) + + return cmds + + def delete_bgp_peer(self, **kwargs): + """ delete_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_DELETE_BGP_PEER_HEADER % ( + vrf_name, peer_addr) + CE_DELETE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp peer failed.') + + cmds = [] + cmd = "undo peer %s" % peer_addr + cmds.append(cmd) + + return cmds + + def merge_bgp_peer_other(self, **kwargs): + """ merge_bgp_peer """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_MERGE_BGP_PEER_HEADER % (vrf_name, peer_addr) + + cmds = [] + + description = module.params['description'] + if description: + conf_str += "%s" % description + + cmd = "peer %s description %s" % (peer_addr, description) + cmds.append(cmd) + + fake_as = module.params['fake_as'] + if fake_as: + conf_str += "%s" % fake_as + + cmd = "peer %s local-as %s" % (peer_addr, fake_as) + cmds.append(cmd) + + dual_as = module.params['dual_as'] + if dual_as != 'no_use': + conf_str += "%s" % dual_as + + if dual_as == "true": + cmd = "peer %s local-as %s dual-as" % (peer_addr, fake_as) + else: + cmd = "peer %s local-as %s" % (peer_addr, fake_as) + cmds.append(cmd) + + conventional = module.params['conventional'] + if conventional != 'no_use': + conf_str += "%s" % conventional + if conventional == "true": + cmd = "peer %s capability-advertise conventional" % peer_addr + else: + cmd = "undo peer %s capability-advertise conventional" % peer_addr + cmds.append(cmd) + + route_refresh = module.params['route_refresh'] + if route_refresh != 'no_use': + conf_str += "%s" % route_refresh + + if route_refresh == "true": + cmd = "peer %s capability-advertise route-refresh" % peer_addr + else: + cmd = "undo peer %s capability-advertise route-refresh" % peer_addr + cmds.append(cmd) + + four_byte_as = module.params['four_byte_as'] + if four_byte_as != 'no_use': + conf_str += "%s" % four_byte_as + + if four_byte_as == "true": + cmd = "peer %s capability-advertise 4-byte-as" % peer_addr + else: + cmd = "undo peer %s capability-advertise 4-byte-as" % peer_addr + cmds.append(cmd) + + is_ignore = module.params['is_ignore'] + if is_ignore != 'no_use': + conf_str += "%s" % is_ignore + + if is_ignore == "true": + cmd = "peer %s ignore" % peer_addr + else: + cmd = "undo peer %s ignore" % peer_addr + cmds.append(cmd) + + local_if_name = module.params['local_if_name'] + if local_if_name: + conf_str += "%s" % local_if_name + + cmd = "peer %s connect-interface %s" % (peer_addr, local_if_name) + cmds.append(cmd) + + ebgp_max_hop = module.params['ebgp_max_hop'] + if ebgp_max_hop: + conf_str += "%s" % ebgp_max_hop + + cmd = "peer %s ebgp-max-hop %s" % (peer_addr, ebgp_max_hop) + cmds.append(cmd) + + valid_ttl_hops = module.params['valid_ttl_hops'] + if valid_ttl_hops: + conf_str += "%s" % valid_ttl_hops + + cmd = "peer %s valid-ttl-hops %s" % (peer_addr, valid_ttl_hops) + cmds.append(cmd) + + connect_mode = module.params['connect_mode'] + if connect_mode: + + if connect_mode == "listenOnly": + cmd = "peer %s listen-only" % peer_addr + cmds.append(cmd) + elif connect_mode == "connectOnly": + cmd = "peer %s connect-only" % peer_addr + cmds.append(cmd) + elif connect_mode == "both": + connect_mode = "null" + cmd = "peer %s listen-only" % peer_addr + cmds.append(cmd) + cmd = "peer %s connect-only" % peer_addr + cmds.append(cmd) + conf_str += "%s" % connect_mode + + is_log_change = module.params['is_log_change'] + if is_log_change != 'no_use': + conf_str += "%s" % is_log_change + + if is_log_change == "true": + cmd = "peer %s log-change" % peer_addr + else: + cmd = "undo peer %s log-change" % peer_addr + cmds.append(cmd) + + pswd_type = module.params['pswd_type'] + if pswd_type: + conf_str += "%s" % pswd_type + + pswd_cipher_text = module.params['pswd_cipher_text'] + if pswd_cipher_text: + conf_str += "%s" % pswd_cipher_text + + if pswd_type == "cipher": + cmd = "peer %s password cipher %s" % ( + peer_addr, pswd_cipher_text) + elif pswd_type == "simple": + cmd = "peer %s password simple %s" % ( + peer_addr, pswd_cipher_text) + cmds.append(cmd) + + keep_alive_time = module.params['keep_alive_time'] + if keep_alive_time: + conf_str += "%s" % keep_alive_time + + cmd = "peer %s timer keepalive %s" % (peer_addr, keep_alive_time) + cmds.append(cmd) + + hold_time = module.params['hold_time'] + if hold_time: + conf_str += "%s" % hold_time + + cmd = "peer %s timer hold %s" % (peer_addr, hold_time) + cmds.append(cmd) + + min_hold_time = module.params['min_hold_time'] + if min_hold_time: + conf_str += "%s" % min_hold_time + + cmd = "peer %s timer min-holdtime %s" % (peer_addr, min_hold_time) + cmds.append(cmd) + + key_chain_name = module.params['key_chain_name'] + if key_chain_name: + conf_str += "%s" % key_chain_name + + cmd = "peer %s keychain %s" % (peer_addr, key_chain_name) + cmds.append(cmd) + + conn_retry_time = module.params['conn_retry_time'] + if conn_retry_time: + conf_str += "%s" % conn_retry_time + + cmd = "peer %s timer connect-retry %s" % ( + peer_addr, conn_retry_time) + cmds.append(cmd) + + tcp_mss = module.params['tcp_MSS'] + if tcp_mss: + conf_str += "%s" % tcp_mss + + cmd = "peer %s tcp-mss %s" % (peer_addr, tcp_mss) + cmds.append(cmd) + + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + if mpls_local_ifnet_disable != 'no_use': + conf_str += "%s" % mpls_local_ifnet_disable + + if mpls_local_ifnet_disable == "false": + cmd = "undo peer %s mpls-local-ifnet disable" % peer_addr + else: + cmd = "peer %s mpls-local-ifnet disable" % peer_addr + cmds.append(cmd) + + prepend_global_as = module.params['prepend_global_as'] + if prepend_global_as != 'no_use': + conf_str += "%s" % prepend_global_as + + if prepend_global_as == "true": + cmd = "peer %s local-as %s prepend-global-as" % (peer_addr, fake_as) + else: + cmd = "undo peer %s local-as %s prepend-global-as" % (peer_addr, fake_as) + cmds.append(cmd) + + prepend_fake_as = module.params['prepend_fake_as'] + if prepend_fake_as != 'no_use': + conf_str += "%s" % prepend_fake_as + + if prepend_fake_as == "true": + cmd = "peer %s local-as %s prepend-local-as" % (peer_addr, fake_as) + else: + cmd = "undo peer %s local-as %s prepend-local-as" % (peer_addr, fake_as) + cmds.append(cmd) + + conf_str += CE_MERGE_BGP_PEER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer other failed.') + + return cmds + + def merge_peer_bfd(self, **kwargs): + """ merge_peer_bfd """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_MERGE_PEER_BFD_HEADER % (vrf_name, peer_addr) + + cmds = [] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + conf_str += "%s" % is_bfd_block + + if is_bfd_block == "true": + cmd = "peer %s bfd block" % peer_addr + else: + cmd = "undo peer %s bfd block" % peer_addr + cmds.append(cmd) + + multiplier = module.params['multiplier'] + if multiplier: + conf_str += "%s" % multiplier + + cmd = "peer %s bfd detect-multiplier %s" % (peer_addr, multiplier) + cmds.append(cmd) + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + conf_str += "%s" % is_bfd_enable + + if is_bfd_enable == "true": + cmd = "peer %s bfd enable" % peer_addr + else: + cmd = "undo peer %s bfd enable" % peer_addr + cmds.append(cmd) + + rx_interval = module.params['rx_interval'] + if rx_interval: + conf_str += "%s" % rx_interval + + cmd = "peer %s bfd min-rx-interval %s" % (peer_addr, rx_interval) + cmds.append(cmd) + + tx_interval = module.params['tx_interval'] + if tx_interval: + conf_str += "%s" % tx_interval + + cmd = "peer %s bfd min-tx-interval %s" % (peer_addr, tx_interval) + cmds.append(cmd) + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + conf_str += "%s" % is_single_hop + + if is_single_hop == "true": + cmd = "peer %s bfd enable single-hop-prefer" % peer_addr + else: + cmd = "undo peer %s bfd enable single-hop-prefer" % peer_addr + cmds.append(cmd) + + conf_str += CE_MERGE_PEER_BFD_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge peer bfd failed.') + + return cmds + + def delete_peer_bfd(self, **kwargs): + """ delete_peer_bfd """ + + module = kwargs["module"] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + + conf_str = CE_DELETE_PEER_BFD_HEADER % (vrf_name, peer_addr) + + cmds = [] + + is_bfd_block = module.params['is_bfd_block'] + if is_bfd_block != 'no_use': + conf_str += "%s" % is_bfd_block + + cmd = "undo peer %s bfd block" % peer_addr + cmds.append(cmd) + + multiplier = module.params['multiplier'] + if multiplier: + conf_str += "%s" % multiplier + + cmd = "undo peer %s bfd detect-multiplier %s" % ( + peer_addr, multiplier) + cmds.append(cmd) + + is_bfd_enable = module.params['is_bfd_enable'] + if is_bfd_enable != 'no_use': + conf_str += "%s" % is_bfd_enable + + cmd = "undo peer %s bfd enable" % peer_addr + cmds.append(cmd) + + rx_interval = module.params['rx_interval'] + if rx_interval: + conf_str += "%s" % rx_interval + + cmd = "undo peer %s bfd min-rx-interval %s" % ( + peer_addr, rx_interval) + cmds.append(cmd) + + tx_interval = module.params['tx_interval'] + if tx_interval: + conf_str += "%s" % tx_interval + + cmd = "undo peer %s bfd min-tx-interval %s" % ( + peer_addr, tx_interval) + cmds.append(cmd) + + is_single_hop = module.params['is_single_hop'] + if is_single_hop != 'no_use': + conf_str += "%s" % is_single_hop + + cmd = "undo peer %s bfd enable single-hop-prefer" % peer_addr + cmds.append(cmd) + + conf_str += CE_DELETE_PEER_BFD_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete peer bfd failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + peer_addr=dict(type='str', required=True), + remote_as=dict(type='str', required=True), + description=dict(type='str'), + fake_as=dict(type='str'), + dual_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + conventional=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + route_refresh=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + four_byte_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_ignore=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + local_if_name=dict(type='str'), + ebgp_max_hop=dict(type='str'), + valid_ttl_hops=dict(type='str'), + connect_mode=dict(choices=['listenOnly', 'connectOnly', 'both']), + is_log_change=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + pswd_type=dict(choices=['null', 'cipher', 'simple']), + pswd_cipher_text=dict(type='str', no_log=True), + keep_alive_time=dict(type='str'), + hold_time=dict(type='str'), + min_hold_time=dict(type='str'), + key_chain_name=dict(type='str'), + conn_retry_time=dict(type='str'), + tcp_MSS=dict(type='str'), + mpls_local_ifnet_disable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + prepend_global_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + prepend_fake_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_bfd_block=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + multiplier=dict(type='str'), + is_bfd_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + rx_interval=dict(type='str'), + tx_interval=dict(type='str'), + is_single_hop=dict(type='str', default='no_use', choices=['no_use', 'true', 'false'])) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + peer_addr = module.params['peer_addr'] + remote_as = module.params['remote_as'] + description = module.params['description'] + fake_as = module.params['fake_as'] + dual_as = module.params['dual_as'] + conventional = module.params['conventional'] + route_refresh = module.params['route_refresh'] + four_byte_as = module.params['four_byte_as'] + is_ignore = module.params['is_ignore'] + local_if_name = module.params['local_if_name'] + ebgp_max_hop = module.params['ebgp_max_hop'] + valid_ttl_hops = module.params['valid_ttl_hops'] + connect_mode = module.params['connect_mode'] + is_log_change = module.params['is_log_change'] + pswd_type = module.params['pswd_type'] + pswd_cipher_text = module.params['pswd_cipher_text'] + keep_alive_time = module.params['keep_alive_time'] + hold_time = module.params['hold_time'] + min_hold_time = module.params['min_hold_time'] + key_chain_name = module.params['key_chain_name'] + conn_retry_time = module.params['conn_retry_time'] + tcp_mss = module.params['tcp_MSS'] + mpls_local_ifnet_disable = module.params['mpls_local_ifnet_disable'] + prepend_global_as = module.params['prepend_global_as'] + prepend_fake_as = module.params['prepend_fake_as'] + is_bfd_block = module.params['is_bfd_block'] + multiplier = module.params['multiplier'] + is_bfd_enable = module.params['is_bfd_enable'] + rx_interval = module.params['rx_interval'] + tx_interval = module.params['tx_interval'] + is_single_hop = module.params['is_single_hop'] + + ce_bgp_peer_obj = BgpNeighbor() + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if peer_addr: + proposed["peer_addr"] = peer_addr + if remote_as: + proposed["remote_as"] = remote_as + if description: + proposed["description"] = description + if fake_as: + proposed["fake_as"] = fake_as + if dual_as != 'no_use': + proposed["dual_as"] = dual_as + if conventional != 'no_use': + proposed["conventional"] = conventional + if route_refresh != 'no_use': + proposed["route_refresh"] = route_refresh + if four_byte_as != 'no_use': + proposed["four_byte_as"] = four_byte_as + if is_ignore != 'no_use': + proposed["is_ignore"] = is_ignore + if local_if_name: + proposed["local_if_name"] = local_if_name + if ebgp_max_hop: + proposed["ebgp_max_hop"] = ebgp_max_hop + if valid_ttl_hops: + proposed["valid_ttl_hops"] = valid_ttl_hops + if connect_mode: + proposed["connect_mode"] = connect_mode + if is_log_change != 'no_use': + proposed["is_log_change"] = is_log_change + if pswd_type: + proposed["pswd_type"] = pswd_type + if pswd_cipher_text: + proposed["pswd_cipher_text"] = pswd_cipher_text + if keep_alive_time: + proposed["keep_alive_time"] = keep_alive_time + if hold_time: + proposed["hold_time"] = hold_time + if min_hold_time: + proposed["min_hold_time"] = min_hold_time + if key_chain_name: + proposed["key_chain_name"] = key_chain_name + if conn_retry_time: + proposed["conn_retry_time"] = conn_retry_time + if tcp_mss: + proposed["tcp_MSS"] = tcp_mss + if mpls_local_ifnet_disable != 'no_use': + proposed["mpls_local_ifnet_disable"] = mpls_local_ifnet_disable + if prepend_global_as != 'no_use': + proposed["prepend_global_as"] = prepend_global_as + if prepend_fake_as != 'no_use': + proposed["prepend_fake_as"] = prepend_fake_as + if is_bfd_block != 'no_use': + proposed["is_bfd_block"] = is_bfd_block + if multiplier: + proposed["multiplier"] = multiplier + if is_bfd_enable != 'no_use': + proposed["is_bfd_enable"] = is_bfd_enable + if rx_interval: + proposed["rx_interval"] = rx_interval + if tx_interval: + proposed["tx_interval"] = tx_interval + if is_single_hop != 'no_use': + proposed["is_single_hop"] = is_single_hop + + if not ce_bgp_peer_obj: + module.fail_json(msg='Error: Init module failed.') + + need_bgp_peer_enable = ce_bgp_peer_obj.check_bgp_peer_args(module=module) + need_bgp_peer_other_rst = ce_bgp_peer_obj.check_bgp_peer_other_args( + module=module) + need_peer_bfd_merge_rst = ce_bgp_peer_obj.check_peer_bfd_merge_args( + module=module) + need_peer_bfd_del_rst = ce_bgp_peer_obj.check_peer_bfd_delete_args( + module=module) + + # bgp peer config + if need_bgp_peer_enable["need_cfg"]: + + if state == "present": + + if remote_as: + + bgp_peer_exist = ce_bgp_peer_obj.get_bgp_peer(module=module) + existing["bgp peer"] = bgp_peer_exist + + bgp_peer_new = (peer_addr, remote_as) + if len(bgp_peer_exist) == 0: + cmd = ce_bgp_peer_obj.create_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + elif bgp_peer_new in bgp_peer_exist: + pass + + else: + cmd = ce_bgp_peer_obj.merge_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + bgp_peer_end = ce_bgp_peer_obj.get_bgp_peer(module=module) + end_state["bgp peer"] = bgp_peer_end + + else: + + bgp_peer_exist = ce_bgp_peer_obj.get_bgp_del_peer(module=module) + existing["bgp peer"] = bgp_peer_exist + + bgp_peer_new = (peer_addr) + + if len(bgp_peer_exist) == 0: + pass + + elif bgp_peer_new in bgp_peer_exist: + cmd = ce_bgp_peer_obj.delete_bgp_peer(module=module) + changed = True + for item in cmd: + updates.append(item) + + bgp_peer_end = ce_bgp_peer_obj.get_bgp_del_peer(module=module) + end_state["bgp peer"] = bgp_peer_end + + # bgp peer other args + exist_tmp = dict() + for item in need_bgp_peer_other_rst: + if item != "need_cfg": + exist_tmp[item] = need_bgp_peer_other_rst[item] + if exist_tmp: + existing["bgp peer other"] = exist_tmp + + if need_bgp_peer_other_rst["need_cfg"]: + + if state == "present": + cmd = ce_bgp_peer_obj.merge_bgp_peer_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_bgp_peer_other_rst = ce_bgp_peer_obj.check_bgp_peer_other_args( + module=module) + end_tmp = dict() + for item in need_bgp_peer_other_rst: + if item != "need_cfg": + end_tmp[item] = need_bgp_peer_other_rst[item] + if end_tmp: + end_state["bgp peer other"] = end_tmp + + # peer bfd args + if state == "present": + exist_tmp = dict() + for item in need_peer_bfd_merge_rst: + if item != "need_cfg": + exist_tmp[item] = need_peer_bfd_merge_rst[item] + if exist_tmp: + existing["peer bfd"] = exist_tmp + + if need_peer_bfd_merge_rst["need_cfg"]: + cmd = ce_bgp_peer_obj.merge_peer_bfd(module=module) + changed = True + for item in cmd: + updates.append(item) + + need_peer_bfd_merge_rst = ce_bgp_peer_obj.check_peer_bfd_merge_args( + module=module) + end_tmp = dict() + for item in need_peer_bfd_merge_rst: + if item != "need_cfg": + end_tmp[item] = need_peer_bfd_merge_rst[item] + if end_tmp: + end_state["peer bfd"] = end_tmp + else: + exist_tmp = dict() + for item in need_peer_bfd_del_rst: + if item != "need_cfg": + exist_tmp[item] = need_peer_bfd_del_rst[item] + if exist_tmp: + existing["peer bfd"] = exist_tmp + + # has already delete with bgp peer + + need_peer_bfd_del_rst = ce_bgp_peer_obj.check_peer_bfd_delete_args( + module=module) + end_tmp = dict() + for item in need_peer_bfd_del_rst: + if item != "need_cfg": + end_tmp[item] = need_peer_bfd_del_rst[item] + if end_tmp: + end_state["peer bfd"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor_af.py new file mode 100644 index 00000000..93bee2a8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_bgp_neighbor_af.py @@ -0,0 +1,2675 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_bgp_neighbor_af +short_description: Manages BGP neighbor Address-family configuration on HUAWEI CloudEngine switches. +description: + - Manages BGP neighbor Address-family configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf_name: + description: + - Name of a BGP instance. The name is a case-sensitive string of characters. + The BGP instance can be used only after the corresponding VPN instance is created. + required: true + af_type: + description: + - Address family type of a BGP instance. + required: true + choices: ['ipv4uni', 'ipv4multi', 'ipv4vpn', 'ipv6uni', 'ipv6vpn', 'evpn'] + remote_address: + description: + - IPv4 or IPv6 peer connection address. + required: true + advertise_irb: + description: + - If the value is true, advertised IRB routes are distinguished. + If the value is false, advertised IRB routes are not distinguished. + default: no_use + choices: ['no_use','true','false'] + advertise_arp: + description: + - If the value is true, advertised ARP routes are distinguished. + If the value is false, advertised ARP routes are not distinguished. + default: no_use + choices: ['no_use','true','false'] + advertise_remote_nexthop: + description: + - If the value is true, the remote next-hop attribute is advertised to peers. + If the value is false, the remote next-hop attribute is not advertised to any peers. + default: no_use + choices: ['no_use','true','false'] + advertise_community: + description: + - If the value is true, the community attribute is advertised to peers. + If the value is false, the community attribute is not advertised to peers. + default: no_use + choices: ['no_use','true','false'] + advertise_ext_community: + description: + - If the value is true, the extended community attribute is advertised to peers. + If the value is false, the extended community attribute is not advertised to peers. + default: no_use + choices: ['no_use','true','false'] + discard_ext_community: + description: + - If the value is true, the extended community attribute in the peer route information is discarded. + If the value is false, the extended community attribute in the peer route information is not discarded. + default: no_use + choices: ['no_use','true','false'] + allow_as_loop_enable: + description: + - If the value is true, repetitive local AS numbers are allowed. + If the value is false, repetitive local AS numbers are not allowed. + default: no_use + choices: ['no_use','true','false'] + allow_as_loop_limit: + description: + - Set the maximum number of repetitive local AS number. + The value is an integer ranging from 1 to 10. + keep_all_routes: + description: + - If the value is true, the system stores all route update messages received from all peers (groups) + after BGP connection setup. + If the value is false, the system stores only BGP update messages that are received from peers + and pass the configured import policy. + default: no_use + choices: ['no_use','true','false'] + nexthop_configure: + description: + - null, The next hop is not changed. + local, The next hop is changed to the local IP address. + invariable, Prevent the device from changing the next hop of each imported IGP route + when advertising it to its BGP peers. + choices: ['null', 'local', 'invariable'] + preferred_value: + description: + - Assign a preferred value for the routes learned from a specified peer. + The value is an integer ranging from 0 to 65535. + public_as_only: + description: + - If the value is true, sent BGP update messages carry only the public AS number but do not carry + private AS numbers. + If the value is false, sent BGP update messages can carry private AS numbers. + default: no_use + choices: ['no_use','true','false'] + public_as_only_force: + description: + - If the value is true, sent BGP update messages carry only the public AS number but do not carry + private AS numbers. + If the value is false, sent BGP update messages can carry private AS numbers. + default: no_use + choices: ['no_use','true','false'] + public_as_only_limited: + description: + - Limited use public as number. + default: no_use + choices: ['no_use','true','false'] + public_as_only_replace: + description: + - Private as replaced by public as number. + default: no_use + choices: ['no_use','true','false'] + public_as_only_skip_peer_as: + description: + - Public as only skip peer as. + default: no_use + choices: ['no_use','true','false'] + route_limit: + description: + - Configure the maximum number of routes that can be accepted from a peer. + The value is an integer ranging from 1 to 4294967295. + route_limit_percent: + description: + - Specify the percentage of routes when a router starts to generate an alarm. + The value is an integer ranging from 1 to 100. + route_limit_type: + description: + - Noparameter, After the number of received routes exceeds the threshold and the timeout + timer expires,no action. + AlertOnly, An alarm is generated and no additional routes will be accepted if the maximum + number of routes allowed have been received. + IdleForever, The connection that is interrupted is not automatically re-established if the + maximum number of routes allowed have been received. + IdleTimeout, After the number of received routes exceeds the threshold and the timeout timer + expires, the connection that is interrupted is automatically re-established. + choices: ['noparameter', 'alertOnly', 'idleForever', 'idleTimeout'] + route_limit_idle_timeout: + description: + - Specify the value of the idle-timeout timer to automatically reestablish the connections after + they are cut off when the number of routes exceeds the set threshold. + The value is an integer ranging from 1 to 1200. + rt_updt_interval: + description: + - Specify the minimum interval at which Update packets are sent. The value is an integer, in seconds. + The value is an integer ranging from 0 to 600. + redirect_ip: + description: + - Redirect ip. + default: no_use + choices: ['no_use','true','false'] + redirect_ip_validation: + description: + - Redirect ip validation. + default: no_use + choices: ['no_use','true','false'] + aliases: ['redirect_ip_vaildation'] + reflect_client: + description: + - If the value is true, the local device functions as the route reflector and a peer functions + as a client of the route reflector. + If the value is false, the route reflector and client functions are not configured. + default: no_use + choices: ['no_use','true','false'] + substitute_as_enable: + description: + - If the value is true, the function to replace a specified peer's AS number in the AS-Path attribute with + the local AS number is enabled. + If the value is false, the function to replace a specified peer's AS number in the AS-Path attribute with + the local AS number is disabled. + default: no_use + choices: ['no_use','true','false'] + import_rt_policy_name: + description: + - Specify the filtering policy applied to the routes learned from a peer. + The value is a string of 1 to 40 characters. + export_rt_policy_name: + description: + - Specify the filtering policy applied to the routes to be advertised to a peer. + The value is a string of 1 to 40 characters. + import_pref_filt_name: + description: + - Specify the IPv4 filtering policy applied to the routes received from a specified peer. + The value is a string of 1 to 169 characters. + export_pref_filt_name: + description: + - Specify the IPv4 filtering policy applied to the routes to be advertised to a specified peer. + The value is a string of 1 to 169 characters. + import_as_path_filter: + description: + - Apply an AS_Path-based filtering policy to the routes received from a specified peer. + The value is an integer ranging from 1 to 256. + export_as_path_filter: + description: + - Apply an AS_Path-based filtering policy to the routes to be advertised to a specified peer. + The value is an integer ranging from 1 to 256. + import_as_path_name_or_num: + description: + - A routing strategy based on the AS path list for routing received by a designated peer. + export_as_path_name_or_num: + description: + - Application of a AS path list based filtering policy to the routing of a specified peer. + import_acl_name_or_num: + description: + - Apply an IPv4 ACL-based filtering policy to the routes received from a specified peer. + The value is a string of 1 to 32 characters. + export_acl_name_or_num: + description: + - Apply an IPv4 ACL-based filtering policy to the routes to be advertised to a specified peer. + The value is a string of 1 to 32 characters. + ipprefix_orf_enable: + description: + - If the value is true, the address prefix-based Outbound Route Filter (ORF) capability is + enabled for peers. + If the value is false, the address prefix-based Outbound Route Filter (ORF) capability is + disabled for peers. + default: no_use + choices: ['no_use','true','false'] + is_nonstd_ipprefix_mod: + description: + - If the value is true, Non-standard capability codes are used during capability negotiation. + If the value is false, RFC-defined standard ORF capability codes are used during capability negotiation. + default: no_use + choices: ['no_use','true','false'] + orftype: + description: + - ORF Type. + The value is an integer ranging from 0 to 65535. + orf_mode: + description: + - ORF mode. + null, Default value. + receive, ORF for incoming packets. + send, ORF for outgoing packets. + both, ORF for incoming and outgoing packets. + choices: ['null', 'receive', 'send', 'both'] + soostring: + description: + - Configure the Site-of-Origin (SoO) extended community attribute. + The value is a string of 3 to 21 characters. + default_rt_adv_enable: + description: + - If the value is true, the function to advertise default routes to peers is enabled. + If the value is false, the function to advertise default routes to peers is disabled. + default: no_use + choices: ['no_use','true', 'false'] + default_rt_adv_policy: + description: + - Specify the name of a used policy. The value is a string. + The value is a string of 1 to 40 characters. + default_rt_match_mode: + description: + - null, Null. + matchall, Advertise the default route if all matching conditions are met. + matchany, Advertise the default route if any matching condition is met. + choices: ['null', 'matchall', 'matchany'] + add_path_mode: + description: + - null, Null. + receive, Support receiving Add-Path routes. + send, Support sending Add-Path routes. + both, Support receiving and sending Add-Path routes. + choices: ['null', 'receive', 'send', 'both'] + adv_add_path_num: + description: + - The number of addPath advertise route. + The value is an integer ranging from 2 to 64. + origin_as_valid: + description: + - If the value is true, Application results of route announcement. + If the value is false, Routing application results are not notified. + default: no_use + choices: ['no_use','true', 'false'] + vpls_enable: + description: + - If the value is true, vpls enable. + If the value is false, vpls disable. + default: no_use + choices: ['no_use','true', 'false'] + vpls_ad_disable: + description: + - If the value is true, enable vpls-ad. + If the value is false, disable vpls-ad. + default: no_use + choices: ['no_use','true', 'false'] + update_pkt_standard_compatible: + description: + - If the value is true, When the vpnv4 multicast neighbor receives and updates the message, + the message has no label. + If the value is false, When the vpnv4 multicast neighbor receives and updates the message, + the message has label. + default: no_use + choices: ['no_use','true', 'false'] +''' + +EXAMPLES = ''' + +- name: CloudEngine BGP neighbor address family test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config BGP peer Address_Family" + community.network.ce_bgp_neighbor_af: + state: present + vrf_name: js + af_type: ipv4uni + remote_address: 192.168.10.10 + nexthop_configure: local + provider: "{{ cli }}" + + - name: "Undo BGP peer Address_Family" + community.network.ce_bgp_neighbor_af: + state: absent + vrf_name: js + af_type: ipv4uni + remote_address: 192.168.10.10 + nexthop_configure: local + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"af_type": "ipv4uni", "nexthop_configure": "local", + "remote_address": "192.168.10.10", + "state": "present", "vrf_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bgp neighbor af": {"af_type": "ipv4uni", "remote_address": "192.168.10.10", + "vrf_name": "js"}, + "bgp neighbor af other": {"af_type": "ipv4uni", "nexthop_configure": "null", + "vrf_name": "js"}} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bgp neighbor af": {"af_type": "ipv4uni", "remote_address": "192.168.10.10", + "vrf_name": "js"}, + "bgp neighbor af other": {"af_type": "ipv4uni", "nexthop_configure": "local", + "vrf_name": "js"}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 192.168.10.10 next-hop-local"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +# get bgp peer af +CE_GET_BGP_PEER_AF_HEADER = """ + + + + + + %s + + + %s + + + %s +""" +CE_GET_BGP_PEER_AF_TAIL = """ + + + + + + + + + +""" + +# merge bgp peer af +CE_MERGE_BGP_PEER_AF_HEADER = """ + + + + + + %s + + + %s + + + %s +""" +CE_MERGE_BGP_PEER_AF_TAIL = """ + + + + + + + + + +""" + +# create bgp peer af +CE_CREATE_BGP_PEER_AF = """ + + + + + + %s + + + %s + + + %s + + + + + + + + + +""" + +# delete bgp peer af +CE_DELETE_BGP_PEER_AF = """ + + + + + + %s + + + %s + + + %s + + + + + + + + + +""" + + +class BgpNeighborAf(object): + """ Manages BGP neighbor Address-family configuration """ + + def netconf_get_config(self, **kwargs): + """ netconf_get_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ netconf_set_config """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_bgp_neighbor_af_args(self, **kwargs): + """ check_bgp_neighbor_af_args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + vrf_name = module.params['vrf_name'] + if vrf_name: + if len(vrf_name) > 31 or len(vrf_name) == 0: + module.fail_json( + msg='Error: The len of vrf_name %s is out of [1 - 31].' % vrf_name) + + state = module.params['state'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + if not check_ip_addr(ipaddr=remote_address): + module.fail_json( + msg='Error: The remote_address %s is invalid.' % remote_address) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if state == "present": + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + if re_find: + result["remote_address"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if remote_address not in re_find: + need_cfg = True + else: + need_cfg = True + else: + if "" in recv_xml: + pass + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["remote_address"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] == remote_address: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def check_bgp_neighbor_af_other(self, **kwargs): + """ check_bgp_neighbor_af_other """ + + module = kwargs["module"] + result = dict() + need_cfg = False + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + if state == "absent": + result["need_cfg"] = need_cfg + return result + + advertise_irb = module.params['advertise_irb'] + if advertise_irb != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall(r'.*%s\s*' + r'(.*).*' % remote_address, recv_xml) + if re_find: + result["advertise_irb"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_irb: + need_cfg = True + else: + need_cfg = True + + advertise_arp = module.params['advertise_arp'] + if advertise_arp != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall(r'.*%s\s*' + r'.*(.*).*' % remote_address, recv_xml) + + if re_find: + result["advertise_arp"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_arp: + need_cfg = True + else: + need_cfg = True + + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + if advertise_remote_nexthop != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_remote_nexthop"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_remote_nexthop: + need_cfg = True + else: + need_cfg = True + + advertise_community = module.params['advertise_community'] + if advertise_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_community: + need_cfg = True + else: + need_cfg = True + + advertise_ext_community = module.params['advertise_ext_community'] + if advertise_ext_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["advertise_ext_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != advertise_ext_community: + need_cfg = True + else: + need_cfg = True + + discard_ext_community = module.params['discard_ext_community'] + if discard_ext_community != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["discard_ext_community"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != discard_ext_community: + need_cfg = True + else: + need_cfg = True + + allow_as_loop_enable = module.params['allow_as_loop_enable'] + if allow_as_loop_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_as_loop_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != allow_as_loop_enable: + need_cfg = True + else: + need_cfg = True + + allow_as_loop_limit = module.params['allow_as_loop_limit'] + if allow_as_loop_limit: + if int(allow_as_loop_limit) > 10 or int(allow_as_loop_limit) < 1: + module.fail_json( + msg='the value of allow_as_loop_limit %s is out of [1 - 10].' % allow_as_loop_limit) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["allow_as_loop_limit"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != allow_as_loop_limit: + need_cfg = True + else: + need_cfg = True + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["keep_all_routes"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != keep_all_routes: + need_cfg = True + else: + need_cfg = True + + nexthop_configure = module.params['nexthop_configure'] + if nexthop_configure: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + self.exist_nexthop_configure = "null" + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + self.exist_nexthop_configure = re_find[0] + result["nexthop_configure"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != nexthop_configure: + need_cfg = True + else: + need_cfg = True + + preferred_value = module.params['preferred_value'] + if preferred_value: + if int(preferred_value) > 65535 or int(preferred_value) < 0: + module.fail_json( + msg='the value of preferred_value %s is out of [0 - 65535].' % preferred_value) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["preferred_value"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != preferred_value: + need_cfg = True + else: + need_cfg = True + + public_as_only = module.params['public_as_only'] + if public_as_only != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only: + need_cfg = True + else: + need_cfg = True + + public_as_only_force = module.params['public_as_only_force'] + if public_as_only_force != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_force"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_force: + need_cfg = True + else: + need_cfg = True + + public_as_only_limited = module.params['public_as_only_limited'] + if public_as_only_limited != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_limited"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_limited: + need_cfg = True + else: + need_cfg = True + + public_as_only_replace = module.params['public_as_only_replace'] + if public_as_only_replace != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_replace"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_replace: + need_cfg = True + else: + need_cfg = True + + public_as_only_skip_peer_as = module.params[ + 'public_as_only_skip_peer_as'] + if public_as_only_skip_peer_as != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["public_as_only_skip_peer_as"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != public_as_only_skip_peer_as: + need_cfg = True + else: + need_cfg = True + + route_limit = module.params['route_limit'] + if route_limit: + + if int(route_limit) < 1: + module.fail_json( + msg='the value of route_limit %s is out of [1 - 4294967295].' % route_limit) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit: + need_cfg = True + else: + need_cfg = True + + route_limit_percent = module.params['route_limit_percent'] + if route_limit_percent: + + if int(route_limit_percent) < 1 or int(route_limit_percent) > 100: + module.fail_json( + msg='Error: The value of route_limit_percent %s is out of [1 - 100].' % route_limit_percent) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_percent"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_percent: + need_cfg = True + else: + need_cfg = True + + route_limit_type = module.params['route_limit_type'] + if route_limit_type: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_type"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_type: + need_cfg = True + else: + need_cfg = True + + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + if route_limit_idle_timeout: + + if int(route_limit_idle_timeout) < 1 or int(route_limit_idle_timeout) > 1200: + module.fail_json( + msg='Error: The value of route_limit_idle_timeout %s is out of ' + '[1 - 1200].' % route_limit_idle_timeout) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["route_limit_idle_timeout"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != route_limit_idle_timeout: + need_cfg = True + else: + need_cfg = True + + rt_updt_interval = module.params['rt_updt_interval'] + if rt_updt_interval: + + if int(rt_updt_interval) < 0 or int(rt_updt_interval) > 600: + module.fail_json( + msg='Error: The value of rt_updt_interval %s is out of [0 - 600].' % rt_updt_interval) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["rt_updt_interval"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != rt_updt_interval: + need_cfg = True + else: + need_cfg = True + + redirect_ip = module.params['redirect_ip'] + if redirect_ip != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["redirect_ip"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != redirect_ip: + need_cfg = True + else: + need_cfg = True + + redirect_ip_validation = module.params['redirect_ip_validation'] + if redirect_ip_validation != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["redirect_ip_validation"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != redirect_ip_validation: + need_cfg = True + else: + need_cfg = True + + reflect_client = module.params['reflect_client'] + if reflect_client != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["reflect_client"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != reflect_client: + need_cfg = True + else: + need_cfg = True + + substitute_as_enable = module.params['substitute_as_enable'] + if substitute_as_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["substitute_as_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != substitute_as_enable: + need_cfg = True + else: + need_cfg = True + + import_rt_policy_name = module.params['import_rt_policy_name'] + if import_rt_policy_name: + + if len(import_rt_policy_name) < 1 or len(import_rt_policy_name) > 40: + module.fail_json( + msg='Error: The len of import_rt_policy_name %s is out of [1 - 40].' % import_rt_policy_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_rt_policy_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_rt_policy_name: + need_cfg = True + else: + need_cfg = True + + export_rt_policy_name = module.params['export_rt_policy_name'] + if export_rt_policy_name: + + if len(export_rt_policy_name) < 1 or len(export_rt_policy_name) > 40: + module.fail_json( + msg='Error: The len of export_rt_policy_name %s is out of [1 - 40].' % export_rt_policy_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_rt_policy_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_rt_policy_name: + need_cfg = True + else: + need_cfg = True + + import_pref_filt_name = module.params['import_pref_filt_name'] + if import_pref_filt_name: + + if len(import_pref_filt_name) < 1 or len(import_pref_filt_name) > 169: + module.fail_json( + msg='Error: The len of import_pref_filt_name %s is out of [1 - 169].' % import_pref_filt_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_pref_filt_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_pref_filt_name: + need_cfg = True + else: + need_cfg = True + + export_pref_filt_name = module.params['export_pref_filt_name'] + if export_pref_filt_name: + + if len(export_pref_filt_name) < 1 or len(export_pref_filt_name) > 169: + module.fail_json( + msg='Error: The len of export_pref_filt_name %s is out of [1 - 169].' % export_pref_filt_name) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_pref_filt_name"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_pref_filt_name: + need_cfg = True + else: + need_cfg = True + + import_as_path_filter = module.params['import_as_path_filter'] + if import_as_path_filter: + + if int(import_as_path_filter) < 1 or int(import_as_path_filter) > 256: + module.fail_json( + msg='Error: The value of import_as_path_filter %s is out of [1 - 256].' % import_as_path_filter) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_as_path_filter"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_as_path_filter: + need_cfg = True + else: + need_cfg = True + + export_as_path_filter = module.params['export_as_path_filter'] + if export_as_path_filter: + + if int(export_as_path_filter) < 1 or int(export_as_path_filter) > 256: + module.fail_json( + msg='Error: The value of export_as_path_filter %s is out of [1 - 256].' % export_as_path_filter) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_as_path_filter"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_as_path_filter: + need_cfg = True + else: + need_cfg = True + + import_as_path_name_or_num = module.params[ + 'import_as_path_name_or_num'] + if import_as_path_name_or_num: + + if len(import_as_path_name_or_num) < 1 or len(import_as_path_name_or_num) > 51: + module.fail_json( + msg='Error: The len of import_as_path_name_or_num %s is out ' + 'of [1 - 51].' % import_as_path_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_as_path_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_as_path_name_or_num: + need_cfg = True + else: + need_cfg = True + + export_as_path_name_or_num = module.params[ + 'export_as_path_name_or_num'] + if export_as_path_name_or_num: + + if len(export_as_path_name_or_num) < 1 or len(export_as_path_name_or_num) > 51: + module.fail_json( + msg='Error: The len of export_as_path_name_or_num %s is out ' + 'of [1 - 51].' % export_as_path_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_as_path_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_as_path_name_or_num: + need_cfg = True + else: + need_cfg = True + + import_acl_name_or_num = module.params['import_acl_name_or_num'] + if import_acl_name_or_num: + + if len(import_acl_name_or_num) < 1 or len(import_acl_name_or_num) > 32: + module.fail_json( + msg='Error: The len of import_acl_name_or_num %s is out of [1 - 32].' % import_acl_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["import_acl_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != import_acl_name_or_num: + need_cfg = True + else: + need_cfg = True + + export_acl_name_or_num = module.params['export_acl_name_or_num'] + if export_acl_name_or_num: + + if len(export_acl_name_or_num) < 1 or len(export_acl_name_or_num) > 32: + module.fail_json( + msg='Error: The len of export_acl_name_or_num %s is out of [1 - 32].' % export_acl_name_or_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["export_acl_name_or_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != export_acl_name_or_num: + need_cfg = True + else: + need_cfg = True + + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + if ipprefix_orf_enable != 'no_use': + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["ipprefix_orf_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != ipprefix_orf_enable: + need_cfg = True + else: + need_cfg = True + + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + if is_nonstd_ipprefix_mod != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["is_nonstd_ipprefix_mod"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != is_nonstd_ipprefix_mod: + need_cfg = True + else: + need_cfg = True + + orftype = module.params['orftype'] + if orftype: + + if int(orftype) < 0 or int(orftype) > 65535: + module.fail_json( + msg='Error: The value of orftype %s is out of [0 - 65535].' % orftype) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["orftype"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != orftype: + need_cfg = True + else: + need_cfg = True + + orf_mode = module.params['orf_mode'] + if orf_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["orf_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != orf_mode: + need_cfg = True + else: + need_cfg = True + + soostring = module.params['soostring'] + if soostring: + + if len(soostring) < 3 or len(soostring) > 21: + module.fail_json( + msg='Error: The len of soostring %s is out of [3 - 21].' % soostring) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["soostring"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != soostring: + need_cfg = True + else: + need_cfg = True + + default_rt_adv_enable = module.params['default_rt_adv_enable'] + if default_rt_adv_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_adv_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_adv_enable: + need_cfg = True + else: + need_cfg = True + + default_rt_adv_policy = module.params['default_rt_adv_policy'] + if default_rt_adv_policy: + + if len(default_rt_adv_policy) < 1 or len(default_rt_adv_policy) > 40: + module.fail_json( + msg='Error: The len of default_rt_adv_policy %s is out of [1 - 40].' % default_rt_adv_policy) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_adv_policy"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_adv_policy: + need_cfg = True + else: + need_cfg = True + + default_rt_match_mode = module.params['default_rt_match_mode'] + if default_rt_match_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["default_rt_match_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != default_rt_match_mode: + need_cfg = True + else: + need_cfg = True + + add_path_mode = module.params['add_path_mode'] + if add_path_mode: + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["add_path_mode"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != add_path_mode: + need_cfg = True + else: + need_cfg = True + + adv_add_path_num = module.params['adv_add_path_num'] + if adv_add_path_num: + + if int(adv_add_path_num) < 2 or int(adv_add_path_num) > 64: + module.fail_json( + msg='Error: The value of adv_add_path_num %s is out of [2 - 64].' % adv_add_path_num) + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["adv_add_path_num"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != adv_add_path_num: + need_cfg = True + else: + need_cfg = True + + origin_as_valid = module.params['origin_as_valid'] + if origin_as_valid != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["origin_as_valid"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != origin_as_valid: + need_cfg = True + else: + need_cfg = True + + vpls_enable = module.params['vpls_enable'] + if vpls_enable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vpls_enable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != vpls_enable: + need_cfg = True + else: + need_cfg = True + + vpls_ad_disable = module.params['vpls_ad_disable'] + if vpls_ad_disable != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["vpls_ad_disable"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != vpls_ad_disable: + need_cfg = True + else: + need_cfg = True + + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + if update_pkt_standard_compatible != 'no_use': + + conf_str = CE_GET_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + "" + \ + CE_GET_BGP_PEER_AF_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + need_cfg = True + else: + re_find = re.findall( + r'.*(.*).*', recv_xml) + + if re_find: + result["update_pkt_standard_compatible"] = re_find + result["vrf_name"] = vrf_name + result["af_type"] = af_type + if re_find[0] != update_pkt_standard_compatible: + need_cfg = True + else: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def merge_bgp_peer_af(self, **kwargs): + """ merge_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_MERGE_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + CE_MERGE_BGP_PEER_AF_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "peer %s enable" % remote_address + else: + cmd = "peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def create_bgp_peer_af(self, **kwargs): + """ create_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_CREATE_BGP_PEER_AF % (vrf_name, af_type, remote_address) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "peer %s enable" % remote_address + else: + cmd = "peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def delete_bgp_peer_af(self, **kwargs): + """ delete_bgp_peer_af """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_DELETE_BGP_PEER_AF % (vrf_name, af_type, remote_address) + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete bgp peer address family failed.') + + cmds = [] + cmd = af_type + if af_type == "ipv4uni": + if vrf_name == "_public_": + cmd = "ipv4-family unicast" + else: + cmd = "ipv4-family vpn-instance %s" % vrf_name + elif af_type == "ipv4multi": + cmd = "ipv4-family multicast" + elif af_type == "ipv6uni": + if vrf_name == "_public_": + cmd = "ipv6-family unicast" + else: + cmd = "ipv6-family vpn-instance %s" % vrf_name + elif af_type == "evpn": + cmd = "l2vpn-family evpn" + elif af_type == "ipv4vpn": + cmd = "ipv4-family vpnv4" + elif af_type == "ipv6vpn": + cmd = "ipv6-family vpnv6" + cmds.append(cmd) + if vrf_name == "_public_": + cmd = "undo peer %s enable" % remote_address + else: + cmd = "undo peer %s" % remote_address + cmds.append(cmd) + + return cmds + + def merge_bgp_peer_af_other(self, **kwargs): + """ merge_bgp_peer_af_other """ + + module = kwargs["module"] + + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + + conf_str = CE_MERGE_BGP_PEER_AF_HEADER % ( + vrf_name, af_type, remote_address) + + cmds = [] + + advertise_irb = module.params['advertise_irb'] + if advertise_irb != 'no_use': + conf_str += "%s" % advertise_irb + + if advertise_irb == "true": + cmd = "peer %s advertise irb" % remote_address + else: + cmd = "undo peer %s advertise irb" % remote_address + cmds.append(cmd) + + advertise_arp = module.params['advertise_arp'] + if advertise_arp != 'no_use': + conf_str += "%s" % advertise_arp + + if advertise_arp == "true": + cmd = "peer %s advertise arp" % remote_address + else: + cmd = "undo peer %s advertise arp" % remote_address + cmds.append(cmd) + + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + if advertise_remote_nexthop != 'no_use': + conf_str += "%s" % advertise_remote_nexthop + + if advertise_remote_nexthop == "true": + cmd = "peer %s advertise remote-nexthop" % remote_address + else: + cmd = "undo peer %s advertise remote-nexthop" % remote_address + cmds.append(cmd) + + advertise_community = module.params['advertise_community'] + if advertise_community != 'no_use': + conf_str += "%s" % advertise_community + + if advertise_community == "true": + cmd = "peer %s advertise-community" % remote_address + else: + cmd = "undo peer %s advertise-community" % remote_address + cmds.append(cmd) + + advertise_ext_community = module.params['advertise_ext_community'] + if advertise_ext_community != 'no_use': + conf_str += "%s" % advertise_ext_community + + if advertise_ext_community == "true": + cmd = "peer %s advertise-ext-community" % remote_address + else: + cmd = "undo peer %s advertise-ext-community" % remote_address + cmds.append(cmd) + + discard_ext_community = module.params['discard_ext_community'] + if discard_ext_community != 'no_use': + conf_str += "%s" % discard_ext_community + + if discard_ext_community == "true": + cmd = "peer %s discard-ext-community" % remote_address + else: + cmd = "undo peer %s discard-ext-community" % remote_address + cmds.append(cmd) + + allow_as_loop_enable = module.params['allow_as_loop_enable'] + if allow_as_loop_enable != 'no_use': + conf_str += "%s" % allow_as_loop_enable + + if allow_as_loop_enable == "true": + cmd = "peer %s allow-as-loop" % remote_address + else: + cmd = "undo peer %s allow-as-loop" % remote_address + cmds.append(cmd) + + allow_as_loop_limit = module.params['allow_as_loop_limit'] + if allow_as_loop_limit: + conf_str += "%s" % allow_as_loop_limit + + if allow_as_loop_enable == "true": + cmd = "peer %s allow-as-loop %s" % (remote_address, allow_as_loop_limit) + else: + cmd = "undo peer %s allow-as-loop" % remote_address + cmds.append(cmd) + + keep_all_routes = module.params['keep_all_routes'] + if keep_all_routes != 'no_use': + conf_str += "%s" % keep_all_routes + + if keep_all_routes == "true": + cmd = "peer %s keep-all-routes" % remote_address + else: + cmd = "undo peer %s keep-all-routes" % remote_address + cmds.append(cmd) + + nexthop_configure = module.params['nexthop_configure'] + if nexthop_configure: + conf_str += "%s" % nexthop_configure + + if nexthop_configure == "local": + cmd = "peer %s next-hop-local" % remote_address + cmds.append(cmd) + elif nexthop_configure == "invariable": + cmd = "peer %s next-hop-invariable" % remote_address + cmds.append(cmd) + else: + if self.exist_nexthop_configure != "null": + if self.exist_nexthop_configure == "local": + cmd = "undo peer %s next-hop-local" % remote_address + cmds.append(cmd) + elif self.exist_nexthop_configure == "invariable": + cmd = "undo peer %s next-hop-invariable" % remote_address + cmds.append(cmd) + preferred_value = module.params['preferred_value'] + if preferred_value: + conf_str += "%s" % preferred_value + + cmd = "peer %s preferred-value %s" % (remote_address, preferred_value) + cmds.append(cmd) + + public_as_only = module.params['public_as_only'] + if public_as_only != 'no_use': + conf_str += "%s" % public_as_only + + if public_as_only == "true": + cmd = "peer %s public-as-only" % remote_address + else: + cmd = "undo peer %s public-as-only" % remote_address + cmds.append(cmd) + + public_as_only_force = module.params['public_as_only_force'] + if public_as_only_force != 'no_use': + conf_str += "%s" % public_as_only_force + + if public_as_only_force == "true": + cmd = "peer %s public-as-only force" % remote_address + else: + cmd = "undo peer %s public-as-only force" % remote_address + cmds.append(cmd) + + public_as_only_limited = module.params['public_as_only_limited'] + if public_as_only_limited != 'no_use': + conf_str += "%s" % public_as_only_limited + + if public_as_only_limited == "true": + cmd = "peer %s public-as-only limited" % remote_address + else: + cmd = "undo peer %s public-as-only limited" % remote_address + cmds.append(cmd) + + public_as_only_replace = module.params['public_as_only_replace'] + if public_as_only_replace != 'no_use': + conf_str += "%s" % public_as_only_replace + + if public_as_only_replace == "true": + if public_as_only_force != "no_use": + cmd = "peer %s public-as-only force replace" % remote_address + if public_as_only_limited != "no_use": + cmd = "peer %s public-as-only limited replace" % remote_address + else: + if public_as_only_force != "no_use": + cmd = "undo peer %s public-as-only force replace" % remote_address + if public_as_only_limited != "no_use": + cmd = "undo peer %s public-as-only limited replace" % remote_address + cmds.append(cmd) + + public_as_only_skip_peer_as = module.params[ + 'public_as_only_skip_peer_as'] + if public_as_only_skip_peer_as != 'no_use': + conf_str += "%s" % public_as_only_skip_peer_as + + if public_as_only_skip_peer_as == "true": + if public_as_only_force != "no_use": + cmd = "peer %s public-as-only force include-peer-as" % remote_address + if public_as_only_limited != "no_use": + cmd = "peer %s public-as-only limited include-peer-as" % remote_address + else: + if public_as_only_force != "no_use": + cmd = "undo peer %s public-as-only force include-peer-as" % remote_address + if public_as_only_limited != "no_use": + cmd = "undo peer %s public-as-only limited include-peer-as" % remote_address + cmds.append(cmd) + + route_limit_sign = "route-limit" + if af_type == "evpn": + route_limit_sign = "mac-limit" + route_limit = module.params['route_limit'] + if route_limit: + conf_str += "%s" % route_limit + + cmd = "peer %s %s %s" % (remote_address, route_limit_sign, route_limit) + cmds.append(cmd) + + route_limit_percent = module.params['route_limit_percent'] + if route_limit_percent: + conf_str += "%s" % route_limit_percent + + cmd = "peer %s %s %s %s" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + + route_limit_type = module.params['route_limit_type'] + if route_limit_type: + conf_str += "%s" % route_limit_type + + if route_limit_type == "alertOnly": + cmd = "peer %s %s %s %s alert-only" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + elif route_limit_type == "idleForever": + cmd = "peer %s %s %s %s idle-forever" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + elif route_limit_type == "idleTimeout": + cmd = "peer %s %s %s %s idle-timeout" % (remote_address, route_limit_sign, route_limit, route_limit_percent) + cmds.append(cmd) + + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + if route_limit_idle_timeout: + conf_str += "%s" % route_limit_idle_timeout + + cmd = "peer %s %s %s %s idle-timeout %s" % (remote_address, route_limit_sign, route_limit, route_limit_percent, route_limit_idle_timeout) + cmds.append(cmd) + + rt_updt_interval = module.params['rt_updt_interval'] + if rt_updt_interval: + conf_str += "%s" % rt_updt_interval + + cmd = "peer %s route-update-interval %s" % (remote_address, rt_updt_interval) + cmds.append(cmd) + + redirect_ip = module.params['redirect_ip'] + if redirect_ip != 'no_use': + conf_str += "%s" % redirect_ip + + redirect_ip_validation = module.params['redirect_ip_validation'] + if redirect_ip_validation != 'no_use': + conf_str += "%s" % redirect_ip_validation + + reflect_client = module.params['reflect_client'] + if reflect_client != 'no_use': + conf_str += "%s" % reflect_client + + if reflect_client == "true": + cmd = "peer %s reflect-client" % remote_address + else: + cmd = "undo peer %s reflect-client" % remote_address + cmds.append(cmd) + + substitute_as_enable = module.params['substitute_as_enable'] + if substitute_as_enable != 'no_use': + conf_str += "%s" % substitute_as_enable + + if substitute_as_enable == "true": + cmd = "peer %s substitute-as" % remote_address + else: + cmd = "undo peer %s substitute-as" % remote_address + cmds.append(cmd) + + import_rt_policy_name = module.params['import_rt_policy_name'] + if import_rt_policy_name: + conf_str += "%s" % import_rt_policy_name + + cmd = "peer %s route-policy %s import" % (remote_address, import_rt_policy_name) + cmds.append(cmd) + + export_rt_policy_name = module.params['export_rt_policy_name'] + if export_rt_policy_name: + conf_str += "%s" % export_rt_policy_name + + cmd = "peer %s route-policy %s export" % (remote_address, export_rt_policy_name) + cmds.append(cmd) + + import_pref_filt_name = module.params['import_pref_filt_name'] + if import_pref_filt_name: + conf_str += "%s" % import_pref_filt_name + + cmd = "peer %s ip-prefix %s import" % (remote_address, import_pref_filt_name) + cmds.append(cmd) + + export_pref_filt_name = module.params['export_pref_filt_name'] + if export_pref_filt_name: + conf_str += "%s" % export_pref_filt_name + + cmd = "peer %s ip-prefix %s export" % (remote_address, export_pref_filt_name) + cmds.append(cmd) + + import_as_path_filter = module.params['import_as_path_filter'] + if import_as_path_filter: + conf_str += "%s" % import_as_path_filter + + cmd = "peer %s as-path-filter %s import" % (remote_address, import_as_path_filter) + cmds.append(cmd) + + export_as_path_filter = module.params['export_as_path_filter'] + if export_as_path_filter: + conf_str += "%s" % export_as_path_filter + + cmd = "peer %s as-path-filter %s export" % (remote_address, export_as_path_filter) + cmds.append(cmd) + + import_as_path_name_or_num = module.params[ + 'import_as_path_name_or_num'] + if import_as_path_name_or_num: + conf_str += "%s" % import_as_path_name_or_num + + cmd = "peer %s as-path-filter %s import" % (remote_address, import_as_path_name_or_num) + cmds.append(cmd) + + export_as_path_name_or_num = module.params[ + 'export_as_path_name_or_num'] + if export_as_path_name_or_num: + conf_str += "%s" % export_as_path_name_or_num + + cmd = "peer %s as-path-filter %s export" % (remote_address, export_as_path_name_or_num) + cmds.append(cmd) + + import_acl_name_or_num = module.params['import_acl_name_or_num'] + if import_acl_name_or_num: + conf_str += "%s" % import_acl_name_or_num + if import_acl_name_or_num.isdigit(): + cmd = "peer %s filter-policy %s import" % (remote_address, import_acl_name_or_num) + else: + cmd = "peer %s filter-policy acl-name %s import" % (remote_address, import_acl_name_or_num) + cmds.append(cmd) + + export_acl_name_or_num = module.params['export_acl_name_or_num'] + if export_acl_name_or_num: + conf_str += "%s" % export_acl_name_or_num + if export_acl_name_or_num.isdigit(): + cmd = "peer %s filter-policy %s export" % (remote_address, export_acl_name_or_num) + else: + cmd = "peer %s filter-policy acl-name %s export" % (remote_address, export_acl_name_or_num) + cmds.append(cmd) + + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + if ipprefix_orf_enable != 'no_use': + conf_str += "%s" % ipprefix_orf_enable + + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf ip-prefix" % remote_address + else: + cmd = "undo peer %s capability-advertise orf ip-prefix" % remote_address + cmds.append(cmd) + + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + if is_nonstd_ipprefix_mod != 'no_use': + conf_str += "%s" % is_nonstd_ipprefix_mod + + if is_nonstd_ipprefix_mod == "true": + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf non-standard-compatible" % remote_address + else: + cmd = "undo peer %s capability-advertise orf non-standard-compatible" % remote_address + cmds.append(cmd) + else: + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf" % remote_address + else: + cmd = "undo peer %s capability-advertise orf" % remote_address + cmds.append(cmd) + + orftype = module.params['orftype'] + if orftype: + conf_str += "%s" % orftype + + orf_mode = module.params['orf_mode'] + if orf_mode: + conf_str += "%s" % orf_mode + + if ipprefix_orf_enable == "true": + cmd = "peer %s capability-advertise orf ip-prefix %s" % (remote_address, orf_mode) + else: + cmd = "undo peer %s capability-advertise orf ip-prefix %s" % (remote_address, orf_mode) + cmds.append(cmd) + + soostring = module.params['soostring'] + if soostring: + conf_str += "%s" % soostring + + cmd = "peer %s soo %s" % (remote_address, soostring) + cmds.append(cmd) + + cmd = "" + default_rt_adv_enable = module.params['default_rt_adv_enable'] + if default_rt_adv_enable != 'no_use': + conf_str += "%s" % default_rt_adv_enable + + if default_rt_adv_enable == "true": + cmd += "peer %s default-route-advertise" % remote_address + else: + cmd += "undo peer %s default-route-advertise" % remote_address + + default_rt_adv_policy = module.params['default_rt_adv_policy'] + if default_rt_adv_policy: + conf_str += "%s" % default_rt_adv_policy + cmd += " route-policy %s" % default_rt_adv_policy + + default_rt_match_mode = module.params['default_rt_match_mode'] + if default_rt_match_mode: + conf_str += "%s" % default_rt_match_mode + + if default_rt_match_mode == "matchall": + cmd += " conditional-route-match-all" + elif default_rt_match_mode == "matchany": + cmd += " conditional-route-match-any" + + if cmd: + cmds.append(cmd) + + add_path_mode = module.params['add_path_mode'] + if add_path_mode: + conf_str += "%s" % add_path_mode + if add_path_mode == "receive": + cmd = "peer %s capability-advertise add-path receive" % remote_address + elif add_path_mode == "send": + cmd = "peer %s capability-advertise add-path send" % remote_address + elif add_path_mode == "both": + cmd = "peer %s capability-advertise add-path both" % remote_address + cmds.append(cmd) + + adv_add_path_num = module.params['adv_add_path_num'] + if adv_add_path_num: + conf_str += "%s" % adv_add_path_num + cmd = "peer %s advertise add-path path-number %s" % (remote_address, adv_add_path_num) + cmds.append(cmd) + origin_as_valid = module.params['origin_as_valid'] + if origin_as_valid != 'no_use': + conf_str += "%s" % origin_as_valid + + vpls_enable = module.params['vpls_enable'] + if vpls_enable != 'no_use': + conf_str += "%s" % vpls_enable + + vpls_ad_disable = module.params['vpls_ad_disable'] + if vpls_ad_disable != 'no_use': + conf_str += "%s" % vpls_ad_disable + + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + if update_pkt_standard_compatible != 'no_use': + conf_str += "%s" % update_pkt_standard_compatible + + conf_str += CE_MERGE_BGP_PEER_AF_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge bgp peer address family other failed.') + + return cmds + + +def main(): + """ main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + vrf_name=dict(type='str', required=True), + af_type=dict(choices=['ipv4uni', 'ipv4multi', 'ipv4vpn', + 'ipv6uni', 'ipv6vpn', 'evpn'], required=True), + remote_address=dict(type='str', required=True), + advertise_irb=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_arp=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_remote_nexthop=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + advertise_ext_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + discard_ext_community=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + allow_as_loop_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + allow_as_loop_limit=dict(type='str'), + keep_all_routes=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + nexthop_configure=dict(choices=['null', 'local', 'invariable']), + preferred_value=dict(type='str'), + public_as_only=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_force=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_limited=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_replace=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + public_as_only_skip_peer_as=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + route_limit=dict(type='str'), + route_limit_percent=dict(type='str'), + route_limit_type=dict( + choices=['noparameter', 'alertOnly', 'idleForever', 'idleTimeout']), + route_limit_idle_timeout=dict(type='str'), + rt_updt_interval=dict(type='str'), + redirect_ip=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + redirect_ip_validation=dict( + type='str', default='no_use', + choices=['no_use', 'true', 'false'], aliases=['redirect_ip_vaildation']), + reflect_client=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + substitute_as_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + import_rt_policy_name=dict(type='str'), + export_rt_policy_name=dict(type='str'), + import_pref_filt_name=dict(type='str'), + export_pref_filt_name=dict(type='str'), + import_as_path_filter=dict(type='str'), + export_as_path_filter=dict(type='str'), + import_as_path_name_or_num=dict(type='str'), + export_as_path_name_or_num=dict(type='str'), + import_acl_name_or_num=dict(type='str'), + export_acl_name_or_num=dict(type='str'), + ipprefix_orf_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + is_nonstd_ipprefix_mod=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + orftype=dict(type='str'), + orf_mode=dict(choices=['null', 'receive', 'send', 'both']), + soostring=dict(type='str'), + default_rt_adv_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + default_rt_adv_policy=dict(type='str'), + default_rt_match_mode=dict(choices=['null', 'matchall', 'matchany']), + add_path_mode=dict(choices=['null', 'receive', 'send', 'both']), + adv_add_path_num=dict(type='str'), + origin_as_valid=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + vpls_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + vpls_ad_disable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + update_pkt_standard_compatible=dict(type='str', default='no_use', choices=['no_use', 'true', 'false'])) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + vrf_name = module.params['vrf_name'] + af_type = module.params['af_type'] + remote_address = module.params['remote_address'] + advertise_irb = module.params['advertise_irb'] + advertise_arp = module.params['advertise_arp'] + advertise_remote_nexthop = module.params['advertise_remote_nexthop'] + advertise_community = module.params['advertise_community'] + advertise_ext_community = module.params['advertise_ext_community'] + discard_ext_community = module.params['discard_ext_community'] + allow_as_loop_enable = module.params['allow_as_loop_enable'] + allow_as_loop_limit = module.params['allow_as_loop_limit'] + keep_all_routes = module.params['keep_all_routes'] + nexthop_configure = module.params['nexthop_configure'] + preferred_value = module.params['preferred_value'] + public_as_only = module.params['public_as_only'] + public_as_only_force = module.params['public_as_only_force'] + public_as_only_limited = module.params['public_as_only_limited'] + public_as_only_replace = module.params['public_as_only_replace'] + public_as_only_skip_peer_as = module.params['public_as_only_skip_peer_as'] + route_limit = module.params['route_limit'] + route_limit_percent = module.params['route_limit_percent'] + route_limit_type = module.params['route_limit_type'] + route_limit_idle_timeout = module.params['route_limit_idle_timeout'] + rt_updt_interval = module.params['rt_updt_interval'] + redirect_ip = module.params['redirect_ip'] + redirect_ip_validation = module.params['redirect_ip_validation'] + reflect_client = module.params['reflect_client'] + substitute_as_enable = module.params['substitute_as_enable'] + import_rt_policy_name = module.params['import_rt_policy_name'] + export_rt_policy_name = module.params['export_rt_policy_name'] + import_pref_filt_name = module.params['import_pref_filt_name'] + export_pref_filt_name = module.params['export_pref_filt_name'] + import_as_path_filter = module.params['import_as_path_filter'] + export_as_path_filter = module.params['export_as_path_filter'] + import_as_path_name_or_num = module.params['import_as_path_name_or_num'] + export_as_path_name_or_num = module.params['export_as_path_name_or_num'] + import_acl_name_or_num = module.params['import_acl_name_or_num'] + export_acl_name_or_num = module.params['export_acl_name_or_num'] + ipprefix_orf_enable = module.params['ipprefix_orf_enable'] + is_nonstd_ipprefix_mod = module.params['is_nonstd_ipprefix_mod'] + orftype = module.params['orftype'] + orf_mode = module.params['orf_mode'] + soostring = module.params['soostring'] + default_rt_adv_enable = module.params['default_rt_adv_enable'] + default_rt_adv_policy = module.params['default_rt_adv_policy'] + default_rt_match_mode = module.params['default_rt_match_mode'] + add_path_mode = module.params['add_path_mode'] + adv_add_path_num = module.params['adv_add_path_num'] + origin_as_valid = module.params['origin_as_valid'] + vpls_enable = module.params['vpls_enable'] + vpls_ad_disable = module.params['vpls_ad_disable'] + update_pkt_standard_compatible = module.params[ + 'update_pkt_standard_compatible'] + + ce_bgp_peer_af_obj = BgpNeighborAf() + + # get proposed + proposed["state"] = state + if vrf_name: + proposed["vrf_name"] = vrf_name + if af_type: + proposed["af_type"] = af_type + if remote_address: + proposed["remote_address"] = remote_address + if advertise_irb != 'no_use': + proposed["advertise_irb"] = advertise_irb + if advertise_arp != 'no_use': + proposed["advertise_arp"] = advertise_arp + if advertise_remote_nexthop != 'no_use': + proposed["advertise_remote_nexthop"] = advertise_remote_nexthop + if advertise_community != 'no_use': + proposed["advertise_community"] = advertise_community + if advertise_ext_community != 'no_use': + proposed["advertise_ext_community"] = advertise_ext_community + if discard_ext_community != 'no_use': + proposed["discard_ext_community"] = discard_ext_community + if allow_as_loop_enable != 'no_use': + proposed["allow_as_loop_enable"] = allow_as_loop_enable + if allow_as_loop_limit: + proposed["allow_as_loop_limit"] = allow_as_loop_limit + if keep_all_routes != 'no_use': + proposed["keep_all_routes"] = keep_all_routes + if nexthop_configure: + proposed["nexthop_configure"] = nexthop_configure + if preferred_value: + proposed["preferred_value"] = preferred_value + if public_as_only != 'no_use': + proposed["public_as_only"] = public_as_only + if public_as_only_force != 'no_use': + proposed["public_as_only_force"] = public_as_only_force + if public_as_only_limited != 'no_use': + proposed["public_as_only_limited"] = public_as_only_limited + if public_as_only_replace != 'no_use': + proposed["public_as_only_replace"] = public_as_only_replace + if public_as_only_skip_peer_as != 'no_use': + proposed["public_as_only_skip_peer_as"] = public_as_only_skip_peer_as + if route_limit: + proposed["route_limit"] = route_limit + if route_limit_percent: + proposed["route_limit_percent"] = route_limit_percent + if route_limit_type: + proposed["route_limit_type"] = route_limit_type + if route_limit_idle_timeout: + proposed["route_limit_idle_timeout"] = route_limit_idle_timeout + if rt_updt_interval: + proposed["rt_updt_interval"] = rt_updt_interval + if redirect_ip != 'no_use': + proposed["redirect_ip"] = redirect_ip + if redirect_ip_validation != 'no_use': + proposed["redirect_ip_validation"] = redirect_ip_validation + if reflect_client != 'no_use': + proposed["reflect_client"] = reflect_client + if substitute_as_enable != 'no_use': + proposed["substitute_as_enable"] = substitute_as_enable + if import_rt_policy_name: + proposed["import_rt_policy_name"] = import_rt_policy_name + if export_rt_policy_name: + proposed["export_rt_policy_name"] = export_rt_policy_name + if import_pref_filt_name: + proposed["import_pref_filt_name"] = import_pref_filt_name + if export_pref_filt_name: + proposed["export_pref_filt_name"] = export_pref_filt_name + if import_as_path_filter: + proposed["import_as_path_filter"] = import_as_path_filter + if export_as_path_filter: + proposed["export_as_path_filter"] = export_as_path_filter + if import_as_path_name_or_num: + proposed["import_as_path_name_or_num"] = import_as_path_name_or_num + if export_as_path_name_or_num: + proposed["export_as_path_name_or_num"] = export_as_path_name_or_num + if import_acl_name_or_num: + proposed["import_acl_name_or_num"] = import_acl_name_or_num + if export_acl_name_or_num: + proposed["export_acl_name_or_num"] = export_acl_name_or_num + if ipprefix_orf_enable != 'no_use': + proposed["ipprefix_orf_enable"] = ipprefix_orf_enable + if is_nonstd_ipprefix_mod != 'no_use': + proposed["is_nonstd_ipprefix_mod"] = is_nonstd_ipprefix_mod + if orftype: + proposed["orftype"] = orftype + if orf_mode: + proposed["orf_mode"] = orf_mode + if soostring: + proposed["soostring"] = soostring + if default_rt_adv_enable != 'no_use': + proposed["default_rt_adv_enable"] = default_rt_adv_enable + if default_rt_adv_policy: + proposed["default_rt_adv_policy"] = default_rt_adv_policy + if default_rt_match_mode: + proposed["default_rt_match_mode"] = default_rt_match_mode + if add_path_mode: + proposed["add_path_mode"] = add_path_mode + if adv_add_path_num: + proposed["adv_add_path_num"] = adv_add_path_num + if origin_as_valid != 'no_use': + proposed["origin_as_valid"] = origin_as_valid + if vpls_enable != 'no_use': + proposed["vpls_enable"] = vpls_enable + if vpls_ad_disable != 'no_use': + proposed["vpls_ad_disable"] = vpls_ad_disable + if update_pkt_standard_compatible != 'no_use': + proposed["update_pkt_standard_compatible"] = update_pkt_standard_compatible + + if not ce_bgp_peer_af_obj: + module.fail_json(msg='Error: Init module failed.') + + bgp_peer_af_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_args( + module=module) + bgp_peer_af_other_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_other( + module=module) + + # state exist bgp peer address family config + exist_tmp = dict() + for item in bgp_peer_af_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_peer_af_rst[item] + if exist_tmp: + existing["bgp neighbor af"] = exist_tmp + # state exist bgp peer address family other config + exist_tmp = dict() + for item in bgp_peer_af_other_rst: + if item != "need_cfg": + exist_tmp[item] = bgp_peer_af_other_rst[item] + if exist_tmp: + existing["bgp neighbor af other"] = exist_tmp + + if state == "present": + if bgp_peer_af_rst["need_cfg"]: + if "remote_address" in bgp_peer_af_rst.keys(): + cmd = ce_bgp_peer_af_obj.merge_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + else: + cmd = ce_bgp_peer_af_obj.create_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_peer_af_other_rst["need_cfg"]: + cmd = ce_bgp_peer_af_obj.merge_bgp_peer_af_other(module=module) + changed = True + for item in cmd: + updates.append(item) + + else: + if bgp_peer_af_rst["need_cfg"]: + cmd = ce_bgp_peer_af_obj.delete_bgp_peer_af(module=module) + changed = True + for item in cmd: + updates.append(item) + + if bgp_peer_af_other_rst["need_cfg"]: + pass + + # state end bgp peer address family config + bgp_peer_af_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_args( + module=module) + end_tmp = dict() + for item in bgp_peer_af_rst: + if item != "need_cfg": + end_tmp[item] = bgp_peer_af_rst[item] + if end_tmp: + end_state["bgp neighbor af"] = end_tmp + # state end bgp peer address family other config + bgp_peer_af_other_rst = ce_bgp_peer_af_obj.check_bgp_neighbor_af_other( + module=module) + end_tmp = dict() + for item in bgp_peer_af_other_rst: + if item != "need_cfg": + end_tmp[item] = bgp_peer_af_other_rst[item] + if end_tmp: + end_state["bgp neighbor af other"] = end_tmp + if end_state == existing: + changed = False + updates = list() + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_command.py new file mode 100644 index 00000000..4bfe3dc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_command.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_command +author: "JackyGao2016 (@CloudEngine-Ansible)" +short_description: Run arbitrary command on HUAWEI CloudEngine devices. +description: + - Sends an arbitrary command to an HUAWEI CloudEngine node and returns + the results read from the device. The ce_command module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + commands: + description: + - The commands to send to the remote HUAWEI CloudEngine device + over the configured provider. The resulting output from the + command is returned. If the I(wait_for) argument is provided, + the module is not returned until the condition is satisfied + or the number of I(retries) has been exceeded. + required: true + wait_for: + description: + - Specifies what to evaluate from the output of the command + and what conditionals to apply. This argument will cause + the task to wait for a particular conditional to be true + before moving forward. If the conditional is not true + by the configured retries, the task fails. See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the I(wait_for) must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the I(wait_for) + conditionals. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditional, the interval indicates how to long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine command test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: "Run display version on remote devices" + community.network.ce_command: + commands: display version + provider: "{{ cli }}" + + - name: "Run display version and check to see if output contains HUAWEI" + community.network.ce_command: + commands: display version + wait_for: result[0] contains HUAWEI + provider: "{{ cli }}" + + - name: "Run multiple commands on remote nodes" + community.network.ce_command: + commands: + - display version + - display device + provider: "{{ cli }}" + + - name: "Run multiple commands and evaluate the output" + community.network.ce_command: + commands: + - display version + - display device + wait_for: + - result[0] contains HUAWEI + - result[1] contains Device + provider: "{{ cli }}" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + + +import time +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_native + + +def to_lines(stdout): + lines = list() + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + lines.append(item) + return lines + + +def parse_commands(module, warnings): + transform = ComplexList(dict( + command=dict(key=True), + output=dict(), + prompt=dict(), + answer=dict() + ), module) + + commands = transform(module.params['commands']) + + for _, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('dis'): + warnings.append( + 'Only display commands are supported when using check_mode, not ' + 'executing %s' % item['command'] + ) + + return commands + + +def to_cli(obj): + cmd = obj['command'] + return cmd + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + # { command: , output: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['any', 'all']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + + try: + conditionals = [Conditional(c) for c in wait_for] + except AttributeError as exc: + module.fail_json(msg=to_native(exc), exception=traceback.format_exc()) + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'stdout': responses, + 'stdout_lines': to_lines(responses) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_config.py new file mode 100644 index 00000000..2f8e71bb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_config.py @@ -0,0 +1,492 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_config +author: "QijunPan (@QijunPan)" +short_description: Manage Huawei CloudEngine configuration sections. +description: + - Huawei CloudEngine configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with CloudEngine configuration sections in + a deterministic way. This module works with CLI transports. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device current-configuration. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - The I(src) argument provides a path to the configuration file + to load into the remote system. The path can either be a full + system path to the configuration file if the value starts with / + or relative to the root of the implemented role or playbook. + This argument is mutually exclusive with the I(lines) and + I(parents) arguments. + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the current-configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - This argument will cause the module to create a full backup of + the current C(current-configuration) from the remote device before any + changes are made. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current current-configuration to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current-configuration for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + defaults: + description: + - The I(defaults) argument will influence how the current-configuration + is collected from the device. When the value is set to true, + the command used to collect the current-configuration is append with + the all keyword. When the value is set to false, the command + is issued without the all keyword. + type: bool + default: 'no' + save: + description: + - The C(save) argument instructs the module to save the + current-configuration to saved-configuration. This operation is performed + after any changes are made to the current running config. If + no changes are made, the configuration is still saved to the + startup config. This option will always cause the module to + return changed. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine config test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: "Configure top level configuration and save it" + community.network.ce_config: + lines: sysname {{ inventory_hostname }} + save: yes + provider: "{{ cli }}" + + - name: "Configure acl configuration and save it" + community.network.ce_config: + lines: + - rule 10 permit source 1.1.1.1 32 + - rule 20 permit source 2.2.2.2 32 + - rule 30 permit source 3.3.3.3 32 + - rule 40 permit source 4.4.4.4 32 + - rule 50 permit source 5.5.5.5 32 + parents: acl 2000 + before: undo acl 2000 + match: exact + provider: "{{ cli }}" + + - name: "Configure acl configuration and save it" + community.network.ce_config: + lines: + - rule 10 permit source 1.1.1.1 32 + - rule 20 permit source 2.2.2.2 32 + - rule 30 permit source 3.3.3.3 32 + - rule 40 permit source 4.4.4.4 32 + parents: acl 2000 + before: undo acl 2000 + replace: block + provider: "{{ cli }}" + + - name: Configurable backup path + community.network.ce_config: + lines: sysname {{ inventory_hostname }} + provider: "{{ cli }}" + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ce_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError, Connection +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig as _NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps, ConfigLine, ignore_line +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_config, run_commands, exec_command, cli_err_msg +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import check_args as ce_check_args +import re + + +def check_args(module, warnings): + ce_check_args(module, warnings) + + +def not_user_view(prompt): + return prompt is not None and prompt.strip().startswith("[") + + +def command_level(command): + regex_level = re.search(r"^(\s*)\S+", command) + if regex_level is not None: + level = str(regex_level.group(1)) + return len(level) + return 0 + + +def _load_config(module, config): + """Sends configuration commands to the remote device + """ + connection = Connection(module._socket_path) + rc, out, err = exec_command(module, 'mmi-mode enable') + if rc != 0: + module.fail_json(msg='unable to set mmi-mode enable', output=err) + rc, out, err = exec_command(module, 'system-view immediately') + if rc != 0: + module.fail_json(msg='unable to enter system-view', output=err) + current_view_prompt = system_view_prompt = connection.get_prompt() + + for index, cmd in enumerate(config): + level = command_level(cmd) + current_view_prompt = connection.get_prompt() + rc, out, err = exec_command(module, cmd) + if rc != 0: + print_msg = cli_err_msg(cmd.strip(), err) + # re-try command max 3 times + for i in (1, 2, 3): + current_view_prompt = connection.get_prompt() + if current_view_prompt != system_view_prompt and not_user_view(current_view_prompt): + exec_command(module, "quit") + current_view_prompt = connection.get_prompt() + # if current view is system-view, break. + if current_view_prompt == system_view_prompt and level > 0: + break + elif current_view_prompt == system_view_prompt or not not_user_view(current_view_prompt): + break + rc, out, err = exec_command(module, cmd) + if rc == 0: + print_msg = None + break + if print_msg is not None: + module.fail_json(msg=print_msg) + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + command = "display current-configuration " + if module.params['defaults']: + command += 'include-default' + resp = run_commands(module, command) + contents = resp[0] + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + config = module.params['src'] + candidate.load(config) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + + if match != 'none': + before = get_running_config(module) + path = module.params['parents'] + configobjs = candidate.difference(before, match=match, replace=replace, path=path) + else: + configobjs = candidate.items + + if configobjs: + out_type = "commands" + if module.params["src"] is not None: + out_type = "raw" + commands = dumps(configobjs, out_type).split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + command_display = [] + for per_command in commands: + if per_command.strip() not in ['quit', 'return', 'system-view']: + command_display.append(per_command) + + result['commands'] = command_display + result['updates'] = command_display + + if not module.check_mode: + if module.params['parents'] is not None: + load_config(module, commands) + else: + _load_config(module, commands) + if match != "none": + after = get_running_config(module) + path = module.params["parents"] + if path is not None and match != 'line': + before_objs = before.get_block(path) + after_objs = after.get_block(path) + update = [] + if len(before_objs) == len(after_objs): + for b_item, a_item in zip(before_objs, after_objs): + if b_item != a_item: + update.append(a_item.text) + else: + update = [item.text for item in after_objs] + if len(update) == 0: + result["changed"] = False + result['updates'] = [] + else: + result["changed"] = True + result['updates'] = update + else: + configobjs = after.difference(before, match=match, replace=replace, path=path) + if len(configobjs) > 0: + result["changed"] = True + else: + result["changed"] = False + result['updates'] = [] + else: + result['changed'] = True + + +class NetworkConfig(_NetworkConfig): + + def add(self, lines, parents=None): + ancestors = list() + offset = 0 + obj = None + + # global config command + if not parents: + for line in lines: + # handle ignore lines + if ignore_line(line): + continue + + item = ConfigLine(line) + item.raw = line + self.items.append(item) + + else: + for index, p in enumerate(parents): + try: + i = index + 1 + obj = self.get_block(parents[:i])[0] + ancestors.append(obj) + + except ValueError: + # add parent to config + offset = index * self._indent + obj = ConfigLine(p) + obj.raw = p.rjust(len(p) + offset) + if ancestors: + obj._parents = list(ancestors) + ancestors[-1]._children.append(obj) + self.items.append(obj) + ancestors.append(obj) + + # add child objects + for line in lines: + # handle ignore lines + if ignore_line(line): + continue + + # check if child already exists + for child in ancestors[-1]._children: + if child.text == line: + break + else: + offset = len(parents) * self._indent + item = ConfigLine(line) + item.raw = line.rjust(len(line) + offset) + item._parents = ancestors + ancestors[-1]._children.append(item) + self.items.append(item) + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + config=dict(), + defaults=dict(type='bool', default=False), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + argument_spec.update(ce_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + if any((module.params['src'], module.params['lines'])): + run(module, result) + + if module.params['save']: + if not module.check_mode: + run_commands(module, ['return', 'mmi-mode enable', 'save']) + result["changed"] = True + run_commands(module, ['return', 'undo mmi-mode enable']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp.py new file mode 100644 index 00000000..f292e0b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp.py @@ -0,0 +1,549 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_dldp +short_description: Manages global DLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages global DLDP configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - The relevant configurations will be deleted if DLDP is disabled using enable=disable. + - When using auth_mode=none, it will restore the default DLDP authentication mode. By default, + DLDP packets are not authenticated. + - By default, the working mode of DLDP is enhance, so you are advised to use work_mode=enhance to restore default + DLDP working mode. + - The default interval for sending Advertisement packets is 5 seconds, so you are advised to use time_interval=5 to + restore default DLDP interval. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + enable: + description: + - Set global DLDP enable state. + choices: ['enable', 'disable'] + work_mode: + description: + - Set global DLDP work-mode. + choices: ['enhance', 'normal'] + time_internal: + description: + - Specifies the interval for sending Advertisement packets. + The value is an integer ranging from 1 to 100, in seconds. + The default interval for sending Advertisement packets is 5 seconds. + auth_mode: + description: + - Specifies authentication algorithm of DLDP. + choices: ['md5', 'simple', 'sha', 'hmac-sha256', 'none'] + auth_pwd: + description: + - Specifies authentication password. + The value is a string of 1 to 16 case-sensitive plaintexts or 24/32/48/108/128 case-sensitive encrypted + characters. The string excludes a question mark (?). + reset: + description: + - Specify whether reset DLDP state of disabled interfaces. + choices: ['enable', 'disable'] +''' + +EXAMPLES = ''' +- name: DLDP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure global DLDP enable state" + community.network.ce_dldp: + enable: enable + provider: "{{ cli }}" + + - name: "Configure DLDP work-mode and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + work_mode: normal + provider: "{{ cli }}" + + - name: "Configure advertisement message time interval in seconds and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + time_interval: 6 + provider: "{{ cli }}" + + - name: "Configure a DLDP authentication mode and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + auth_mode: md5 + auth_pwd: abc + provider: "{{ cli }}" + + - name: "Reset DLDP state of disabled interfaces and ensure global DLDP state is already enabled" + community.network.ce_dldp: + enable: enable + reset: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "enable": "enable", + "reset": "enable", + "time_internal": "12", + "work_mode": "normal" + } +existing: + description: k/v pairs of existing global DLDP configuration + returned: always + type: dict + sample: { + "enable": "disable", + "reset": "disable", + "time_internal": "5", + "work_mode": "enhance" + } +end_state: + description: k/v pairs of global DLDP configuration after module execution + returned: always + type: dict + sample: { + "enable": "enable", + "reset": "enable", + "time_internal": "12", + "work_mode": "normal" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "dldp enable", + "dldp work-mode normal", + "dldp interval 12", + "dldp reset" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, set_nc_config, get_nc_config, execute_nc_action + +CE_NC_ACTION_RESET_DLDP = """ + + + + + +""" + +CE_NC_GET_GLOBAL_DLDP_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_DLDP_GLOBAL_CONFIG_HEAD = """ + + + + %s + %s + %s +""" + +CE_NC_MERGE_DLDP_GLOBAL_CONFIG_TAIL = """ + + + +""" + + +class Dldp(object): + """Manage global dldp configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # DLDP global configuration info + self.enable = self.module.params['enable'] or None + self.work_mode = self.module.params['work_mode'] or None + self.internal = self.module.params['time_interval'] or None + self.reset = self.module.params['reset'] or None + self.auth_mode = self.module.params['auth_mode'] + self.auth_pwd = self.module.params['auth_pwd'] + + self.dldp_conf = dict() + self.same_conf = False + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + def check_config_if_same(self): + """Judge whether current config is the same as what we excepted""" + + if self.enable and self.enable != self.dldp_conf['dldpEnable']: + return False + + if self.internal and self.internal != self.dldp_conf['dldpInterval']: + return False + + work_mode = 'normal' + if self.dldp_conf['dldpWorkMode'] == 'dldpEnhance': + work_mode = 'enhance' + if self.work_mode and self.work_mode != work_mode: + return False + + if self.auth_mode: + if self.auth_mode != 'none': + return False + + if self.auth_mode == 'none' and self.dldp_conf['dldpAuthMode'] != 'dldpAuthNone': + return False + + if self.reset and self.reset == 'enable': + return False + + return True + + def check_params(self): + """Check all input params""" + + if (self.auth_mode and self.auth_mode != 'none' and not self.auth_pwd) \ + or (self.auth_pwd and not self.auth_mode): + self.module.fail_json(msg="Error: auth_mode and auth_pwd must both exist or not exist.") + + if self.dldp_conf['dldpEnable'] == 'disable' and not self.enable: + if self.work_mode or self.reset or self.internal or self.auth_mode: + self.module.fail_json(msg="Error: when DLDP is already disabled globally, " + "work_mode, time_internal auth_mode and reset parameters are not " + "expected to configure.") + + if self.enable == 'disable' and (self.work_mode or self.internal or self.reset or self.auth_mode): + self.module.fail_json(msg="Error: when using enable=disable, work_mode, " + "time_internal auth_mode and reset parameters are not expected " + "to configure.") + + if self.internal: + if not self.internal.isdigit(): + self.module.fail_json( + msg='Error: time_interval must be digit.') + + if int(self.internal) < 1 or int(self.internal) > 100: + self.module.fail_json( + msg='Error: The value of time_internal should be between 1 and 100.') + + if self.auth_pwd: + if '?' in self.auth_pwd: + self.module.fail_json( + msg='Error: The auth_pwd string excludes a question mark (?).') + if (len(self.auth_pwd) != 24) and (len(self.auth_pwd) != 32) and (len(self.auth_pwd) != 48) and \ + (len(self.auth_pwd) != 108) and (len(self.auth_pwd) != 128): + if (len(self.auth_pwd) < 1) or (len(self.auth_pwd) > 16): + self.module.fail_json( + msg='Error: The value is a string of 1 to 16 case-sensitive plaintexts or 24/32/48/108/128 ' + 'case-sensitive encrypted characters.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dldp_exist_config(self): + """Get current dldp existed configuration""" + + dldp_conf = dict() + xml_str = CE_NC_GET_GLOBAL_DLDP_CONFIG + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return dldp_conf + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global DLDP info + root = ElementTree.fromstring(xml_str) + topo = root.find("dldp/dldpSys") + if not topo: + self.module.fail_json( + msg="Error: Get current DLDP configuration failed.") + + for eles in topo: + if eles.tag in ["dldpEnable", "dldpInterval", "dldpWorkMode", "dldpAuthMode"]: + if eles.tag == 'dldpEnable': + if eles.text == 'true': + value = 'enable' + else: + value = 'disable' + else: + value = eles.text + dldp_conf[eles.tag] = value + + return dldp_conf + + def config_global_dldp(self): + """Config global dldp""" + + if self.same_conf: + return + + enable = self.enable + if not self.enable: + enable = self.dldp_conf['dldpEnable'] + if enable == 'enable': + enable = 'true' + else: + enable = 'false' + + internal = self.internal + if not self.internal: + internal = self.dldp_conf['dldpInterval'] + + work_mode = self.work_mode + if not self.work_mode: + work_mode = self.dldp_conf['dldpWorkMode'] + + if work_mode == 'enhance' or work_mode == 'dldpEnhance': + work_mode = 'dldpEnhance' + else: + work_mode = 'dldpNormal' + + auth_mode = self.auth_mode + if not self.auth_mode: + auth_mode = self.dldp_conf['dldpAuthMode'] + if auth_mode == 'md5': + auth_mode = 'dldpAuthMD5' + elif auth_mode == 'simple': + auth_mode = 'dldpAuthSimple' + elif auth_mode == 'sha': + auth_mode = 'dldpAuthSHA' + elif auth_mode == 'hmac-sha256': + auth_mode = 'dldpAuthHMAC-SHA256' + elif auth_mode == 'none': + auth_mode = 'dldpAuthNone' + + xml_str = CE_NC_MERGE_DLDP_GLOBAL_CONFIG_HEAD % ( + enable, internal, work_mode) + if self.auth_mode: + if self.auth_mode == 'none': + xml_str += "dldpAuthNone" + else: + xml_str += "%s" % auth_mode + xml_str += "%s" % self.auth_pwd + + xml_str += CE_NC_MERGE_DLDP_GLOBAL_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MERGE_DLDP_GLOBAL_CONFIG") + + if self.reset == 'enable': + xml_str = CE_NC_ACTION_RESET_DLDP + ret_xml = execute_nc_action(self.module, xml_str) + self.check_response(ret_xml, "ACTION_RESET_DLDP") + + self.changed = True + + def get_existing(self): + """Get existing info""" + + dldp_conf = dict() + + dldp_conf['enable'] = self.dldp_conf.get('dldpEnable', None) + dldp_conf['time_interval'] = self.dldp_conf.get('dldpInterval', None) + work_mode = self.dldp_conf.get('dldpWorkMode', None) + if work_mode == 'dldpEnhance': + dldp_conf['work_mode'] = 'enhance' + else: + dldp_conf['work_mode'] = 'normal' + + auth_mode = self.dldp_conf.get('dldpAuthMode', None) + if auth_mode == 'dldpAuthNone': + dldp_conf['auth_mode'] = 'none' + elif auth_mode == 'dldpAuthSimple': + dldp_conf['auth_mode'] = 'simple' + elif auth_mode == 'dldpAuthMD5': + dldp_conf['auth_mode'] = 'md5' + elif auth_mode == 'dldpAuthSHA': + dldp_conf['auth_mode'] = 'sha' + else: + dldp_conf['auth_mode'] = 'hmac-sha256' + + dldp_conf['reset'] = 'disable' + + self.existing = copy.deepcopy(dldp_conf) + + def get_proposed(self): + """Get proposed result""" + + self.proposed = dict(enable=self.enable, work_mode=self.work_mode, + time_interval=self.internal, reset=self.reset, + auth_mode=self.auth_mode, auth_pwd=self.auth_pwd) + + def get_update_cmd(self): + """Get update commands""" + if self.same_conf: + return + + if self.enable and self.enable != self.dldp_conf['dldpEnable']: + if self.enable == 'enable': + self.updates_cmd.append("dldp enable") + elif self.enable == 'disable': + self.updates_cmd.append("undo dldp enable") + return + + work_mode = 'normal' + if self.dldp_conf['dldpWorkMode'] == 'dldpEnhance': + work_mode = 'enhance' + if self.work_mode and self.work_mode != work_mode: + if self.work_mode == 'enhance': + self.updates_cmd.append("dldp work-mode enhance") + else: + self.updates_cmd.append("dldp work-mode normal") + + if self.internal and self.internal != self.dldp_conf['dldpInterval']: + self.updates_cmd.append("dldp interval %s" % self.internal) + + if self.auth_mode: + if self.auth_mode == 'none': + self.updates_cmd.append("undo dldp authentication-mode") + else: + self.updates_cmd.append("dldp authentication-mode %s %s" % (self.auth_mode, self.auth_pwd)) + + if self.reset and self.reset == 'enable': + self.updates_cmd.append('dldp reset') + + def get_end_state(self): + """Get end state info""" + + dldp_conf = dict() + self.dldp_conf = self.get_dldp_exist_config() + + dldp_conf['enable'] = self.dldp_conf.get('dldpEnable', None) + dldp_conf['time_interval'] = self.dldp_conf.get('dldpInterval', None) + work_mode = self.dldp_conf.get('dldpWorkMode', None) + if work_mode == 'dldpEnhance': + dldp_conf['work_mode'] = 'enhance' + else: + dldp_conf['work_mode'] = 'normal' + + auth_mode = self.dldp_conf.get('dldpAuthMode', None) + if auth_mode == 'dldpAuthNone': + dldp_conf['auth_mode'] = 'none' + elif auth_mode == 'dldpAuthSimple': + dldp_conf['auth_mode'] = 'simple' + elif auth_mode == 'dldpAuthMD5': + dldp_conf['auth_mode'] = 'md5' + elif auth_mode == 'dldpAuthSHA': + dldp_conf['auth_mode'] = 'sha' + else: + dldp_conf['auth_mode'] = 'hmac-sha256' + + dldp_conf['reset'] = 'disable' + if self.reset == 'enable': + dldp_conf['reset'] = 'enable' + self.end_state = copy.deepcopy(dldp_conf) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Worker""" + + self.dldp_conf = self.get_dldp_exist_config() + self.check_params() + self.same_conf = self.check_config_if_same() + self.get_existing() + self.get_proposed() + self.config_global_dldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + enable=dict(choices=['enable', 'disable'], type='str'), + work_mode=dict(choices=['enhance', 'normal'], type='str'), + time_interval=dict(type='str'), + reset=dict(choices=['enable', 'disable'], type='str'), + auth_mode=dict(choices=['md5', 'simple', 'sha', 'hmac-sha256', 'none'], type='str'), + auth_pwd=dict(type='str', no_log=True), + ) + argument_spec.update(ce_argument_spec) + dldp_obj = Dldp(argument_spec) + dldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp_interface.py new file mode 100644 index 00000000..8a9d965a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_dldp_interface.py @@ -0,0 +1,658 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_dldp_interface +short_description: Manages interface DLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages interface DLDP configuration on HUAWEI CloudEngine switches. +author: + - Zhou Zhijin (@QijunPan) +notes: + - If C(state=present, enable=disable), interface DLDP enable will be turned off and + related interface DLDP configuration will be cleared. + - If C(state=absent), only local_mac is supported to configure. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Must be fully qualified interface name, i.e. GE1/0/1, 10GE1/0/1, 40GE1/0/22, 100GE1/0/1. + required: true + enable: + description: + - Set interface DLDP enable state. + choices: ['enable', 'disable'] + mode_enable: + description: + - Set DLDP compatible-mode enable state. + choices: ['enable', 'disable'] + local_mac: + description: + - Set the source MAC address for DLDP packets sent in the DLDP-compatible mode. + The value of MAC address is in H-H-H format. H contains 1 to 4 hexadecimal digits. + reset: + description: + - Specify whether reseting interface DLDP state. + choices: ['enable', 'disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: DLDP interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure interface DLDP enable state and ensure global dldp enable is turned on" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + provider: "{{ cli }}" + + - name: "Configuire interface DLDP compatible-mode enable state and ensure interface DLDP state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + mode_enable: enable + provider: "{{ cli }}" + + - name: "Configuire the source MAC address for DLDP packets sent in the DLDP-compatible mode and + ensure interface DLDP state and compatible-mode enable state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + mode_enable: enable + local_mac: aa-aa-aa + provider: "{{ cli }}" + + - name: "Reset DLDP state of specified interface and ensure interface DLDP state is already enabled" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + enable: enable + reset: enable + provider: "{{ cli }}" + + - name: "Unconfigure interface DLDP local mac address when C(state=absent)" + community.network.ce_dldp_interface: + interface: 40GE2/0/1 + state: absent + local_mac: aa-aa-aa + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "enable": "enalbe", + "interface": "40GE2/0/22", + "local_mac": "aa-aa-aa", + "mode_enable": "enable", + "reset": "enable" + } +existing: + description: k/v pairs of existing interface DLDP configuration + returned: always + type: dict + sample: { + "enable": "disable", + "interface": "40GE2/0/22", + "local_mac": null, + "mode_enable": null, + "reset": "disable" + } +end_state: + description: k/v pairs of interface DLDP configuration after module execution + returned: always + type: dict + sample: { + "enable": "enable", + "interface": "40GE2/0/22", + "local_mac": "00aa-00aa-00aa", + "mode_enable": "enable", + "reset": "enable" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "dldp enable", + "dldp compatible-mode enable", + "dldp compatible-mode local-mac aa-aa-aa", + "dldp reset" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, set_nc_config, get_nc_config, execute_nc_action + + +CE_NC_ACTION_RESET_INTF_DLDP = """ + + + + %s + + + +""" + +CE_NC_GET_INTF_DLDP_CONFIG = """ + + + + + %s + + + + + + + +""" + +CE_NC_MERGE_DLDP_INTF_CONFIG = """ + + + + + %s + %s + %s + %s + + + + +""" + +CE_NC_CREATE_DLDP_INTF_CONFIG = """ + + + + + %s + %s + %s + %s + + + + +""" + +CE_NC_DELETE_DLDP_INTF_CONFIG = """ + + + + + %s + + + + +""" + + +def judge_is_mac_same(mac1, mac2): + """Judge whether two macs are the same""" + + if mac1 == mac2: + return True + + list1 = re.findall(r'([0-9A-Fa-f]+)', mac1) + list2 = re.findall(r'([0-9A-Fa-f]+)', mac2) + if len(list1) != len(list2): + return False + + for index, value in enumerate(list1, start=0): + if value.lstrip('0').lower() != list2[index].lstrip('0').lower(): + return False + + return True + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class DldpInterface(object): + """Manage interface dldp configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # DLDP interface configuration info + self.interface = self.module.params['interface'] + self.enable = self.module.params['enable'] or None + self.reset = self.module.params['reset'] or None + self.mode_enable = self.module.params['mode_enable'] or None + self.local_mac = self.module.params['local_mac'] or None + self.state = self.module.params['state'] + + self.dldp_intf_conf = dict() + self.same_conf = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + def check_config_if_same(self): + """Judge whether current config is the same as what we excepted""" + + if self.state == 'absent': + return False + else: + if self.enable and self.enable != self.dldp_intf_conf['dldpEnable']: + return False + + if self.mode_enable and self.mode_enable != self.dldp_intf_conf['dldpCompatibleEnable']: + return False + + if self.local_mac: + flag = judge_is_mac_same( + self.local_mac, self.dldp_intf_conf['dldpLocalMac']) + if not flag: + return False + + if self.reset and self.reset == 'enable': + return False + return True + + def check_macaddr(self): + """Check mac-address whether valid""" + + valid_char = '0123456789abcdef-' + mac = self.local_mac + + if len(mac) > 16: + return False + + mac_list = re.findall(r'([0-9a-fA-F]+)', mac) + if len(mac_list) != 3: + return False + + if mac.count('-') != 2: + return False + + for _, value in enumerate(mac, start=0): + if value.lower() not in valid_char: + return False + + return True + + def check_params(self): + """Check all input params""" + + if not self.interface: + self.module.fail_json(msg='Error: Interface name cannot be empty.') + + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if (self.state == 'absent') and (self.reset or self.mode_enable or self.enable): + self.module.fail_json(msg="Error: It's better to use state=present when " + "configuring or unconfiguring enable, mode_enable " + "or using reset flag. state=absent is just for " + "when using local_mac param.") + + if self.state == 'absent' and not self.local_mac: + self.module.fail_json( + msg="Error: Please specify local_mac parameter.") + + if self.state == 'present': + if (self.dldp_intf_conf['dldpEnable'] == 'disable' and not self.enable and + (self.mode_enable or self.local_mac or self.reset)): + self.module.fail_json(msg="Error: when DLDP is already disabled on this port, " + "mode_enable, local_mac and reset parameters are not " + "expected to configure.") + + if self.enable == 'disable' and (self.mode_enable or self.local_mac or self.reset): + self.module.fail_json(msg="Error: when using enable=disable, " + "mode_enable, local_mac and reset parameters " + "are not expected to configure.") + + if self.local_mac and (self.mode_enable == 'disable' or + (self.dldp_intf_conf['dldpCompatibleEnable'] == 'disable' and self.mode_enable != 'enable')): + self.module.fail_json(msg="Error: when DLDP compatible-mode is disabled on this port, " + "Configuring local_mac is not allowed.") + + if self.local_mac: + if not self.check_macaddr(): + self.module.fail_json( + msg="Error: local_mac has invalid value %s." % self.local_mac) + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dldp_intf_exist_config(self): + """Get current dldp existed config""" + + dldp_conf = dict() + xml_str = CE_NC_GET_INTF_DLDP_CONFIG % self.interface + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + dldp_conf['dldpEnable'] = 'disable' + dldp_conf['dldpCompatibleEnable'] = "" + dldp_conf['dldpLocalMac'] = "" + return dldp_conf + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global DLDP info + root = ElementTree.fromstring(xml_str) + topo = root.find("dldp/dldpInterfaces/dldpInterface") + if topo is None: + self.module.fail_json( + msg="Error: Get current DLDP configuration failed.") + for eles in topo: + if eles.tag in ["dldpEnable", "dldpCompatibleEnable", "dldpLocalMac"]: + if not eles.text: + dldp_conf[eles.tag] = "" + else: + if eles.tag == "dldpEnable" or eles.tag == "dldpCompatibleEnable": + if eles.text == 'true': + value = 'enable' + else: + value = 'disable' + else: + value = eles.text + dldp_conf[eles.tag] = value + + return dldp_conf + + def config_intf_dldp(self): + """Config global dldp""" + + if self.same_conf: + return + + if self.state == "present": + enable = self.enable + if not self.enable: + enable = self.dldp_intf_conf['dldpEnable'] + if enable == 'enable': + enable = 'true' + else: + enable = 'false' + + mode_enable = self.mode_enable + if not self.mode_enable: + mode_enable = self.dldp_intf_conf['dldpCompatibleEnable'] + if mode_enable == 'enable': + mode_enable = 'true' + else: + mode_enable = 'false' + + local_mac = self.local_mac + if not self.local_mac: + local_mac = self.dldp_intf_conf['dldpLocalMac'] + + if self.enable == 'disable' and self.enable != self.dldp_intf_conf['dldpEnable']: + xml_str = CE_NC_DELETE_DLDP_INTF_CONFIG % self.interface + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "DELETE_DLDP_INTF_CONFIG") + elif self.dldp_intf_conf['dldpEnable'] == 'disable' and self.enable == 'enable': + xml_str = CE_NC_CREATE_DLDP_INTF_CONFIG % ( + self.interface, 'true', mode_enable, local_mac) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "CREATE_DLDP_INTF_CONFIG") + elif self.dldp_intf_conf['dldpEnable'] == 'enable': + if mode_enable == 'false': + local_mac = '' + xml_str = CE_NC_MERGE_DLDP_INTF_CONFIG % ( + self.interface, enable, mode_enable, local_mac) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MERGE_DLDP_INTF_CONFIG") + + if self.reset == 'enable': + xml_str = CE_NC_ACTION_RESET_INTF_DLDP % self.interface + ret_xml = execute_nc_action(self.module, xml_str) + self.check_response(ret_xml, "ACTION_RESET_INTF_DLDP") + + self.changed = True + else: + if self.local_mac and judge_is_mac_same(self.local_mac, self.dldp_intf_conf['dldpLocalMac']): + if self.dldp_intf_conf['dldpEnable'] == 'enable': + dldp_enable = 'true' + else: + dldp_enable = 'false' + if self.dldp_intf_conf['dldpCompatibleEnable'] == 'enable': + dldp_compat_enable = 'true' + else: + dldp_compat_enable = 'false' + xml_str = CE_NC_MERGE_DLDP_INTF_CONFIG % (self.interface, dldp_enable, dldp_compat_enable, '') + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "UNDO_DLDP_INTF_LOCAL_MAC_CONFIG") + self.changed = True + + def get_existing(self): + """Get existing info""" + + dldp_conf = dict() + + dldp_conf['interface'] = self.interface + dldp_conf['enable'] = self.dldp_intf_conf.get('dldpEnable', None) + dldp_conf['mode_enable'] = self.dldp_intf_conf.get( + 'dldpCompatibleEnable', None) + dldp_conf['local_mac'] = self.dldp_intf_conf.get('dldpLocalMac', None) + dldp_conf['reset'] = 'disable' + + self.existing = copy.deepcopy(dldp_conf) + + def get_proposed(self): + """Get proposed result """ + + self.proposed = dict(interface=self.interface, enable=self.enable, + mode_enable=self.mode_enable, local_mac=self.local_mac, + reset=self.reset, state=self.state) + + def get_update_cmd(self): + """Get updated commands""" + + if self.same_conf: + return + + if self.state == "present": + if self.enable and self.enable != self.dldp_intf_conf['dldpEnable']: + if self.enable == 'enable': + self.updates_cmd.append("dldp enable") + elif self.enable == 'disable': + self.updates_cmd.append("undo dldp enable") + + if self.mode_enable and self.mode_enable != self.dldp_intf_conf['dldpCompatibleEnable']: + if self.mode_enable == 'enable': + self.updates_cmd.append("dldp compatible-mode enable") + else: + self.updates_cmd.append("undo dldp compatible-mode enable") + + if self.local_mac: + flag = judge_is_mac_same( + self.local_mac, self.dldp_intf_conf['dldpLocalMac']) + if not flag: + self.updates_cmd.append( + "dldp compatible-mode local-mac %s" % self.local_mac) + + if self.reset and self.reset == 'enable': + self.updates_cmd.append('dldp reset') + else: + if self.changed: + self.updates_cmd.append("undo dldp compatible-mode local-mac") + + def get_end_state(self): + """Get end state info""" + + dldp_conf = dict() + self.dldp_intf_conf = self.get_dldp_intf_exist_config() + + dldp_conf['interface'] = self.interface + dldp_conf['enable'] = self.dldp_intf_conf.get('dldpEnable', None) + dldp_conf['mode_enable'] = self.dldp_intf_conf.get( + 'dldpCompatibleEnable', None) + dldp_conf['local_mac'] = self.dldp_intf_conf.get('dldpLocalMac', None) + dldp_conf['reset'] = 'disable' + if self.reset == 'enable': + dldp_conf['reset'] = 'enable' + + self.end_state = copy.deepcopy(dldp_conf) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Execute task""" + + self.dldp_intf_conf = self.get_dldp_intf_exist_config() + self.check_params() + self.same_conf = self.check_config_if_same() + self.get_existing() + self.get_proposed() + self.config_intf_dldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + enable=dict(choices=['enable', 'disable'], type='str'), + reset=dict(choices=['enable', 'disable'], type='str'), + mode_enable=dict(choices=['enable', 'disable'], type='str'), + local_mac=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + dldp_intf_obj = DldpInterface(argument_spec) + dldp_intf_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_eth_trunk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_eth_trunk.py new file mode 100644 index 00000000..c658f93f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_eth_trunk.py @@ -0,0 +1,672 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_eth_trunk +short_description: Manages Eth-Trunk interfaces on HUAWEI CloudEngine switches. +description: + - Manages Eth-Trunk specific configuration parameters on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - C(state=absent) removes the Eth-Trunk config and interface if it + already exists. If members to be removed are not explicitly + passed, all existing members (if any), are removed, + and Eth-Trunk removed. + - Members must be a list. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + trunk_id: + description: + - Eth-Trunk interface number. + The value is an integer. + The value range depends on the assign forward eth-trunk mode command. + When 256 is specified, the value ranges from 0 to 255. + When 512 is specified, the value ranges from 0 to 511. + When 1024 is specified, the value ranges from 0 to 1023. + required: true + mode: + description: + - Specifies the working mode of an Eth-Trunk interface. + choices: ['manual','lacp-dynamic','lacp-static'] + min_links: + description: + - Specifies the minimum number of Eth-Trunk member links in the Up state. + The value is an integer ranging from 1 to the maximum number of interfaces + that can be added to a Eth-Trunk interface. + hash_type: + description: + - Hash algorithm used for load balancing among Eth-Trunk member interfaces. + choices: ['src-dst-ip', 'src-dst-mac', 'enhanced', 'dst-ip', 'dst-mac', 'src-ip', 'src-mac'] + members: + description: + - List of interfaces that will be managed in a given Eth-Trunk. + The interface name must be full name. + force: + description: + - When true it forces Eth-Trunk members to match what is + declared in the members param. This can be used to remove + members. + type: bool + default: 'no' + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Eth_trunk module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure Eth-Trunk100 is created, add two members, and set to mode lacp-static + community.network.ce_eth_trunk: + trunk_id: 100 + members: ['10GE1/0/24','10GE1/0/25'] + mode: 'lacp-static' + state: present + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"trunk_id": "100", "members": ['10GE1/0/24','10GE1/0/25'], "mode": "lacp-static"} +existing: + description: k/v pairs of existing Eth-Trunk + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "manual"} +end_state: + description: k/v pairs of Eth-Trunk info after module execution + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/24", "memberIfState": "Down"}, + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "lacp-static"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Eth-Trunk 100", + "mode lacp-static", + "interface 10GE1/0/25", + "eth-trunk 100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_TRUNK = """ + + + + + Eth-Trunk%s + + + + + + + + + + + + + + + + + +""" + +CE_NC_XML_BUILD_TRUNK_CFG = """ + + + %s + + +""" + +CE_NC_XML_DELETE_TRUNK = """ + + Eth-Trunk%s + +""" + +CE_NC_XML_CREATE_TRUNK = """ + + Eth-Trunk%s + +""" + +CE_NC_XML_MERGE_MINUPNUM = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_HASHTYPE = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_WORKMODE = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_BUILD_MEMBER_CFG = """ + + Eth-Trunk%s + %s + +""" + +CE_NC_XML_MERGE_MEMBER = """ + + %s + +""" + +CE_NC_XML_DELETE_MEMBER = """ + + %s + +""" + +MODE_XML2CLI = {"Manual": "manual", "Dynamic": "lacp-dynamic", "Static": "lacp-static"} +MODE_CLI2XML = {"manual": "Manual", "lacp-dynamic": "Dynamic", "lacp-static": "Static"} +HASH_XML2CLI = {"IP": "src-dst-ip", "MAC": "src-dst-mac", "Enhanced": "enhanced", + "Desip": "dst-ip", "Desmac": "dst-mac", "Sourceip": "src-ip", "Sourcemac": "src-mac"} +HASH_CLI2XML = {"src-dst-ip": "IP", "src-dst-mac": "MAC", "enhanced": "Enhanced", + "dst-ip": "Desip", "dst-mac": "Desmac", "src-ip": "Sourceip", "src-mac": "Sourcemac"} + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +def mode_xml_to_cli_str(mode): + """convert mode to cli format string""" + + if not mode: + return "" + + return MODE_XML2CLI.get(mode) + + +def hash_type_xml_to_cli_str(hash_type): + """convert trunk hash type netconf xml to cli format string""" + + if not hash_type: + return "" + + return HASH_XML2CLI.get(hash_type) + + +class EthTrunk(object): + """ + Manages Eth-Trunk interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.trunk_id = self.module.params['trunk_id'] + self.mode = self.module.params['mode'] + self.min_links = self.module.params['min_links'] + self.hash_type = self.module.params['hash_type'] + self.members = self.module.params['members'] + self.state = self.module.params['state'] + self.force = self.module.params['force'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # interface info + self.trunk_info = dict() + + def __init_module__(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def netconf_set_config(self, xml_str, xml_name): + """ netconf set config """ + + recv_xml = set_nc_config(self.module, xml_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_trunk_dict(self, trunk_id): + """ get one interface attributes dict.""" + + trunk_info = dict() + conf_str = CE_NC_GET_TRUNK % trunk_id + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return trunk_info + + # get trunk base info + base = re.findall( + r'.*(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*\s*' + r'(.*).*', recv_xml) + + if base: + trunk_info = dict(ifName=base[0][0], + trunkId=base[0][0].lower().replace("eth-trunk", "").replace(" ", ""), + minUpNum=base[0][1], + maxUpNum=base[0][2], + trunkType=base[0][3], + hashType=base[0][4], + workMode=base[0][5], + upMemberIfNum=base[0][6], + memberIfNum=base[0][7]) + + # get trunk member interface info + member = re.findall( + r'.*(.*).*\s*' + r'(.*).*', recv_xml) + trunk_info["TrunkMemberIfs"] = list() + + for mem in member: + trunk_info["TrunkMemberIfs"].append( + dict(memberIfName=mem[0], memberIfState=mem[1])) + + return trunk_info + + def is_member_exist(self, ifname): + """is trunk member exist""" + + if not self.trunk_info["TrunkMemberIfs"]: + return False + + for mem in self.trunk_info["TrunkMemberIfs"]: + if ifname.replace(" ", "").upper() == mem["memberIfName"].replace(" ", "").upper(): + return True + + return False + + def get_mode_xml_str(self): + """trunk mode netconf xml format string""" + + return MODE_CLI2XML.get(self.mode) + + def get_hash_type_xml_str(self): + """trunk hash type netconf xml format string""" + + return HASH_CLI2XML.get(self.hash_type) + + def create_eth_trunk(self): + """Create Eth-Trunk interface""" + + xml_str = CE_NC_XML_CREATE_TRUNK % self.trunk_id + self.updates_cmd.append("interface Eth-Trunk %s" % self.trunk_id) + + if self.hash_type: + self.updates_cmd.append("load-balance %s" % self.hash_type) + xml_str += CE_NC_XML_MERGE_HASHTYPE % (self.trunk_id, self.get_hash_type_xml_str()) + + if self.mode: + self.updates_cmd.append("mode %s" % self.mode) + xml_str += CE_NC_XML_MERGE_WORKMODE % (self.trunk_id, self.get_mode_xml_str()) + + if self.min_links: + self.updates_cmd.append("least active-linknumber %s" % self.min_links) + xml_str += CE_NC_XML_MERGE_MINUPNUM % (self.trunk_id, self.min_links) + + if self.members: + mem_xml = "" + for mem in self.members: + mem_xml += CE_NC_XML_MERGE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("eth-trunk %s" % self.trunk_id) + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "CREATE_TRUNK") + self.changed = True + + def delete_eth_trunk(self): + """Delete Eth-Trunk interface and remove all member""" + + if not self.trunk_info: + return + + xml_str = "" + mem_str = "" + if self.trunk_info["TrunkMemberIfs"]: + for mem in self.trunk_info["TrunkMemberIfs"]: + mem_str += CE_NC_XML_DELETE_MEMBER % mem["memberIfName"] + self.updates_cmd.append("interface %s" % mem["memberIfName"]) + self.updates_cmd.append("undo eth-trunk") + if mem_str: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_str) + + xml_str += CE_NC_XML_DELETE_TRUNK % self.trunk_id + self.updates_cmd.append("undo interface Eth-Trunk %s" % self.trunk_id) + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "DELETE_TRUNK") + self.changed = True + + def remove_member(self): + """delete trunk member""" + + if not self.members: + return + + change = False + mem_xml = "" + xml_str = "" + for mem in self.members: + if self.is_member_exist(mem): + mem_xml += CE_NC_XML_DELETE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("undo eth-trunk") + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + change = True + + if not change: + return + + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "REMOVE_TRUNK_MEMBER") + self.changed = True + + def merge_eth_trunk(self): + """Create or merge Eth-Trunk""" + + change = False + xml_str = "" + self.updates_cmd.append("interface Eth-Trunk %s" % self.trunk_id) + if self.hash_type and self.get_hash_type_xml_str() != self.trunk_info["hashType"]: + self.updates_cmd.append("load-balance %s" % + self.hash_type) + xml_str += CE_NC_XML_MERGE_HASHTYPE % ( + self.trunk_id, self.get_hash_type_xml_str()) + change = True + if self.min_links and self.min_links != self.trunk_info["minUpNum"]: + self.updates_cmd.append( + "least active-linknumber %s" % self.min_links) + xml_str += CE_NC_XML_MERGE_MINUPNUM % ( + self.trunk_id, self.min_links) + change = True + if self.mode and self.get_mode_xml_str() != self.trunk_info["workMode"]: + self.updates_cmd.append("mode %s" % self.mode) + xml_str += CE_NC_XML_MERGE_WORKMODE % ( + self.trunk_id, self.get_mode_xml_str()) + change = True + + if not change: + self.updates_cmd.pop() # remove 'interface Eth-Trunk' command + + # deal force: + # When true it forces Eth-Trunk members to match + # what is declared in the members param. + if self.force and self.trunk_info["TrunkMemberIfs"]: + mem_xml = "" + for mem in self.trunk_info["TrunkMemberIfs"]: + if not self.members or mem["memberIfName"].replace(" ", "").upper() not in self.members: + mem_xml += CE_NC_XML_DELETE_MEMBER % mem["memberIfName"] + self.updates_cmd.append("interface %s" % mem["memberIfName"]) + self.updates_cmd.append("undo eth-trunk") + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % (self.trunk_id, mem_xml) + change = True + + if self.members: + mem_xml = "" + for mem in self.members: + if not self.is_member_exist(mem): + mem_xml += CE_NC_XML_MERGE_MEMBER % mem.upper() + self.updates_cmd.append("interface %s" % mem) + self.updates_cmd.append("eth-trunk %s" % self.trunk_id) + if mem_xml: + xml_str += CE_NC_XML_BUILD_MEMBER_CFG % ( + self.trunk_id, mem_xml) + change = True + + if not change: + return + + cfg_xml = CE_NC_XML_BUILD_TRUNK_CFG % xml_str + self.netconf_set_config(cfg_xml, "MERGE_TRUNK") + self.changed = True + + def check_params(self): + """Check all input params""" + + # trunk_id check + if not self.trunk_id.isdigit(): + self.module.fail_json(msg='The parameter of trunk_id is invalid.') + + # min_links check + if self.min_links and not self.min_links.isdigit(): + self.module.fail_json(msg='The parameter of min_links is invalid.') + + # members check and convert members to upper + if self.members: + for mem in self.members: + if not get_interface_type(mem.replace(" ", "")): + self.module.fail_json( + msg='The parameter of members is invalid.') + + for mem_id in range(len(self.members)): + self.members[mem_id] = self.members[mem_id].replace(" ", "").upper() + + def get_proposed(self): + """get proposed info""" + + self.proposed["trunk_id"] = self.trunk_id + self.proposed["mode"] = self.mode + if self.min_links: + self.proposed["min_links"] = self.min_links + self.proposed["hash_type"] = self.hash_type + if self.members: + self.proposed["members"] = self.members + self.proposed["state"] = self.state + self.proposed["force"] = self.force + + def get_existing(self): + """get existing info""" + + if not self.trunk_info: + return + + self.existing["trunk_id"] = self.trunk_info["trunkId"] + self.existing["min_links"] = self.trunk_info["minUpNum"] + self.existing["hash_type"] = hash_type_xml_to_cli_str(self.trunk_info["hashType"]) + self.existing["mode"] = mode_xml_to_cli_str(self.trunk_info["workMode"]) + self.existing["members_detail"] = self.trunk_info["TrunkMemberIfs"] + + def get_end_state(self): + """get end state info""" + + trunk_info = self.get_trunk_dict(self.trunk_id) + if not trunk_info: + return + + self.end_state["trunk_id"] = trunk_info["trunkId"] + self.end_state["min_links"] = trunk_info["minUpNum"] + self.end_state["hash_type"] = hash_type_xml_to_cli_str(trunk_info["hashType"]) + self.end_state["mode"] = mode_xml_to_cli_str(trunk_info["workMode"]) + self.end_state["members_detail"] = trunk_info["TrunkMemberIfs"] + + def work(self): + """worker""" + + self.check_params() + self.trunk_info = self.get_trunk_dict(self.trunk_id) + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.trunk_info: + # create + self.create_eth_trunk() + else: + # merge trunk + self.merge_eth_trunk() + else: + if self.trunk_info: + if not self.members: + # remove all members and delete trunk + self.delete_eth_trunk() + else: + # remove some trunk members + self.remove_member() + else: + self.module.fail_json(msg='Error: Eth-Trunk does not exist.') + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + trunk_id=dict(required=True), + mode=dict(required=False, + choices=['manual', 'lacp-dynamic', 'lacp-static'], + type='str'), + min_links=dict(required=False, type='str'), + hash_type=dict(required=False, + choices=['src-dst-ip', 'src-dst-mac', 'enhanced', + 'dst-ip', 'dst-mac', 'src-ip', 'src-mac'], + type='str'), + members=dict(required=False, default=None, type='list'), + force=dict(required=False, default=False, type='bool'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = EthTrunk(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bd_vni.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bd_vni.py new file mode 100644 index 00000000..1fcc8ae6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bd_vni.py @@ -0,0 +1,1053 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bd_vni +short_description: Manages EVPN VXLAN Network Identifier (VNI) on HUAWEI CloudEngine switches. +description: + - Manages Ethernet Virtual Private Network (EVPN) VXLAN Network + Identifier (VNI) configurations on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that EVPN has been configured to serve as the VXLAN control plane when state is present. + - Ensure that a bridge domain (BD) has existed when state is present. + - Ensure that a VNI has been created and associated with a broadcast domain (BD) when state is present. + - If you configure evpn:false to delete an EVPN instance, all configurations in the EVPN instance are deleted. + - After an EVPN instance has been created in the BD view, you can configure an RD using route_distinguisher + parameter in BD-EVPN instance view. + - Before configuring VPN targets for a BD EVPN instance, ensure that an RD has been configured + for the BD EVPN instance + - If you unconfigure route_distinguisher, all VPN target attributes for the BD EVPN instance will be removed at the same time. + - When using state:absent, evpn is not supported and it will be ignored. + - When using state:absent to delete VPN target attributes, ensure the configuration of VPN target attributes has + existed and otherwise it will report an error. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specify an existed bridge domain (BD).The value is an integer ranging from 1 to 16777215. + required: true + evpn: + description: + - Create or delete an EVPN instance for a VXLAN in BD view. + choices: ['enable','disable'] + default: 'enable' + route_distinguisher: + description: + - Configures a route distinguisher (RD) for a BD EVPN instance. + The format of an RD can be as follows + - 1) 2-byte AS number:4-byte user-defined number, for example, 1:3. An AS number is an integer ranging from + 0 to 65535, and a user-defined number is an integer ranging from 0 to 4294967295. The AS and user-defined + numbers cannot be both 0s. This means that an RD cannot be 0:0. + - 2) Integral 4-byte AS number:2-byte user-defined number, for example, 65537:3. An AS number is an integer + ranging from 65536 to 4294967295, and a user-defined number is an integer ranging from 0 to 65535. + - 3) 4-byte AS number in dotted notation:2-byte user-defined number, for example, 0.0:3 or 0.1:0. A 4-byte + AS number in dotted notation is in the format of x.y, where x and y are integers ranging from 0 to 65535. + - 4) A user-defined number is an integer ranging from 0 to 65535. The AS and user-defined numbers cannot be + both 0s. This means that an RD cannot be 0.0:0. + - 5) 32-bit IP address:2-byte user-defined number. For example, 192.168.122.15:1. An IP address ranges from + 0.0.0.0 to 255.255.255.255, and a user-defined number is an integer ranging from 0 to 65535. + - 6) 'auto' specifies the RD that is automatically generated. + vpn_target_both: + description: + - Add VPN targets to both the import and export VPN target lists of a BD EVPN instance. + The format is the same as route_distinguisher. + vpn_target_import: + description: + - Add VPN targets to the import VPN target list of a BD EVPN instance. + The format is the same as route_distinguisher. + required: true + vpn_target_export: + description: + - Add VPN targets to the export VPN target list of a BD EVPN instance. + The format is the same as route_distinguisher. + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: 'present' +''' + +EXAMPLES = ''' +- name: EVPN BD VNI test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure an EVPN instance for a VXLAN in BD view" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + evpn: enable + provider: "{{ cli }}" + + - name: "Configure a route distinguisher (RD) for a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + route_distinguisher: '22:22' + provider: "{{ cli }}" + + - name: "Configure VPN targets to both the import and export VPN target lists of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_both: 22:100,22:101 + provider: "{{ cli }}" + + - name: "Configure VPN targets to the import VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_import: 22:22,22:23 + provider: "{{ cli }}" + + - name: "Configure VPN targets to the export VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_export: 22:38,22:39 + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to both the import and export VPN target lists of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_both: '22:100' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to the import VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_import: '22:22' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure VPN targets to the export VPN target list of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + vpn_target_export: '22:38' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure a route distinguisher (RD) of a BD EVPN instance" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + route_distinguisher: '22:22' + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure an EVPN instance for a VXLAN in BD view" + community.network.ce_evpn_bd_vni: + bridge_domain_id: 20 + evpn: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "enable", + "route_distinguisher": "22:22", + "state": "present", + "vpn_target_both": [ + "22:100", + "22:101" + ], + "vpn_target_export": [ + "22:38", + "22:39" + ], + "vpn_target_import": [ + "22:22", + "22:23" + ] + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "disable", + "route_distinguisher": null, + "vpn_target_both": [], + "vpn_target_export": [], + "vpn_target_import": [] + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "bridge_domain_id": "2", + "evpn": "enable", + "route_distinguisher": "22:22", + "vpn_target_both": [ + "22:100", + "22:101" + ], + "vpn_target_export": [ + "22:38", + "22:39" + ], + "vpn_target_import": [ + "22:22", + "22:23" + ] + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "bridge-domain 2", + " evpn", + " route-distinguisher 22:22", + " vpn-target 22:38 export-extcommunity", + " vpn-target 22:39 export-extcommunity", + " vpn-target 22:100 export-extcommunity", + " vpn-target 22:101 export-extcommunity", + " vpn-target 22:22 import-extcommunity", + " vpn-target 22:23 import-extcommunity", + " vpn-target 22:100 import-extcommunity", + " vpn-target 22:101 import-extcommunity" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +import copy +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VNI_BD = """ + + + + + + + + + + +""" + +CE_NC_GET_EVPN_CONFIG = """ + + + + + %s + %s + + + + + + + + + + + + + + + + + +""" + +CE_NC_DELETE_EVPN_CONFIG = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_EVPN_CONFIG_HEAD = """ + + + + + %s + %s +""" + +CE_NC_MERGE_EVPN_CONFIG_HEAD = """ + + + + + %s + %s +""" + +CE_NC_MERGE_EVPN_AUTORTS_HEAD = """ + +""" + +CE_NC_MERGE_EVPN_AUTORTS_TAIL = """ + +""" + +CE_NC_DELETE_EVPN_AUTORTS_CONTEXT = """ + + %s + +""" + +CE_NC_MERGE_EVPN_AUTORTS_CONTEXT = """ + + %s + +""" + +CE_NC_MERGE_EVPN_RTS_HEAD = """ + +""" + +CE_NC_MERGE_EVPN_RTS_TAIL = """ + +""" + +CE_NC_DELETE_EVPN_RTS_CONTEXT = """ + + %s + %s + +""" + +CE_NC_MERGE_EVPN_RTS_CONTEXT = """ + + %s + %s + +""" + +CE_NC_MERGE_EVPN_CONFIG_TAIL = """ + + + + +""" + + +def is_valid_value(vrf_targe_value): + """check whether VPN target value is valid""" + + each_num = None + if len(vrf_targe_value) > 21 or len(vrf_targe_value) < 3: + return False + if vrf_targe_value.find(':') == -1: + return False + elif vrf_targe_value == '0:0': + return False + elif vrf_targe_value == '0.0:0': + return False + else: + value_list = vrf_targe_value.split(':') + if value_list[0].find('.') != -1: + if not value_list[1].isdigit(): + return False + if int(value_list[1]) > 65535: + return False + value = value_list[0].split('.') + if len(value) == 4: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + elif len(value) == 2: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + else: + return False + elif not value_list[0].isdigit(): + return False + elif not value_list[1].isdigit(): + return False + elif int(value_list[0]) < 65536 and int(value_list[1]) < 4294967296: + return True + elif int(value_list[0]) > 65535 and int(value_list[0]) < 4294967296: + return bool(int(value_list[1]) < 65536) + else: + return False + + +class EvpnBd(object): + """Manage evpn instance in BD view""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # EVPN instance info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.evpn = self.module.params['evpn'] + self.route_distinguisher = self.module.params['route_distinguisher'] + self.vpn_target_both = self.module.params['vpn_target_both'] or list() + self.vpn_target_import = self.module.params[ + 'vpn_target_import'] or list() + self.vpn_target_export = self.module.params[ + 'vpn_target_export'] or list() + self.state = self.module.params['state'] + self.__string_to_lowercase__() + + self.commands = list() + self.evpn_info = dict() + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def __check_response__(self, xml_str, xml_name): + """Check if response message is already succeed""" + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def __string_to_lowercase__(self): + """Convert string to lowercase""" + + if self.route_distinguisher: + self.route_distinguisher = self.route_distinguisher.lower() + + if self.vpn_target_export: + for index, ele in enumerate(self.vpn_target_export): + self.vpn_target_export[index] = ele.lower() + + if self.vpn_target_import: + for index, ele in enumerate(self.vpn_target_import): + self.vpn_target_import[index] = ele.lower() + + if self.vpn_target_both: + for index, ele in enumerate(self.vpn_target_both): + self.vpn_target_both[index] = ele.lower() + + def get_all_evpn_rts(self, evpn_rts): + """Get all EVPN RTS""" + + rts = evpn_rts.findall("evpnRT") + if not rts: + return + + for ele in rts: + vrf_rttype = ele.find('vrfRTType') + vrf_rtvalue = ele.find('vrfRTValue') + + if vrf_rttype.text == 'export_extcommunity': + self.evpn_info['vpn_target_export'].append(vrf_rtvalue.text) + elif vrf_rttype.text == 'import_extcommunity': + self.evpn_info['vpn_target_import'].append(vrf_rtvalue.text) + + def get_all_evpn_autorts(self, evpn_autorts): + """"Get all EVPN AUTORTS""" + + autorts = evpn_autorts.findall("evpnAutoRT") + if not autorts: + return + + for autort in autorts: + vrf_rttype = autort.find('vrfRTType') + + if vrf_rttype.text == 'export_extcommunity': + self.evpn_info['vpn_target_export'].append('auto') + elif vrf_rttype.text == 'import_extcommunity': + self.evpn_info['vpn_target_import'].append('auto') + + def process_rts_info(self): + """Process RTS information""" + + if not self.evpn_info['vpn_target_export'] or\ + not self.evpn_info['vpn_target_import']: + return + + vpn_target_export = copy.deepcopy(self.evpn_info['vpn_target_export']) + for ele in vpn_target_export: + if ele in self.evpn_info['vpn_target_import']: + self.evpn_info['vpn_target_both'].append(ele) + self.evpn_info['vpn_target_export'].remove(ele) + self.evpn_info['vpn_target_import'].remove(ele) + + def get_evpn_instance_info(self): + """Get current EVPN instance information""" + + if not self.bridge_domain_id: + self.module.fail_json(msg='Error: The value of bridge_domain_id cannot be empty.') + + self.evpn_info['route_distinguisher'] = None + self.evpn_info['vpn_target_import'] = list() + self.evpn_info['vpn_target_export'] = list() + self.evpn_info['vpn_target_both'] = list() + self.evpn_info['evpn_inst'] = 'enable' + + xml_str = CE_NC_GET_EVPN_CONFIG % ( + self.bridge_domain_id, self.bridge_domain_id) + xml_str = get_nc_config(self.module, xml_str) + if "" in xml_str: + self.evpn_info['evpn_inst'] = 'disable' + return + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + evpn_inst = root.find("evpn/evpnInstances/evpnInstance") + if evpn_inst: + for eles in evpn_inst: + if eles.tag in ["evpnAutoRD", "evpnRD", "evpnRTs", "evpnAutoRTs"]: + if eles.tag == 'evpnAutoRD' and eles.text == 'true': + self.evpn_info['route_distinguisher'] = 'auto' + elif eles.tag == 'evpnRD' and self.evpn_info['route_distinguisher'] != 'auto': + self.evpn_info['route_distinguisher'] = eles.text + elif eles.tag == 'evpnRTs': + self.get_all_evpn_rts(eles) + elif eles.tag == 'evpnAutoRTs': + self.get_all_evpn_autorts(eles) + self.process_rts_info() + + def get_existing(self): + """Get existing config""" + + self.existing = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn_info['evpn_inst'], + route_distinguisher=self.evpn_info[ + 'route_distinguisher'], + vpn_target_both=self.evpn_info['vpn_target_both'], + vpn_target_import=self.evpn_info[ + 'vpn_target_import'], + vpn_target_export=self.evpn_info['vpn_target_export']) + + def get_proposed(self): + """Get proposed config""" + + self.proposed = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn, + route_distinguisher=self.route_distinguisher, + vpn_target_both=self.vpn_target_both, + vpn_target_import=self.vpn_target_import, + vpn_target_export=self.vpn_target_export, + state=self.state) + + def get_end_state(self): + """Get end config""" + + self.get_evpn_instance_info() + self.end_state = dict(bridge_domain_id=self.bridge_domain_id, + evpn=self.evpn_info['evpn_inst'], + route_distinguisher=self.evpn_info[ + 'route_distinguisher'], + vpn_target_both=self.evpn_info[ + 'vpn_target_both'], + vpn_target_import=self.evpn_info[ + 'vpn_target_import'], + vpn_target_export=self.evpn_info['vpn_target_export']) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_vpn_target_exist(self, vpn_target_type): + """Judge whether proposed vpn target has existed""" + + vpn_target = list() + if vpn_target_type == 'vpn_target_import': + vpn_target.extend(self.existing['vpn_target_both']) + vpn_target.extend(self.existing['vpn_target_import']) + return set(self.proposed['vpn_target_import']).issubset(vpn_target) + elif vpn_target_type == 'vpn_target_export': + vpn_target.extend(self.existing['vpn_target_both']) + vpn_target.extend(self.existing['vpn_target_export']) + return set(self.proposed['vpn_target_export']).issubset(vpn_target) + + return False + + def judge_if_config_exist(self): + """Judge whether configuration has existed""" + + if self.state == 'absent': + if self.route_distinguisher or self.vpn_target_import or self.vpn_target_export or self.vpn_target_both: + return False + else: + return True + + if self.evpn_info['evpn_inst'] != self.evpn: + return False + + if self.evpn == 'disable' and self.evpn_info['evpn_inst'] == 'disable': + return True + + if self.proposed['bridge_domain_id'] != self.existing['bridge_domain_id']: + return False + + if self.proposed['route_distinguisher']: + if self.proposed['route_distinguisher'] != self.existing['route_distinguisher']: + return False + + if self.proposed['vpn_target_both']: + if not self.existing['vpn_target_both']: + return False + if not set(self.proposed['vpn_target_both']).issubset(self.existing['vpn_target_both']): + return False + + if self.proposed['vpn_target_import']: + if not self.judge_if_vpn_target_exist('vpn_target_import'): + return False + + if self.proposed['vpn_target_export']: + if not self.judge_if_vpn_target_exist('vpn_target_export'): + return False + + return True + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def unconfig_evpn_instance(self): + """Unconfigure EVPN instance""" + + self.updates_cmd.append("bridge-domain %s" % self.bridge_domain_id) + xml_str = CE_NC_MERGE_EVPN_CONFIG_HEAD % ( + self.bridge_domain_id, self.bridge_domain_id) + self.updates_cmd.append(" evpn") + + # unconfigure RD + if self.route_distinguisher: + if self.route_distinguisher.lower() == 'auto': + xml_str += 'false' + self.updates_cmd.append(" undo route-distinguisher auto") + else: + xml_str += '' + self.updates_cmd.append( + " undo route-distinguisher %s" % self.route_distinguisher) + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "UNDO_EVPN_BD_RD") + self.changed = True + return + + # process VPN target list + vpn_target_export = copy.deepcopy(self.vpn_target_export) + vpn_target_import = copy.deepcopy(self.vpn_target_import) + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in vpn_target_export: + vpn_target_export.append(ele) + if ele not in vpn_target_import: + vpn_target_import.append(ele) + + # unconfig EVPN auto RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() == 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_AUTORTS_CONTEXT % ( + 'export_extcommunity') + self.updates_cmd.append( + " undo vpn-target auto export-extcommunity") + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() == 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_AUTORTS_CONTEXT % ( + 'import_extcommunity') + self.updates_cmd.append( + " undo vpn-target auto import-extcommunity") + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_TAIL + + # unconfig EVPN RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() != 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_RTS_CONTEXT % ( + 'export_extcommunity', ele) + self.updates_cmd.append( + " undo vpn-target %s export-extcommunity" % ele) + + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() != 'auto': + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_DELETE_EVPN_RTS_CONTEXT % ( + 'import_extcommunity', ele) + self.updates_cmd.append( + " undo vpn-target %s import-extcommunity" % ele) + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_TAIL + + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_VPN_TARGET_CONFIG") + self.changed = True + + def config_evpn_instance(self): + """Configure EVPN instance""" + + self.updates_cmd.append("bridge-domain %s" % self.bridge_domain_id) + + if self.evpn == 'disable': + xml_str = CE_NC_DELETE_EVPN_CONFIG % ( + self.bridge_domain_id, self.bridge_domain_id) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_CONFIG") + self.updates_cmd.append(" undo evpn") + self.changed = True + return + + xml_str = CE_NC_MERGE_EVPN_CONFIG_HEAD % ( + self.bridge_domain_id, self.bridge_domain_id) + self.updates_cmd.append(" evpn") + + # configure RD + if self.route_distinguisher: + if not self.existing['route_distinguisher']: + if self.route_distinguisher.lower() == 'auto': + xml_str += 'true' + self.updates_cmd.append(" route-distinguisher auto") + else: + xml_str += '%s' % self.route_distinguisher + self.updates_cmd.append( + " route-distinguisher %s" % self.route_distinguisher) + + # process VPN target list + vpn_target_export = copy.deepcopy(self.vpn_target_export) + vpn_target_import = copy.deepcopy(self.vpn_target_import) + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in vpn_target_export: + vpn_target_export.append(ele) + if ele not in vpn_target_import: + vpn_target_import.append(ele) + + # config EVPN auto RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() == 'auto' and \ + (not self.is_vpn_target_exist('export_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_AUTORTS_CONTEXT % ( + 'export_extcommunity') + self.updates_cmd.append( + " vpn-target auto export-extcommunity") + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() == 'auto' and \ + (not self.is_vpn_target_exist('import_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_AUTORTS_CONTEXT % ( + 'import_extcommunity') + self.updates_cmd.append( + " vpn-target auto import-extcommunity") + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_AUTORTS_TAIL + + # config EVPN RTS + head_flag = False + if vpn_target_export: + for ele in vpn_target_export: + if ele.lower() != 'auto' and \ + (not self.is_vpn_target_exist('export_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_RTS_CONTEXT % ( + 'export_extcommunity', ele) + self.updates_cmd.append( + " vpn-target %s export-extcommunity" % ele) + + if vpn_target_import: + for ele in vpn_target_import: + if ele.lower() != 'auto' and \ + (not self.is_vpn_target_exist('import_extcommunity', ele.lower())): + if not head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_HEAD + head_flag = True + xml_str += CE_NC_MERGE_EVPN_RTS_CONTEXT % ( + 'import_extcommunity', ele) + self.updates_cmd.append( + " vpn-target %s import-extcommunity" % ele) + + if head_flag: + xml_str += CE_NC_MERGE_EVPN_RTS_TAIL + + xml_str += CE_NC_MERGE_EVPN_CONFIG_TAIL + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_EVPN_BD_CONFIG") + self.changed = True + + def is_vpn_target_exist(self, target_type, value): + """Judge whether VPN target has existed""" + + if target_type == 'export_extcommunity': + if (value not in self.existing['vpn_target_export']) and\ + (value not in self.existing['vpn_target_both']): + return False + return True + + if target_type == 'import_extcommunity': + if (value not in self.existing['vpn_target_import']) and\ + (value not in self.existing['vpn_target_both']): + return False + return True + + return False + + def config_evnp_bd(self): + """Configure EVPN in BD view""" + + if not self.conf_exist: + if self.state == 'present': + self.config_evpn_instance() + else: + self.unconfig_evpn_instance() + + def process_input_params(self): + """Process input parameters""" + + if self.state == 'absent': + self.evpn = None + else: + if self.evpn == 'disable': + return + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele in self.vpn_target_export: + self.vpn_target_export.remove(ele) + if ele in self.vpn_target_import: + self.vpn_target_import.remove(ele) + + if self.vpn_target_export and self.vpn_target_import: + vpn_target_export = copy.deepcopy(self.vpn_target_export) + for ele in vpn_target_export: + if ele in self.vpn_target_import: + self.vpn_target_both.append(ele) + self.vpn_target_import.remove(ele) + self.vpn_target_export.remove(ele) + + def check_vpn_target_para(self): + """Check whether VPN target value is valid""" + + if self.route_distinguisher: + if self.route_distinguisher.lower() != 'auto' and\ + not is_valid_value(self.route_distinguisher): + self.module.fail_json( + msg='Error: Route distinguisher has invalid value %s.' % self.route_distinguisher) + + if self.vpn_target_export: + for ele in self.vpn_target_export: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + if self.vpn_target_import: + for ele in self.vpn_target_import: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele.lower() != 'auto' and not is_valid_value(ele): + self.module.fail_json( + msg='Error: VPN target extended community attribute has invalid value %s.' % ele) + + def check_undo_params_if_exist(self): + """Check whether all undo parameters is existed""" + + if self.vpn_target_import: + for ele in self.vpn_target_import: + if ele not in self.evpn_info['vpn_target_import'] and ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target import attribute value %s does not exist.' % ele) + + if self.vpn_target_export: + for ele in self.vpn_target_export: + if ele not in self.evpn_info['vpn_target_export'] and ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target export attribute value %s does not exist.' % ele) + + if self.vpn_target_both: + for ele in self.vpn_target_both: + if ele not in self.evpn_info['vpn_target_both']: + self.module.fail_json( + msg='Error: VPN target export and import attribute value %s does not exist.' % ele) + + def check_params(self): + """Check all input params""" + + # bridge_domain_id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of bridge domain id is invalid.') + if int(self.bridge_domain_id) > 16777215 or int(self.bridge_domain_id) < 1: + self.module.fail_json( + msg='Error: The bridge domain id must be an integer between 1 and 16777215.') + + if self.state == 'absent': + self.check_undo_params_if_exist() + + # check bd whether binding the vxlan vni + self.check_vni_bd() + self.check_vpn_target_para() + + if self.state == 'absent': + if self.route_distinguisher: + if not self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Route distinguisher has not been configured.') + else: + if self.route_distinguisher != self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Current route distinguisher value is %s.' % + self.evpn_info['route_distinguisher']) + + if self.state == 'present': + if self.route_distinguisher: + if self.evpn_info['route_distinguisher'] and\ + self.route_distinguisher != self.evpn_info['route_distinguisher']: + self.module.fail_json( + msg='Error: Route distinguisher has already been configured.') + + def check_vni_bd(self): + """Check whether vxlan vni is configured in BD view""" + + xml_str = CE_NC_GET_VNI_BD + xml_str = get_nc_config(self.module, xml_str) + if "" in xml_str or not re.findall(r'\S+\s+%s' % self.bridge_domain_id, xml_str): + self.module.fail_json( + msg='Error: The vxlan vni is not configured or the bridge domain id is invalid.') + + def work(self): + """Execute task""" + + self.get_evpn_instance_info() + self.process_input_params() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_evnp_bd() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + bridge_domain_id=dict(required=True, type='str'), + evpn=dict(required=False, type='str', + default='enable', choices=['enable', 'disable']), + route_distinguisher=dict(required=False, type='str'), + vpn_target_both=dict(required=False, type='list'), + vpn_target_import=dict(required=False, type='list'), + vpn_target_export=dict(required=False, type='list'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + evpn_bd = EvpnBd(argument_spec) + evpn_bd.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp.py new file mode 100644 index 00000000..5141a415 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp.py @@ -0,0 +1,727 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bgp +short_description: Manages BGP EVPN configuration on HUAWEI CloudEngine switches. +description: + - This module offers the ability to configure a BGP EVPN peer relationship on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + bgp_instance: + description: + - Name of a BGP instance. The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + required: True + as_number: + description: + - Specifies integral AS number. The value is an integer ranging from 1 to 4294967295. + peer_address: + description: + - Specifies the IPv4 address of a BGP EVPN peer. The value is in dotted decimal notation. + peer_group_name: + description: + - Specify the name of a peer group that BGP peers need to join. + The value is a string of 1 to 47 case-sensitive characters, spaces not supported. + peer_enable: + description: + - Enable or disable a BGP device to exchange routes with a specified peer or peer group in the address + family view. + choices: ['true','false'] + advertise_router_type: + description: + - Configures a device to advertise routes to its BGP EVPN peers. + choices: ['arp','irb'] + vpn_name: + description: + - Associates a specified VPN instance with the IPv4 address family. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + advertise_l2vpn_evpn: + description: + - Enable or disable a device to advertise IP routes imported to a VPN instance to its EVPN instance. + choices: ['enable','disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Evpn bgp module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Enable peer address. + community.network.ce_evpn_bgp: + bgp_instance: 100 + peer_address: 1.1.1.1 + as_number: 100 + peer_enable: true + provider: "{{ cli }}" + + - name: Enable peer group arp. + community.network.ce_evpn_bgp: + bgp_instance: 100 + peer_group_name: aaa + advertise_router_type: arp + provider: "{{ cli }}" + + - name: Enable advertise l2vpn evpn. + community.network.ce_evpn_bgp: + bgp_instance: 100 + vpn_name: aaa + advertise_l2vpn_evpn: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"advertise_router_type": "arp", "bgp_instance": "100", "peer_group_name": "aaa", "state": "present"} +existing: + description: k/v pairs of existing rollback + returned: always + type: dict + sample: {"bgp_instance": "100", "peer_group_advertise_type": []} + +updates: + description: command sent to the device + returned: always + type: list + sample: ["peer 1.1.1.1 enable", + "peer aaa advertise arp"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"advertise_l2vpn_evpn": "enable", "bgp_instance": "100", "vpn_name": "aaa"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def is_config_exist(cmp_cfg, test_cfg): + """check configuration is exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def is_valid_as_number(as_number): + """check as-number is valid""" + + if as_number.isdigit(): + if int(as_number) > 4294967295 or int(as_number) < 1: + return False + return True + else: + if as_number.find('.') != -1: + number_list = as_number.split('.') + if len(number_list) != 2: + return False + if number_list[1] == 0: + return False + for each_num in number_list: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + + return False + + +class EvpnBgp(object): + """ + Manages evpn bgp configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.netconf = None + self.init_module() + + # module input info + self.as_number = self.module.params['as_number'] + self.bgp_instance = self.module.params['bgp_instance'] + self.peer_address = self.module.params['peer_address'] + self.peer_group_name = self.module.params['peer_group_name'] + self.peer_enable = self.module.params['peer_enable'] + self.advertise_router_type = self.module.params[ + 'advertise_router_type'] + self.vpn_name = self.module.params['vpn_name'] + self.advertise_l2vpn_evpn = self.module.params['advertise_l2vpn_evpn'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" + self.config_list = list() + self.l2vpn_evpn_exist = False + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_evpn_overlay_config(self): + """get evpn-overlay enable configuration""" + + cmd = "display current-configuration | include ^evpn-overlay enable" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + return out + + def get_current_config(self): + """get current configuration""" + + cmd = "display current-configuration | section include bgp %s" % self.bgp_instance + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + return out + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def check_params(self): + """Check all input params""" + + # as_number check + if not self.bgp_instance: + self.module.fail_json( + msg='Error: The bgp_instance can not be none.') + if not self.peer_enable and not self.advertise_router_type and not self.advertise_l2vpn_evpn: + self.module.fail_json( + msg='Error: The peer_enable, advertise_router_type, advertise_l2vpn_evpn ' + 'can not be none at the same time.') + if self.as_number: + if not is_valid_as_number(self.as_number): + self.module.fail_json( + msg='Error: The parameter of as_number %s is invalid.' % self.as_number) + # bgp_instance check + if self.bgp_instance: + if not is_valid_as_number(self.bgp_instance): + self.module.fail_json( + msg='Error: The parameter of bgp_instance %s is invalid.' % self.bgp_instance) + + # peer_address check + if self.peer_address: + if not is_valid_address(self.peer_address): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.peer_address) + + # peer_group_name check + if self.peer_group_name: + if len(self.peer_group_name) > 47 \ + or len(self.peer_group_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: peer group name is not in the range from 1 to 47.') + + # vpn_name check + if self.vpn_name: + if len(self.vpn_name) > 31 \ + or len(self.vpn_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: peer group name is not in the range from 1 to 31.') + + def get_proposed(self): + """get proposed info""" + + if self.as_number: + self.proposed["as_number"] = self.as_number + if self.bgp_instance: + self.proposed["bgp_instance"] = self.bgp_instance + if self.peer_address: + self.proposed["peer_address"] = self.peer_address + if self.peer_group_name: + self.proposed["peer_group_name"] = self.peer_group_name + if self.peer_enable: + self.proposed["peer_enable"] = self.peer_enable + if self.advertise_router_type: + self.proposed["advertise_router_type"] = self.advertise_router_type + if self.vpn_name: + self.proposed["vpn_name"] = self.vpn_name + if self.advertise_l2vpn_evpn: + self.proposed["advertise_l2vpn_evpn"] = self.advertise_l2vpn_evpn + if not self.peer_enable or not self.advertise_l2vpn_evpn: + if self.state: + self.proposed["state"] = self.state + + def get_peers_enable(self): + """get evpn peer address enable list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?as-number\s?(\S*)", self.config_list[0]) + if not get: + return None + else: + peers = list() + for item in get: + cmd = "peer %s enable" % item[0] + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peers.append( + dict(peer_address=item[0], as_number=item[1], peer_enable='true')) + else: + peers.append(dict(peer_address=item[0], as_number=item[1], peer_enable='false')) + return peers + + def get_peers_advertise_type(self): + """get evpn peer address advertise type list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?as-number\s?(\S*)", self.config_list[0]) + if not get: + return None + else: + peers = list() + for item in get: + cmd = "peer %s advertise arp" % item[0] + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item[0] + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peers.append(dict(peer_address=item[0], as_number=item[1], advertise_type='arp')) + if exist2: + peers.append(dict(peer_address=item[0], as_number=item[1], advertise_type='irb')) + return peers + + def get_peers_group_enable(self): + """get evpn peer group name enable list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get1 = re.findall( + r"group (\S+) external", self.config_list[0]) + + get2 = re.findall( + r"group (\S+) internal", self.config_list[0]) + + if not get1 and not get2: + return None + else: + peer_groups = list() + for item in get1: + cmd = "peer %s enable" % item + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peer_groups.append( + dict(peer_group_name=item, peer_enable='true')) + else: + peer_groups.append( + dict(peer_group_name=item, peer_enable='false')) + + for item in get2: + cmd = "peer %s enable" % item + exist = is_config_exist(self.config_list[1], cmd) + if exist: + peer_groups.append( + dict(peer_group_name=item, peer_enable='true')) + else: + peer_groups.append( + dict(peer_group_name=item, peer_enable='false')) + return peer_groups + + def get_peer_groups_advertise_type(self): + """get evpn peer group name advertise type list""" + + if len(self.config_list) != 2: + return None + self.config_list = self.config.split('l2vpn-family evpn') + get1 = re.findall( + r"group (\S+) external", self.config_list[0]) + + get2 = re.findall( + r"group (\S+) internal", self.config_list[0]) + if not get1 and not get2: + return None + else: + peer_groups = list() + for item in get1: + cmd = "peer %s advertise arp" % item + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peer_groups.append( + dict(peer_group_name=item, advertise_type='arp')) + if exist2: + peer_groups.append( + dict(peer_group_name=item, advertise_type='irb')) + + for item in get2: + cmd = "peer %s advertise arp" % item + exist1 = is_config_exist(self.config_list[1], cmd) + cmd = "peer %s advertise irb" % item + exist2 = is_config_exist(self.config_list[1], cmd) + if exist1: + peer_groups.append( + dict(peer_group_name=item, advertise_type='arp')) + if exist2: + peer_groups.append( + dict(peer_group_name=item, advertise_type='irb')) + return peer_groups + + def get_existing(self): + """get existing info""" + + if not self.config: + return + if self.bgp_instance: + self.existing["bgp_instance"] = self.bgp_instance + + if self.peer_address and self.peer_enable: + if self.l2vpn_evpn_exist: + self.existing["peer_address_enable"] = self.get_peers_enable() + + if self.peer_group_name and self.peer_enable: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_group_enable"] = self.get_peers_group_enable() + + if self.peer_address and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_address_advertise_type"] = self.get_peers_advertise_type() + + if self.peer_group_name and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.existing[ + "peer_group_advertise_type"] = self.get_peer_groups_advertise_type() + + if self.advertise_l2vpn_evpn and self.vpn_name: + cmd = " ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["vpn_name"] = self.vpn_name + l2vpn_cmd = "advertise l2vpn evpn" + l2vpn_exist = is_config_exist(self.config, l2vpn_cmd) + if l2vpn_exist: + self.existing["advertise_l2vpn_evpn"] = 'enable' + else: + self.existing["advertise_l2vpn_evpn"] = 'disable' + + def get_end_state(self): + """get end state info""" + + self.config = self.get_current_config() + if not self.config: + return + + self.config_list = self.config.split('l2vpn-family evpn') + if len(self.config_list) == 2: + self.l2vpn_evpn_exist = True + + if self.bgp_instance: + self.end_state["bgp_instance"] = self.bgp_instance + + if self.peer_address and self.peer_enable: + if self.l2vpn_evpn_exist: + self.end_state["peer_address_enable"] = self.get_peers_enable() + + if self.peer_group_name and self.peer_enable: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_group_enable"] = self.get_peers_group_enable() + + if self.peer_address and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_address_advertise_type"] = self.get_peers_advertise_type() + + if self.peer_group_name and self.advertise_router_type: + if self.l2vpn_evpn_exist: + self.end_state[ + "peer_group_advertise_type"] = self.get_peer_groups_advertise_type() + + if self.advertise_l2vpn_evpn and self.vpn_name: + cmd = " ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if exist: + self.end_state["vpn_name"] = self.vpn_name + l2vpn_cmd = "advertise l2vpn evpn" + l2vpn_exist = is_config_exist(self.config, l2vpn_cmd) + if l2vpn_exist: + self.end_state["advertise_l2vpn_evpn"] = 'enable' + else: + self.end_state["advertise_l2vpn_evpn"] = 'disable' + + def config_peer(self): + """configure evpn bgp peer command""" + + if self.as_number and self.peer_address: + cmd = "peer %s as-number %s" % (self.peer_address, self.as_number) + exist = is_config_exist(self.config, cmd) + if not exist: + self.module.fail_json( + msg='Error: The peer session %s does not exist or the peer already ' + 'exists in another as-number.' % self.peer_address) + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "l2vpn-family evpn" + self.cli_add_command(cmd) + exist_l2vpn = is_config_exist(self.config, cmd) + if self.peer_enable: + cmd = "peer %s enable" % self.peer_address + if exist_l2vpn: + exist = is_config_exist(self.config_list[1], cmd) + if self.peer_enable == "true" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.peer_enable == "false" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + else: + self.cli_add_command(cmd) + self.changed = True + + if self.advertise_router_type: + cmd = "peer %s advertise %s" % ( + self.peer_address, self.advertise_router_type) + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.state == "absent" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + elif self.peer_group_name: + cmd_1 = "group %s external" % self.peer_group_name + exist_1 = is_config_exist(self.config, cmd_1) + cmd_2 = "group %s internal" % self.peer_group_name + exist_2 = is_config_exist(self.config, cmd_2) + exist = False + if exist_1: + exist = True + if exist_2: + exist = True + if not exist: + self.module.fail_json( + msg='Error: The peer-group %s does not exist.' % self.peer_group_name) + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "l2vpn-family evpn" + self.cli_add_command(cmd) + exist_l2vpn = is_config_exist(self.config, cmd) + if self.peer_enable: + cmd = "peer %s enable" % self.peer_group_name + if exist_l2vpn: + exist = is_config_exist(self.config_list[1], cmd) + if self.peer_enable == "true" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.peer_enable == "false" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + else: + self.cli_add_command(cmd) + self.changed = True + + if self.advertise_router_type: + cmd = "peer %s advertise %s" % ( + self.peer_group_name, self.advertise_router_type) + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(cmd) + self.changed = True + elif self.state == "absent" and exist: + self.cli_add_command(cmd, undo=True) + self.changed = True + + def config_advertise_l2vpn_evpn(self): + """configure advertise l2vpn evpn""" + + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + exist = is_config_exist(self.config, cmd) + if not exist: + self.module.fail_json( + msg='Error: The VPN instance name %s does not exist.' % self.vpn_name) + config_vpn_list = self.config.split(cmd) + cmd = "ipv4-family vpn-instance" + exist_vpn = is_config_exist(config_vpn_list[1], cmd) + cmd_l2vpn = "advertise l2vpn evpn" + if exist_vpn: + config_vpn = config_vpn_list[1].split('ipv4-family vpn-instance') + exist_l2vpn = is_config_exist(config_vpn[0], cmd_l2vpn) + else: + exist_l2vpn = is_config_exist(config_vpn_list[1], cmd_l2vpn) + cmd = "advertise l2vpn evpn" + if self.advertise_l2vpn_evpn == "enable" and not exist_l2vpn: + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + self.cli_add_command(cmd) + cmd = "advertise l2vpn evpn" + self.cli_add_command(cmd) + self.changed = True + elif self.advertise_l2vpn_evpn == "disable" and exist_l2vpn: + cmd = "bgp %s" % self.bgp_instance + self.cli_add_command(cmd) + cmd = "ipv4-family vpn-instance %s" % self.vpn_name + self.cli_add_command(cmd) + cmd = "advertise l2vpn evpn" + self.cli_add_command(cmd, undo=True) + self.changed = True + + def work(self): + """worker""" + + self.check_params() + evpn_config = self.get_evpn_overlay_config() + if not evpn_config: + self.module.fail_json( + msg="Error: evpn-overlay enable is not configured.") + self.config = self.get_current_config() + if not self.config: + self.module.fail_json( + msg="Error: Bgp instance %s does not exist." % self.bgp_instance) + + self.config_list = self.config.split('l2vpn-family evpn') + if len(self.config_list) == 2: + self.l2vpn_evpn_exist = True + self.get_existing() + self.get_proposed() + + if self.peer_enable or self.advertise_router_type: + self.config_peer() + + if self.advertise_l2vpn_evpn: + self.config_advertise_l2vpn_evpn() + if self.commands: + self.cli_load_config(self.commands) + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bgp_instance=dict(required=True, type='str'), + as_number=dict(required=False, type='str'), + peer_address=dict(required=False, type='str'), + peer_group_name=dict(required=False, type='str'), + peer_enable=dict(required=False, type='str', choices=[ + 'true', 'false']), + advertise_router_type=dict(required=False, type='str', choices=[ + 'arp', 'irb']), + + vpn_name=dict(required=False, type='str'), + advertise_l2vpn_evpn=dict(required=False, type='str', choices=[ + 'enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = EvpnBgp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp_rr.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp_rr.py new file mode 100644 index 00000000..a8b5a5b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_bgp_rr.py @@ -0,0 +1,527 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_bgp_rr +short_description: Manages RR for the VXLAN Network on HUAWEI CloudEngine switches. +description: + - Configure an RR in BGP-EVPN address family view on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that BGP view is existed. + - The peer, peer_type, and reflect_client arguments must all exist or not exist. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + as_number: + description: + - Specifies the number of the AS, in integer format. + The value is an integer that ranges from 1 to 4294967295. + required: true + bgp_instance: + description: + - Specifies the name of a BGP instance. + The value of instance-name can be an integer 1 or a string of 1 to 31. + bgp_evpn_enable: + description: + - Enable or disable the BGP-EVPN address family. + choices: ['enable','disable'] + default: 'enable' + peer_type: + description: + - Specify the peer type. + choices: ['group_name','ipv4_address'] + peer: + description: + - Specifies the IPv4 address or the group name of a peer. + reflect_client: + description: + - Configure the local device as the route reflector and the peer or peer group as the client of the route reflector. + choices: ['enable','disable'] + policy_vpn_target: + description: + - Enable or disable the VPN-Target filtering. + choices: ['enable','disable'] +''' + +EXAMPLES = ''' +- name: BGP RR test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure BGP-EVPN address family view and ensure that BGP view has existed." + community.network.ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + provider: "{{ cli }}" + + - name: "Configure reflect client and ensure peer has existed." + community.network.ce_evpn_bgp_rr: + as_number: 20 + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + provider: "{{ cli }}" + + - name: "Configure the VPN-Target filtering." + community.network.ce_evpn_bgp_rr: + as_number: 20 + policy_vpn_target: enable + provider: "{{ cli }}" + + - name: "Configure an RR in BGP-EVPN address family view." + community.network.ce_evpn_bgp_rr: + as_number: 20 + bgp_evpn_enable: enable + peer_type: ipv4_address + peer: 192.8.3.3 + reflect_client: enable + policy_vpn_target: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "disable", + "bgp_instance": null, + "peer": null, + "peer_type": null, + "policy_vpn_target": "disable", + "reflect_client": "disable" + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "as_number": "20", + "bgp_evpn_enable": "enable", + "bgp_instance": null, + "peer": "192.8.3.3", + "peer_type": "ipv4_address", + "policy_vpn_target": "disable", + "reflect_client": "enable" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "bgp 20", + " l2vpn-family evpn", + " peer 192.8.3.3 enable", + " peer 192.8.3.3 reflect-client", + " undo policy vpn-target" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +class EvpnBgpRr(object): + """Manage RR in BGP-EVPN address family view""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # RR configuration parameters + self.as_number = self.module.params['as_number'] + self.bgp_instance = self.module.params['bgp_instance'] + self.peer_type = self.module.params['peer_type'] + self.peer = self.module.params['peer'] + self.bgp_evpn_enable = self.module.params['bgp_evpn_enable'] + self.reflect_client = self.module.params['reflect_client'] + self.policy_vpn_target = self.module.params['policy_vpn_target'] + + self.commands = list() + self.config = None + self.bgp_evpn_config = "" + self.cur_config = dict() + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """Load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def is_bgp_view_exist(self): + """Judge whether BGP view has existed""" + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + + return is_config_exist(self.config, view_cmd) + + def is_l2vpn_family_evpn_exist(self): + """Judge whether BGP-EVPN address family view has existed""" + + view_cmd = "l2vpn-family evpn" + return is_config_exist(self.config, view_cmd) + + def is_reflect_client_exist(self): + """Judge whether reflect client is configured""" + + view_cmd = "peer %s reflect-client" % self.peer + return is_config_exist(self.bgp_evpn_config, view_cmd) + + def is_policy_vpn_target_exist(self): + """Judge whether the VPN-Target filtering is enabled""" + + view_cmd = "undo policy vpn-target" + if is_config_exist(self.bgp_evpn_config, view_cmd): + return False + else: + return True + + def get_config_in_bgp_view(self): + """Get configuration in BGP view""" + + cmd = "display current-configuration | section include" + if self.as_number: + if self.bgp_instance: + cmd += " bgp %s instance %s" % (self.as_number, + self.bgp_instance) + else: + cmd += " bgp %s" % self.as_number + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = out.strip() if out else "" + if cmd == config: + return '' + + return config + + def get_config_in_bgp_evpn_view(self): + """Get configuration in BGP_EVPN view""" + + self.bgp_evpn_config = "" + if not self.config: + return "" + + index = self.config.find("l2vpn-family evpn") + if index == -1: + return "" + + return self.config[index:] + + def get_current_config(self): + """Get current configuration""" + + if not self.as_number: + self.module.fail_json(msg='Error: The value of as-number cannot be empty.') + + self.cur_config['bgp_exist'] = False + self.cur_config['bgp_evpn_enable'] = 'disable' + self.cur_config['reflect_client'] = 'disable' + self.cur_config['policy_vpn_target'] = 'disable' + self.cur_config['peer_type'] = None + self.cur_config['peer'] = None + + self.config = self.get_config_in_bgp_view() + + if not self.is_bgp_view_exist(): + return + self.cur_config['bgp_exist'] = True + + if not self.is_l2vpn_family_evpn_exist(): + return + self.cur_config['bgp_evpn_enable'] = 'enable' + + self.bgp_evpn_config = self.get_config_in_bgp_evpn_view() + if self.is_reflect_client_exist(): + self.cur_config['reflect_client'] = 'enable' + self.cur_config['peer_type'] = self.peer_type + self.cur_config['peer'] = self.peer + + if self.is_policy_vpn_target_exist(): + self.cur_config['policy_vpn_target'] = 'enable' + + def get_existing(self): + """Get existing config""" + + self.existing = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config[ + 'policy_vpn_target']) + + def get_proposed(self): + """Get proposed config""" + + self.proposed = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.peer_type, + peer=self.peer, + bgp_evpn_enable=self.bgp_evpn_enable, + reflect_client=self.reflect_client, + policy_vpn_target=self.policy_vpn_target) + + def get_end_state(self): + """Get end config""" + + self.get_current_config() + self.end_state = dict(as_number=self.as_number, + bgp_instance=self.bgp_instance, + peer_type=self.cur_config['peer_type'], + peer=self.cur_config['peer'], + bgp_evpn_enable=self.cur_config[ + 'bgp_evpn_enable'], + reflect_client=self.cur_config['reflect_client'], + policy_vpn_target=self.cur_config['policy_vpn_target']) + if self.end_state == self.existing: + self.changed = False + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """Judge whether configuration has existed""" + + if self.bgp_evpn_enable and self.bgp_evpn_enable != self.cur_config['bgp_evpn_enable']: + return False + + if self.bgp_evpn_enable == 'disable' and self.cur_config['bgp_evpn_enable'] == 'disable': + return True + + if self.reflect_client and self.reflect_client == 'enable': + if self.peer_type and self.peer_type != self.cur_config['peer_type']: + return False + if self.peer and self.peer != self.cur_config['peer']: + return False + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + return False + + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + return False + + return True + + def cli_add_command(self, command, undo=False): + """Add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_rr(self): + """Configure RR""" + + if self.conf_exist: + return + + if self.bgp_instance: + view_cmd = "bgp %s instance %s" % ( + self.as_number, self.bgp_instance) + else: + view_cmd = "bgp %s" % self.as_number + self.cli_add_command(view_cmd) + + if self.bgp_evpn_enable == 'disable': + self.cli_add_command("undo l2vpn-family evpn") + else: + self.cli_add_command("l2vpn-family evpn") + if self.reflect_client and self.reflect_client != self.cur_config['reflect_client']: + if self.reflect_client == 'enable': + self.cli_add_command("peer %s enable" % self.peer) + self.cli_add_command( + "peer %s reflect-client" % self.peer) + else: + self.cli_add_command( + "undo peer %s reflect-client" % self.peer) + self.cli_add_command("undo peer %s enable" % self.peer) + if self.cur_config['bgp_evpn_enable'] == 'enable': + if self.policy_vpn_target and self.policy_vpn_target != self.cur_config['policy_vpn_target']: + if self.policy_vpn_target == 'enable': + self.cli_add_command("policy vpn-target") + else: + self.cli_add_command("undo policy vpn-target") + else: + if self.policy_vpn_target and self.policy_vpn_target == 'disable': + self.cli_add_command("undo policy vpn-target") + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def check_is_ipv4_addr(self): + """Check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + + return bool(re.match(ipv4_regex, self.peer)) + + def check_params(self): + """Check all input params""" + + if self.cur_config['bgp_exist'] == 'false': + self.module.fail_json(msg="Error: BGP view does not exist.") + + if self.bgp_instance: + if len(self.bgp_instance) < 1 or len(self.bgp_instance) > 31: + self.module.fail_json( + msg="Error: The length of BGP instance-name must be between 1 or a string of 1 to and 31.") + + if self.as_number: + if len(self.as_number) > 11 or len(self.as_number) == 0: + self.module.fail_json( + msg='Error: The len of as_number %s is out of [1 - 11].' % self.as_number) + + tmp_dict1 = dict(peer_type=self.peer_type, + peer=self.peer, + reflect_client=self.reflect_client) + tmp_dict2 = dict((k, v) + for k, v in tmp_dict1.items() if v is not None) + if len(tmp_dict2) != 0 and len(tmp_dict2) != 3: + self.module.fail_json( + msg='Error: The peer, peer_type, and reflect_client arguments must all exist or not exist.') + + if self.peer_type: + if self.peer_type == 'ipv4_address' and not self.check_is_ipv4_addr(): + self.module.fail_json(msg='Error: Illegal IPv4 address.') + elif self.peer_type == 'group_name' and self.check_is_ipv4_addr(): + self.module.fail_json( + msg='Error: Ip address cannot be configured as group-name.') + + def work(self): + """Execute task""" + + self.get_current_config() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_rr() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + as_number=dict(required=True, type='str'), + bgp_instance=dict(required=False, type='str'), + bgp_evpn_enable=dict(required=False, type='str', + default='enable', choices=['enable', 'disable']), + peer_type=dict(required=False, type='str', choices=[ + 'group_name', 'ipv4_address']), + peer=dict(required=False, type='str'), + reflect_client=dict(required=False, type='str', + choices=['enable', 'disable']), + policy_vpn_target=dict(required=False, choices=['enable', 'disable']), + ) + argument_spec.update(ce_argument_spec) + evpn_bgp_rr = EvpnBgpRr(argument_spec) + evpn_bgp_rr.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_global.py new file mode 100644 index 00000000..c60adb13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_evpn_global.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_evpn_global +short_description: Manages global configuration of EVPN on HUAWEI CloudEngine switches. +description: + - Manages global configuration of EVPN on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Before configuring evpn_overlay_enable=disable, delete other EVPN configurations. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + evpn_overlay_enable: + description: + - Configure EVPN as the VXLAN control plane. + required: true + choices: ['enable','disable'] +''' + +EXAMPLES = ''' +- name: Evpn global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure EVPN as the VXLAN control plan + community.network.ce_evpn_global: + evpn_overlay_enable: enable + provider: "{{ cli }}" + + - name: Undo EVPN as the VXLAN control plan + community.network.ce_evpn_global: + evpn_overlay_enable: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "evpn_overlay_enable": "enable" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "evpn_overlay_enable": "disable" + } +end_state: + description: k/v pairs of end attributes on the interface + returned: always + type: dict + sample: { + "evpn_overlay_enable": "enable" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "evpn-overlay enable", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class EvpnGlobal(object): + """Manage global configuration of EVPN""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # EVPN global configuration parameters + self.overlay_enable = self.module.params['evpn_overlay_enable'] + + self.commands = list() + self.global_info = dict() + self.conf_exist = False + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def get_evpn_global_info(self): + """ get current EVPN global configuration""" + + self.global_info['evpnOverLay'] = 'disable' + cmd = "display current-configuration | include ^evpn-overlay enable" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + if out: + self.global_info['evpnOverLay'] = 'enable' + + def get_existing(self): + """get existing config""" + self.existing = dict( + evpn_overlay_enable=self.global_info['evpnOverLay']) + + def get_proposed(self): + """get proposed config""" + self.proposed = dict(evpn_overlay_enable=self.overlay_enable) + + def get_end_state(self): + """get end config""" + self.get_evpn_global_info() + self.end_state = dict( + evpn_overlay_enable=self.global_info['evpnOverLay']) + + def show_result(self): + """ show result""" + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """ judge whether configuration has existed""" + if self.overlay_enable == self.global_info['evpnOverLay']: + return True + + return False + + def config_evnp_global(self): + """ set global EVPN configuration""" + if not self.conf_exist: + if self.overlay_enable == 'enable': + self.cli_add_command('evpn-overlay enable') + else: + self.cli_add_command('evpn-overlay enable', True) + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def work(self): + """execute task""" + self.get_evpn_global_info() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_evnp_global() + + self.get_end_state() + self.show_result() + + +def main(): + """main function entry""" + + argument_spec = dict( + evpn_overlay_enable=dict( + required=True, type='str', choices=['enable', 'disable']), + ) + argument_spec.update(ce_argument_spec) + evpn_global = EvpnGlobal(argument_spec) + evpn_global.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_facts.py new file mode 100644 index 00000000..6d9c0bab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_facts.py @@ -0,0 +1,414 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_facts +author: "wangdezhuang (@QijunPan)" +short_description: Gets facts about HUAWEI CloudEngine switches. +description: + - Collects facts from CloudEngine devices running the CloudEngine + operating system. Fact collection is supported over Cli + transport. This module prepends all of the base network fact keys + with C(ansible_net_). The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. + +- name: CloudEngine facts test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Gather_subset is all" + community.network.ce_facts: + gather_subset: all + provider: "{{ cli }}" + + - name: "Collect only the config facts" + community.network.ce_facts: + gather_subset: config + provider: "{{ cli }}" + + - name: "Do not collect hardware facts" + community.network.ce_facts: + gather_subset: "!hardware" + provider: "{{ cli }}" +""" + +RETURN = """ +gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +BIOS Version: + description: The BIOS version running on the remote device + returned: always + type: str +Board Type: + description: The board type of the remote device + returned: always + type: str +CPLD1 Version: + description: The CPLD1 Version running the remote device + returned: always + type: str +CPLD2 Version: + description: The CPLD2 Version running the remote device + returned: always + type: str +MAB Version: + description: The MAB Version running the remote device + returned: always + type: str +PCB Version: + description: The PCB Version running the remote device + returned: always + type: str +hostname: + description: The hostname of the remote device + returned: always + type: str + +# hardware +FAN: + description: The fan state on the device + returned: when hardware is configured + type: str +PWR: + description: The power state on the device + returned: when hardware is configured + type: str +filesystems: + description: The filesystems on the device + returned: when hardware is configured + type: str +flash_free: + description: The flash free space on the device + returned: when hardware is configured + type: str +flash_total: + description: The flash total space on the device + returned: when hardware is configured + type: str +memory_free: + description: The memory free space on the remote device + returned: when hardware is configured + type: str +memory_total: + description: The memory total space on the remote device + returned: when hardware is configured + type: str + +# config +config: + description: The current system configuration on the device + returned: when config is configured + type: str + +# interfaces +all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + +import re + +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import run_commands +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS)) + + +class Default(FactsBase): + """ Class default """ + + COMMANDS = [ + 'display version', + 'display current-configuration | include sysname' + ] + + def populate(self): + """ Populate method """ + + super(Default, self).populate() + + data = self.responses[0] + if data: + version = data.split("\n") + for item in version: + if re.findall(r"^\d+\S\s+", item.strip()): + tmp_item = item.split() + tmp_key = tmp_item[1] + " " + tmp_item[2] + if len(tmp_item) > 5: + self.facts[tmp_key] = " ".join(tmp_item[4:]) + else: + self.facts[tmp_key] = tmp_item[4] + + data = self.responses[1] + if data: + tmp_value = re.findall(r'sysname (.*)', data) + self.facts['hostname'] = tmp_value[0] + + +class Config(FactsBase): + """ Class config """ + + COMMANDS = [ + 'display current-configuration configuration system' + ] + + def populate(self): + """ Populate method """ + + super(Config, self).populate() + + data = self.responses[0] + if data: + self.facts['config'] = data.split("\n") + + +class Hardware(FactsBase): + """ Class hardware """ + + COMMANDS = [ + 'dir', + 'display memory', + 'display device' + ] + + def populate(self): + """ Populate method """ + + super(Hardware, self).populate() + + data = self.responses[0] + if data: + self.facts['filesystems'] = re.findall(r'Directory of (.*)/', data)[0] + self.facts['flash_total'] = re.findall(r'(.*) total', data)[0].replace(",", "") + self.facts['flash_free'] = re.findall(r'total \((.*) free\)', data)[0].replace(",", "") + + data = self.responses[1] + if data: + memory_total = re.findall(r'Total Memory Used: (.*) Kbytes', data)[0] + use_percent = re.findall(r'Memory Using Percentage: (.*)%', data)[0] + memory_free = str(int(memory_total) - int(memory_total) * int(use_percent) / 100) + self.facts['memory_total'] = memory_total + " Kb" + self.facts['memory_free'] = memory_free + " Kb" + + data = self.responses[2] + if data: + device_info = data.split("\n") + tmp_device_info = device_info[4:-1] + for item in tmp_device_info: + tmp_item = item.split() + if len(tmp_item) == 8: + self.facts[tmp_item[2]] = tmp_item[6] + elif len(tmp_item) == 7: + self.facts[tmp_item[0]] = tmp_item[5] + + +class Interfaces(FactsBase): + """ Class interfaces """ + + COMMANDS = [ + 'display interface brief', + 'display ip interface brief', + 'display lldp neighbor brief' + ] + + def populate(self): + """ Populate method""" + + interface_dict = dict() + ipv4_addr_dict = dict() + neighbors_dict = dict() + + super(Interfaces, self).populate() + + data = self.responses[0] + begin = False + if data: + interface_info = data.split("\n") + for item in interface_info: + if begin: + tmp_item = item.split() + interface_dict[tmp_item[0]] = tmp_item[1] + + if re.findall(r"^Interface", item.strip()): + begin = True + + self.facts['interfaces'] = interface_dict + + data = self.responses[1] + if data: + ipv4_addr = data.split("\n") + tmp_ipv4 = ipv4_addr[11:] + for item in tmp_ipv4: + tmp_item = item.split() + ipv4_addr_dict[tmp_item[0]] = tmp_item[1] + self.facts['all_ipv4_addresses'] = ipv4_addr_dict + + data = self.responses[2] + if data: + neighbors = data.split("\n") + tmp_neighbors = neighbors[2:] + for item in tmp_neighbors: + tmp_item = item.split() + if len(tmp_item) > 3: + neighbors_dict[tmp_item[0]] = tmp_item[3] + else: + neighbors_dict[tmp_item[0]] = None + self.facts['neighbors'] = neighbors_dict + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """ Module main """ + + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + spec.update(ce_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + # this is to maintain capability with nxos_facts 2.1 + if key.startswith('_'): + ansible_facts[key[1:]] = value + else: + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_file_copy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_file_copy.py new file mode 100644 index 00000000..8007960b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_file_copy.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_file_copy +short_description: Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches. +description: + - Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches. +author: + - Zhou Zhijin (@QijunPan) +notes: + - The feature must be enabled with feature scp-server. + - If the file is already present, no transfer will take place. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +requirements: + - paramiko +options: + local_file: + description: + - Path to local file. Local directory must exist. + The maximum length of I(local_file) is C(4096). + required: true + remote_file: + description: + - Remote file path of the copy. Remote directories must exist. + If omitted, the name of the local file will be used. + The maximum length of I(remote_file) is C(4096). + file_system: + description: + - The remote file system of the device. If omitted, + devices that support a I(file_system) parameter will use + their default values. + File system indicates the storage medium and can be set to as follows, + 1) C(flash) is root directory of the flash memory on the master MPU. + 2) C(slave#flash) is root directory of the flash memory on the slave MPU. + If no slave MPU exists, this drive is unavailable. + 3) C(chassis ID/slot number#flash) is root directory of the flash memory on + a device in a stack. For example, C(1/5#flash) indicates the flash memory + whose chassis ID is 1 and slot number is 5. + default: 'flash:' +''' + +EXAMPLES = ''' +- name: File copy test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Copy a local file to remote device" + community.network.ce_file_copy: + local_file: /usr/vrpcfg.cfg + remote_file: /vrpcfg.cfg + file_system: 'flash:' + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +transfer_result: + description: information about transfer result. + returned: always + type: str + sample: 'The local file has been successfully transferred to the device.' +local_file: + description: The path of the local file. + returned: always + type: str + sample: '/usr/work/vrpcfg.zip' +remote_file: + description: The path of the remote file. + returned: always + type: str + sample: '/vrpcfg.zip' +''' + +import re +import os +import sys +import time +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_v6_address + +try: + import paramiko + HAS_PARAMIKO = True +except ImportError: + HAS_PARAMIKO = False + +try: + from scp import SCPClient + HAS_SCP = True +except ImportError: + HAS_SCP = False + +CE_NC_GET_DISK_INFO = """ + + + + + + + + + + + + +""" + +CE_NC_GET_FILE_INFO = """ + + + + + %s + %s + + + + + +""" + +CE_NC_GET_SCP_ENABLE = """ + + + + +""" + + +def get_cli_exception(exc=None): + """Get cli exception message""" + + msg = list() + if not exc: + exc = sys.exc_info[1] + if exc: + errs = str(exc).split("\r\n") + for err in errs: + if not err: + continue + if "matched error in response:" in err: + continue + if " at '^' position" in err: + err = err.replace(" at '^' position", "") + if err.replace(" ", "") == "^": + continue + if len(err) > 2 and err[0] in ["<", "["] and err[-1] in [">", "]"]: + continue + if err[-1] == ".": + err = err[:-1] + if err.replace(" ", "") == "": + continue + msg.append(err) + else: + msg = ["Error: Fail to get cli exception message."] + + while msg[-1][-1] == ' ': + msg[-1] = msg[-1][:-1] + + if msg[-1][-1] != ".": + msg[-1] += "." + + return ", ".join(msg).capitalize() + + +class FileCopy(object): + """File copy function class""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # file copy parameters + self.local_file = self.module.params['local_file'] + self.remote_file = self.module.params['remote_file'] + self.file_system = self.module.params['file_system'] + self.host_is_ipv6 = validate_ip_v6_address(self.module.params['provider']['host']) + + # state + self.transfer_result = None + self.changed = False + + def init_module(self): + """Init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def remote_file_exists(self, dst, file_system='flash:'): + """Remote file whether exists""" + + full_path = file_system + dst + file_name = os.path.basename(full_path) + file_path = os.path.dirname(full_path) + file_path = file_path + '/' + xml_str = CE_NC_GET_FILE_INFO % (file_name, file_path) + ret_xml = get_nc_config(self.module, xml_str) + if "" in ret_xml: + return False, 0 + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get file info + root = ElementTree.fromstring(xml_str) + topo = root.find("vfm/dirs/dir") + if topo is None: + return False, 0 + + for eles in topo: + if eles.tag in ["DirSize"]: + return True, int(eles.text.replace(',', '')) + + return False, 0 + + def local_file_exists(self): + """Local file whether exists""" + + return os.path.isfile(self.local_file) + + def enough_space(self): + """Whether device has enough space""" + + xml_str = CE_NC_GET_DISK_INFO + ret_xml = get_nc_config(self.module, xml_str) + if "" in ret_xml: + return + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + topo = root.find("vfm/dfs/df/freeSize") + kbytes_free = topo.text + + file_size = os.path.getsize(self.local_file) + if int(kbytes_free) * 1024 > file_size: + return True + + return False + + def transfer_file(self, dest): + """Begin to transfer file by scp""" + + if not self.local_file_exists(): + self.module.fail_json( + msg='Could not transfer file. Local file doesn\'t exist.') + + if not self.enough_space(): + self.module.fail_json( + msg='Could not transfer file. Not enough space on device.') + + hostname = self.module.params['provider']['host'] + username = self.module.params['provider']['username'] + password = self.module.params['provider']['password'] + port = self.module.params['provider']['port'] + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=hostname, username=username, password=password, port=port) + full_remote_path = '{0}{1}'.format(self.file_system, dest) + scp = SCPClient(ssh.get_transport()) + try: + scp.put(self.local_file, full_remote_path) + except Exception: + time.sleep(10) + file_exists, temp_size = self.remote_file_exists( + dest, self.file_system) + file_size = os.path.getsize(self.local_file) + if file_exists and int(temp_size) == int(file_size): + pass + else: + scp.close() + self.module.fail_json(msg='Could not transfer file. There was an error ' + 'during transfer. Please make sure the format of ' + 'input parameters is right.') + scp.close() + return True + + def get_scp_enable(self): + """Get scp enable state""" + + ret_xml = '' + try: + ret_xml = get_nc_config(self.module, CE_NC_GET_SCP_ENABLE) + except ConnectionError: + self.module.fail_json(msg='Error: The NETCONF API of scp_enable is not supported.') + + if "" in ret_xml: + return False + + xml_str = ret_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get file info + root = ElementTree.fromstring(xml_str) + topo1 = root.find("sshs/sshServer/scpEnable") + topo2 = root.find("sshs/sshServerEnable/scpIpv4Enable") + topo3 = root.find("sshs/sshServerEnable/scpIpv6Enable") + if topo1 is not None: + return str(topo1.text).strip().lower() == 'enable' + elif self.host_is_ipv6 and topo3 is not None: + return str(topo3.text).strip().lower() == 'enable' + elif topo2 is not None: + return str(topo2.text).strip().lower() == 'enable' + return False + + def work(self): + """Execute task """ + + if not HAS_SCP: + self.module.fail_json( + msg="'Error: No scp package, please install it.'") + + if not HAS_PARAMIKO: + self.module.fail_json( + msg="'Error: No paramiko package, please install it.'") + + if self.local_file and len(self.local_file) > 4096: + self.module.fail_json( + msg="'Error: The maximum length of local_file is 4096.'") + + if self.remote_file and len(self.remote_file) > 4096: + self.module.fail_json( + msg="'Error: The maximum length of remote_file is 4096.'") + + scp_enable = self.get_scp_enable() + if not scp_enable: + if self.host_is_ipv6: + self.module.fail_json( + msg="'Error: Please ensure ipv6 SCP server are enabled.'") + else: + self.module.fail_json( + msg="'Error: Please ensure ipv4 SCP server are enabled.'") + + if not os.path.isfile(self.local_file): + self.module.fail_json( + msg="Local file {0} not found".format(self.local_file)) + + dest = self.remote_file or ('/' + os.path.basename(self.local_file)) + remote_exists, file_size = self.remote_file_exists( + dest, file_system=self.file_system) + if remote_exists and (os.path.getsize(self.local_file) != file_size): + remote_exists = False + + if not remote_exists: + self.changed = True + file_exists = False + else: + file_exists = True + self.transfer_result = 'The local file already exists on the device.' + + if not file_exists: + self.transfer_file(dest) + self.transfer_result = 'The local file has been successfully transferred to the device.' + + if self.remote_file is None: + self.remote_file = '/' + os.path.basename(self.local_file) + + self.module.exit_json( + changed=self.changed, + transfer_result=self.transfer_result, + local_file=self.local_file, + remote_file=self.remote_file, + file_system=self.file_system) + + +def main(): + """Main function entry""" + + argument_spec = dict( + local_file=dict(required=True), + remote_file=dict(required=False), + file_system=dict(required=False, default='flash:') + ) + argument_spec.update(ce_argument_spec) + filecopy_obj = FileCopy(argument_spec) + filecopy_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_debug.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_debug.py new file mode 100644 index 00000000..bdd8ee1a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_debug.py @@ -0,0 +1,613 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_debug +short_description: Manages information center debug configuration on HUAWEI CloudEngine switches. +description: + - Manages information center debug configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + debug_time_stamp: + description: + - Timestamp type of debugging information. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 'shortdate_second', + 'shortdate_tenthsecond', 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond'] + module_name: + description: + - Module name of the rule. + The value is a string of 1 to 31 case-insensitive characters. The default value is default. + Please use lower-case letter, such as [aaa, acl, arp, bfd]. + channel_id: + description: + - Number of a channel. + The value is an integer ranging from 0 to 9. The default value is 0. + debug_enable: + description: + - Whether a device is enabled to output debugging information. + default: no_use + choices: ['no_use','true','false'] + debug_level: + description: + - Debug level permitted to output. + choices: ['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center debug test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config debug time stamp" + community.network.ce_info_center_debug: + state: present + debug_time_stamp: date_boot + provider: "{{ cli }}" + + - name: "Undo debug time stamp" + community.network.ce_info_center_debug: + state: absent + debug_time_stamp: date_boot + provider: "{{ cli }}" + + - name: "Config debug module log level" + community.network.ce_info_center_debug: + state: present + module_name: aaa + channel_id: 1 + debug_enable: true + debug_level: error + provider: "{{ cli }}" + + - name: "Undo debug module log level" + community.network.ce_info_center_debug: + state: absent + module_name: aaa + channel_id: 1 + debug_enable: true + debug_level: error + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"state": "present", "debug_time_stamp": "date_boot"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"debugTimeStamp": "DATE_MILLISECOND"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"debugTimeStamp": "DATE_BOOT"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center timestamp debugging boot"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get info center debug global +CE_GET_DEBUG_GLOBAL_HEADER = """ + + + +""" +CE_GET_DEBUG_GLOBAL_TAIL = """ + + + +""" +# merge info center debug global +CE_MERGE_DEBUG_GLOBAL_HEADER = """ + + + +""" +CE_MERGE_DEBUG_GLOBAL_TAIL = """ + + + +""" + +# get info center debug source +CE_GET_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_GET_DEBUG_SOURCE_TAIL = """ + + + + +""" +# merge info center debug source +CE_MERGE_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_MERGE_DEBUG_SOURCE_TAIL = """ + + + + +""" +# delete info center debug source +CE_DELETE_DEBUG_SOURCE_HEADER = """ + + + + +""" +CE_DELETE_DEBUG_SOURCE_TAIL = """ + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_DBG_STATE = {"0": "true", + "1": "true", + "2": "false", + "3": "false", + "4": "false", + "5": "false", + "6": "false", + "7": "false", + "8": "false", + "9": "false"} + +CHANNEL_DEFAULT_DBG_LEVEL = {"0": "debugging", + "1": "debugging", + "2": "debugging", + "3": "debugging", + "4": "debugging", + "5": "debugging", + "6": "debugging", + "7": "debugging", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterDebug(object): + """ Manages info center debug configuration """ + + def __init__(self, **kwargs): + """ Init function """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.debug_time_stamp = self.module.params['debug_time_stamp'] or None + self.module_name = self.module.params['module_name'] or None + self.channel_id = self.module.params['channel_id'] or None + self.debug_enable = self.module.params['debug_enable'] + self.debug_level = self.module.params['debug_level'] or None + + # cur config + self.cur_global_cfg = dict() + self.cur_source_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_global_args(self): + """ Check global args """ + + need_cfg = False + find_flag = False + self.cur_global_cfg["global_cfg"] = [] + + if self.debug_time_stamp: + + conf_str = CE_GET_DEBUG_GLOBAL_HEADER + conf_str += "" + conf_str += CE_GET_DEBUG_GLOBAL_TAIL + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + find_flag = False + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_cfg = root.findall("syslog/globalParam") + if global_cfg: + for tmp in global_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["debugTimeStamp"]: + tmp_dict[site.tag] = site.text + + self.cur_global_cfg["global_cfg"].append(tmp_dict) + + if self.cur_global_cfg["global_cfg"]: + for tmp in self.cur_global_cfg["global_cfg"]: + find_flag = True + + if tmp.get("debugTimeStamp").lower() != self.debug_time_stamp: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_global_cfg["need_cfg"] = need_cfg + + def check_source_args(self): + """ Check source args """ + + need_cfg = False + find_flag = False + self.cur_source_cfg["source_cfg"] = [] + + if self.module_name: + if len(self.module_name) < 1 or len(self.module_name) > 31: + self.module.fail_json( + msg='Error: The module_name is out of [1 - 31].') + + if not self.channel_id: + self.module.fail_json( + msg='Error: Please input channel_id at the same time.') + + if self.channel_id: + if self.channel_id.isdigit(): + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg='Error: The value of channel_id is out of [0 - 9].') + else: + self.module.fail_json( + msg='Error: The channel_id is not digit.') + + conf_str = CE_GET_DEBUG_SOURCE_HEADER + + if self.module_name != "default": + conf_str += "%s" % self.module_name.upper() + else: + conf_str += "default" + + if self.channel_id: + conf_str += "" + if self.debug_enable != 'no_use': + conf_str += "" + if self.debug_level: + conf_str += "" + + conf_str += CE_GET_DEBUG_SOURCE_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + find_flag = False + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + source_cfg = root.findall("syslog/icSources/icSource") + if source_cfg: + for tmp in source_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["moduleName", "icChannelId", "dbgEnFlg", "dbgEnLevel"]: + tmp_dict[site.tag] = site.text + + self.cur_source_cfg["source_cfg"].append(tmp_dict) + + if self.cur_source_cfg["source_cfg"]: + for tmp in self.cur_source_cfg["source_cfg"]: + find_flag = True + + if self.module_name and tmp.get("moduleName").lower() != self.module_name.lower(): + find_flag = False + if self.channel_id and tmp.get("icChannelId") != self.channel_id: + find_flag = False + if self.debug_enable != 'no_use' and tmp.get("dbgEnFlg") != self.debug_enable: + find_flag = False + if self.debug_level and tmp.get("dbgEnLevel") != self.debug_level: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_source_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed """ + + self.proposed["state"] = self.state + + if self.debug_time_stamp: + self.proposed["debug_time_stamp"] = self.debug_time_stamp + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.debug_enable != 'no_use': + self.proposed["debug_enable"] = self.debug_enable + if self.debug_level: + self.proposed["debug_level"] = self.debug_level + + def get_existing(self): + """ Get existing """ + + if self.cur_global_cfg["global_cfg"]: + self.existing["global_cfg"] = self.cur_global_cfg["global_cfg"] + if self.cur_source_cfg["source_cfg"]: + self.existing["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def get_end_state(self): + """ Get end state """ + + self.check_global_args() + if self.cur_global_cfg["global_cfg"]: + self.end_state["global_cfg"] = self.cur_global_cfg["global_cfg"] + + self.check_source_args() + if self.cur_source_cfg["source_cfg"]: + self.end_state["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def merge_debug_global(self): + """ Merge debug global """ + + conf_str = CE_MERGE_DEBUG_GLOBAL_HEADER + + if self.debug_time_stamp: + conf_str += "%s" % self.debug_time_stamp.upper() + + conf_str += CE_MERGE_DEBUG_GLOBAL_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge debug global failed.') + + if self.debug_time_stamp: + cmd = "info-center timestamp debugging " + TIME_STAMP_DICT.get(self.debug_time_stamp) + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_debug_global(self): + """ Delete debug global """ + + conf_str = CE_MERGE_DEBUG_GLOBAL_HEADER + + if self.debug_time_stamp: + conf_str += "DATE_MILLISECOND" + + conf_str += CE_MERGE_DEBUG_GLOBAL_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: delete debug global failed.') + + if self.debug_time_stamp: + cmd = "undo info-center timestamp debugging" + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_debug_source(self): + """ Merge debug source """ + + conf_str = CE_MERGE_DEBUG_SOURCE_HEADER + + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.debug_enable != 'no_use': + conf_str += "%s" % self.debug_enable + if self.debug_level: + conf_str += "%s" % self.debug_level + + conf_str += CE_MERGE_DEBUG_SOURCE_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge debug source failed.') + + cmd = "info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.debug_enable != 'no_use': + if self.debug_enable == "true": + cmd += " debug state on" + else: + cmd += " debug state off" + if self.debug_level: + cmd += " level %s" % self.debug_level + + self.updates_cmd.append(cmd) + self.changed = True + + def delete_debug_source(self): + """ Delete debug source """ + + if self.debug_enable == 'no_use' and not self.debug_level: + conf_str = CE_DELETE_DEBUG_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + conf_str += CE_DELETE_DEBUG_SOURCE_TAIL + else: + conf_str = CE_MERGE_DEBUG_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.debug_enable != 'no_use': + conf_str += "%s" % CHANNEL_DEFAULT_DBG_STATE.get(self.channel_id) + if self.debug_level: + conf_str += "%s" % CHANNEL_DEFAULT_DBG_LEVEL.get(self.channel_id) + conf_str += CE_MERGE_DEBUG_SOURCE_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete debug source failed.') + + cmd = "undo info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.debug_enable != 'no_use': + cmd += " debug state" + if self.debug_level: + cmd += " level" + + self.updates_cmd.append(cmd) + self.changed = True + + def work(self): + """ work function """ + + self.check_global_args() + self.check_source_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_global_cfg["need_cfg"]: + self.merge_debug_global() + if self.cur_source_cfg["need_cfg"]: + self.merge_debug_source() + + else: + if self.cur_global_cfg["need_cfg"]: + self.delete_debug_global() + if self.cur_source_cfg["need_cfg"]: + self.delete_debug_source() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + debug_time_stamp=dict(choices=['date_boot', 'date_second', 'date_tenthsecond', + 'date_millisecond', 'shortdate_second', 'shortdate_tenthsecond', + 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond']), + module_name=dict(type='str'), + channel_id=dict(type='str'), + debug_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + debug_level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterDebug(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_global.py new file mode 100644 index 00000000..64fad157 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_global.py @@ -0,0 +1,1721 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_global +short_description: Manages outputting logs on HUAWEI CloudEngine switches. +description: + - This module offers the ability to be output to the log buffer, log file, console, terminal, or log host on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + info_center_enable: + description: + - Whether the info-center function is enabled. The value is of the Boolean type. + choices: ['true','false'] + packet_priority: + description: + - Set the priority of the syslog packet.The value is an integer ranging from 0 to 7. The default value is 0. + suppress_enable: + description: + - Whether a device is enabled to suppress duplicate statistics. The value is of the Boolean type. + choices: [ 'false', 'true' ] + logfile_max_num: + description: + - Maximum number of log files of the same type. The default value is 200. + - The value range for log files is[3, 500], for security files is [1, 3],and for operation files is [1, 7]. + logfile_max_size: + description: + - Maximum size (in MB) of a log file. The default value is 32. + - The value range for log files is [4, 8, 16, 32], for security files is [1, 4], + - and for operation files is [1, 4]. + default: 32 + choices: ['4', '8', '16', '32'] + channel_id: + description: + - Number for channel. The value is an integer ranging from 0 to 9. The default value is 0. + channel_cfg_name: + description: + - Channel name.The value is a string of 1 to 30 case-sensitive characters. The default value is console. + default: console + channel_out_direct: + description: + - Direction of information output. + choices: ['console','monitor','trapbuffer','logbuffer','snmp','logfile'] + filter_feature_name: + description: + - Feature name of the filtered log. The value is a string of 1 to 31 case-insensitive characters. + filter_log_name: + description: + - Name of the filtered log. The value is a string of 1 to 63 case-sensitive characters. + ip_type: + description: + - Log server address type, IPv4 or IPv6. + choices: ['ipv4','ipv6'] + server_ip: + description: + - Log server address, IPv4 or IPv6 type. The value is a string of 0 to 255 characters. + The value can be an valid IPv4 or IPv6 address. + server_domain: + description: + - Server name. The value is a string of 1 to 255 case-sensitive characters. + is_default_vpn: + description: + - Use the default VPN or not. + type: bool + default: 'no' + vrf_name: + description: + - VPN name on a log server. The value is a string of 1 to 31 case-sensitive characters. + The default value is _public_. + level: + description: + - Level of logs saved on a log server. + choices: ['emergencies','alert','critical','error','warning','notification','informational','debugging'] + server_port: + description: + - Number of a port sending logs.The value is an integer ranging from 1 to 65535. + For UDP, the default value is 514. For TCP, the default value is 601. For TSL, the default value is 6514. + facility: + description: + - Log record tool. + choices: ['local0','local1','local2','local3','local4','local5','local6','local7'] + channel_name: + description: + - Channel name. The value is a string of 1 to 30 case-sensitive characters. + timestamp: + description: + - Log server timestamp. The value is of the enumerated type and case-sensitive. + choices: ['UTC', 'localtime'] + transport_mode: + description: + - Transport mode. The value is of the enumerated type and case-sensitive. + choices: ['tcp','udp'] + ssl_policy_name: + description: + - SSL policy name. The value is a string of 1 to 23 case-sensitive characters. + source_ip: + description: + - Log source ip address, IPv4 or IPv6 type. The value is a string of 0 to 255. + The value can be an valid IPv4 or IPv6 address. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Info center global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config info-center enable + community.network.ce_info_center_global: + info_center_enable: true + state: present + provider: "{{ cli }}" + + - name: Config statistic-suppress enable + community.network.ce_info_center_global: + suppress_enable: true + state: present + provider: "{{ cli }}" + + - name: Config info-center syslog packet-priority 1 + community.network.ce_info_center_global: + packet_priority: 2 + state: present + provider: "{{ cli }}" + + - name: Config info-center channel 1 name aaa + community.network.ce_info_center_global: + channel_id: 1 + channel_cfg_name: aaa + state: present + provider: "{{ cli }}" + + - name: Config info-center logfile size 10 + community.network.ce_info_center_global: + logfile_max_num: 10 + state: present + provider: "{{ cli }}" + + - name: Config info-center console channel 1 + community.network.ce_info_center_global: + channel_out_direct: console + channel_id: 1 + state: present + provider: "{{ cli }}" + + - name: Config info-center filter-id bymodule-alias snmp snmp_ipunlock + community.network.ce_info_center_global: + filter_feature_name: SNMP + filter_log_name: SNMP_IPLOCK + state: present + provider: "{{ cli }}" + + + - name: Config info-center max-logfile-number 16 + community.network.ce_info_center_global: + logfile_max_size: 16 + state: present + provider: "{{ cli }}" + + - name: Config syslog loghost domain. + community.network.ce_info_center_global: + server_domain: aaa + vrf_name: aaa + channel_id: 1 + transport_mode: tcp + facility: local4 + server_port: 100 + level: alert + timestamp: UTC + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"channel_id": "1", "facility": "local4", "is_default_vpn": True, "level": "alert", "server_domain": "aaa", + "server_port": "100", "state": "present", "timestamp": "localtime", "transport_mode": "tcp"} +existing: + description: k/v pairs of existing rollback + returned: always + type: dict + sample: + "server_domain_info": [ + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "false", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": "gmc", + "timestamp": "UTC", + "transportMode": "tcp", + "vrfName": "aaa" + } + ] +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: + "server_domain_info": [ + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "true", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": null, + "timestamp": "localtime", + "transportMode": "tcp", + "vrfName": "_public_" + }, + { + "chnlId": "1", + "chnlName": "monitor", + "facility": "local4", + "isBriefFmt": "false", + "isDefaultVpn": "false", + "level": "alert", + "serverDomain": "aaa", + "serverPort": "100", + "sourceIP": "0.0.0.0", + "sslPolicyName": "gmc", + "timestamp": "UTC", + "transportMode": "tcp", + "vrfName": "aaa" + } + ] +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center loghost domain aaa level alert port 100 facility local4 channel 1 localtime transport tcp"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config, check_ip_addr + + +CE_NC_GET_CENTER_GLOBAL_INFO_HEADER = """ + + + +""" +CE_NC_GET_CENTER_GLOBAL_INFO_TAIL = """ + + + +""" + +CE_NC_MERGE_CENTER_GLOBAL_INFO_HEADER = """ + + + +""" + +CE_NC_MERGE_CENTER_GLOBAL_INFO_TAIL = """ + + + +""" + +CE_NC_GET_LOG_FILE_INFO_HEADER = """ + + + + +""" +CE_NC_GET_LOG_FILE_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_LOG_FILE_INFO_HEADER = """ + + + + +""" + +CE_NC_MERGE_LOG_FILE_INFO_TAIL = """ + + + + +""" + + +CE_NC_GET_CHANNEL_INFO = """ + + + + + %s + + + + + +""" + +CE_NC_MERGE_CHANNEL_INFO_HEADER = """ + + + + +""" +CE_NC_MERGE_CHANNEL_INFO_TAIL = """ + + + + +""" + +CE_NC_GET_CHANNEL_DIRECT_INFO = """ + + + + + %s + + + + + +""" +CE_NC_MERGE_CHANNEL_DIRECT_HEADER = """ + + + + +""" + +CE_NC_MERGE_CHANNEL_DIRECT_TAIL = """ + + + + +""" + +CE_NC_GET_FILTER_INFO = """ + + + + + + + + + + +""" + +CE_NC_CREATE_CHANNEL_FILTER_HEADER = """ + + + + + +""" +CE_NC_CREATE_CHANNEL_FILTER_TAIL = """ + + + + +""" +CE_NC_DELETE_CHANNEL_FILTER_HEADER = """ + + + + + +""" +CE_NC_DELETE_CHANNEL_FILTER_TAIL = """ + + + + +""" + +CE_NC_GET_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_GET_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_MERGE_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_MERGE_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_DELETE_SERVER_IP_INFO_HEADER = """ + + + + + %s + %s + %s + %s +""" +CE_NC_DELETE_SERVER_IP_INFO_TAIL = """ + + + + +""" +CE_NC_GET_SERVER_DNS_INFO_HEADER = """ + + + + +""" + +CE_NC_GET_SERVER_DNS_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_SERVER_DNS_INFO_HEADER = """ + + + + + %s + %s + %s +""" +CE_NC_MERGE_SERVER_DNS_INFO_TAIL = """ + + + + +""" + +CE_NC_DELETE_SERVER_DNS_INFO_HEADER = """ + + + + + %s + %s + %s +""" +CE_NC_DELETE_SERVER_DNS_INFO_TAIL = """ + + + + +""" + + +def get_out_direct_default(out_direct): + """get default out direct""" + + outdict = {"console": "1", "monitor": "2", "trapbuffer": "3", + "logbuffer": "4", "snmp": "5", "logfile": "6"} + channel_id_default = outdict.get(out_direct) + return channel_id_default + + +def get_channel_name_default(channel_id): + """get default out direct""" + + channel_dict = {"0": "console", "1": "monitor", "2": "loghost", "3": "trapbuffer", "4": "logbuffer", + "5": "snmpagent", "6": "channel6", "7": "channel7", "8": "channel8", "9": "channel9"} + channel_name_default = channel_dict.get(channel_id) + return channel_name_default + + +class InfoCenterGlobal(object): + """ + Manages info center global configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.info_center_enable = self.module.params['info_center_enable'] or None + self.packet_priority = self.module.params['packet_priority'] or None + self.suppress_enable = self.module.params['suppress_enable'] or None + self.logfile_max_num = self.module.params['logfile_max_num'] or None + self.logfile_max_size = self.module.params['logfile_max_size'] or None + self.channel_id = self.module.params['channel_id'] or None + self.channel_cfg_name = self.module.params['channel_cfg_name'] or None + self.channel_out_direct = self.module.params['channel_out_direct'] or None + self.filter_feature_name = self.module.params['filter_feature_name'] or None + self.filter_log_name = self.module.params['filter_log_name'] or None + self.ip_type = self.module.params['ip_type'] or None + self.server_ip = self.module.params['server_ip'] or None + self.server_domain = self.module.params['server_domain'] or None + self.is_default_vpn = self.module.params['is_default_vpn'] or None + self.vrf_name = self.module.params['vrf_name'] or None + self.level = self.module.params['level'] or None + self.server_port = self.module.params['server_port'] or None + self.facility = self.module.params['facility'] or None + self.channel_name = self.module.params['channel_name'] or None + self.timestamp = self.module.params['timestamp'] or None + self.transport_mode = self.module.params['transport_mode'] or None + self.ssl_policy_name = self.module.params['ssl_policy_name'] or None + self.source_ip = self.module.params['source_ip'] or None + self.state = self.module.params['state'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # syslog info + self.cur_global_info = None + self.cur_logfile_info = None + self.channel_info = None + self.channel_direct_info = None + self.filter_info = None + self.server_ip_info = None + self.server_domain_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed.""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_channel_dict(self): + """ get channel attributes dict.""" + + channel_info = dict() + # get channel info + conf_str = CE_NC_GET_CHANNEL_INFO % self.channel_id + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return channel_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + channel_info["channelInfos"] = list() + channels = root.findall("syslog/icChannels/icChannel") + if channels: + for channel in channels: + channel_dict = dict() + for ele in channel: + if ele.tag in ["icChnlId", "icChnlCfgName"]: + channel_dict[ele.tag] = ele.text + channel_info["channelInfos"].append(channel_dict) + return channel_info + + def is_exist_channel_id_name(self, channel_id, channel_name): + """if channel id exist""" + + if not self.channel_info: + return False + + for id2name in self.channel_info["channelInfos"]: + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] == channel_name: + return True + return False + + def config_merge_syslog_channel(self, channel_id, channel_name): + """config channel id""" + + if not self.is_exist_channel_id_name(channel_id, channel_name): + conf_str = CE_NC_MERGE_CHANNEL_INFO_HEADER + if channel_id: + conf_str += "%s" % channel_id + if channel_name: + conf_str += "%s" % channel_name + + conf_str += CE_NC_MERGE_CHANNEL_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel id failed.') + + self.updates_cmd.append( + "info-center channel %s name %s" % (channel_id, channel_name)) + self.changed = True + + def delete_merge_syslog_channel(self, channel_id, channel_name): + """delete channel id""" + + change_flag = False + + if channel_name: + for id2name in self.channel_info["channelInfos"]: + channel_default_name = get_channel_name_default( + id2name["icChnlId"]) + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] == channel_name: + channel_name = channel_default_name + change_flag = True + + if not channel_name: + for id2name in self.channel_info["channelInfos"]: + channel_default_name = get_channel_name_default( + id2name["icChnlId"]) + if id2name["icChnlId"] == channel_id and id2name["icChnlCfgName"] != channel_default_name: + channel_name = channel_default_name + change_flag = True + if change_flag: + conf_str = CE_NC_MERGE_CHANNEL_INFO_HEADER + if channel_id: + conf_str += "%s" % channel_id + if channel_name: + conf_str += "%s" % channel_name + + conf_str += CE_NC_MERGE_CHANNEL_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel id failed.') + + self.updates_cmd.append("undo info-center channel %s" % channel_id) + self.changed = True + + def get_channel_direct_dict(self): + """ get channel direct attributes dict.""" + + channel_direct_info = dict() + # get channel direct info + conf_str = CE_NC_GET_CHANNEL_DIRECT_INFO % self.channel_out_direct + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return channel_direct_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + channel_direct_info["channelDirectInfos"] = list() + dir_channels = root.findall("syslog/icDirChannels/icDirChannel") + if dir_channels: + for ic_dir_channel in dir_channels: + channel_direct_dict = dict() + for ele in ic_dir_channel: + if ele.tag in ["icOutDirect", "icCfgChnlId"]: + channel_direct_dict[ele.tag] = ele.text + channel_direct_info["channelDirectInfos"].append( + channel_direct_dict) + return channel_direct_info + + def is_exist_out_direct(self, out_direct, channel_id): + """if channel out direct exist""" + + if not self.channel_direct_info: + return False + + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] == channel_id: + return True + return False + + def config_merge_out_direct(self, out_direct, channel_id): + """config out direct""" + + if not self.is_exist_out_direct(out_direct, channel_id): + conf_str = CE_NC_MERGE_CHANNEL_DIRECT_HEADER + if out_direct: + conf_str += "%s" % out_direct + if channel_id: + conf_str += "%s" % channel_id + + conf_str += CE_NC_MERGE_CHANNEL_DIRECT_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + + self.updates_cmd.append( + "info-center %s channel %s" % (out_direct, channel_id)) + self.changed = True + + def delete_merge_out_direct(self, out_direct, channel_id): + """delete out direct""" + + change_flag = False + channel_id_default = get_out_direct_default(out_direct) + if channel_id: + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] == channel_id: + if channel_id != channel_id_default: + channel_id = channel_id_default + change_flag = True + + if not channel_id: + for id2name in self.channel_direct_info["channelDirectInfos"]: + if id2name["icOutDirect"] == out_direct and id2name["icCfgChnlId"] != channel_id_default: + channel_id = channel_id_default + change_flag = True + + if change_flag: + conf_str = CE_NC_MERGE_CHANNEL_DIRECT_HEADER + if out_direct: + conf_str += "%s" % out_direct + if channel_id: + conf_str += "%s" % channel_id + + conf_str += CE_NC_MERGE_CHANNEL_DIRECT_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + + self.updates_cmd.append("undo info-center logfile channel") + self.changed = True + + def get_filter_dict(self): + """ get syslog filter attributes dict.""" + + filter_info = dict() + # get filter info + conf_str = CE_NC_GET_FILTER_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return filter_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + filter_info["filterInfos"] = list() + ic_filters = root.findall("syslog/icFilters/icFilter") + if ic_filters: + for ic_filter in ic_filters: + filter_dict = dict() + for ele in ic_filter: + if ele.tag in ["icFeatureName", "icFilterLogName"]: + filter_dict[ele.tag] = ele.text + filter_info["filterInfos"].append(filter_dict) + return filter_info + + def is_exist_filter(self, filter_feature_name, filter_log_name): + """if filter info exist""" + + if not self.filter_info: + return False + for id2name in self.filter_info["filterInfos"]: + if id2name["icFeatureName"] == filter_feature_name and id2name["icFilterLogName"] == filter_log_name: + return True + return False + + def config_merge_filter(self, filter_feature_name, filter_log_name): + """config filter""" + + if not self.is_exist_filter(filter_feature_name, filter_log_name): + conf_str = CE_NC_CREATE_CHANNEL_FILTER_HEADER + conf_str += "true" + if filter_feature_name: + conf_str += "%s" % filter_feature_name + if filter_log_name: + conf_str += "%s" % filter_log_name + + conf_str += CE_NC_CREATE_CHANNEL_FILTER_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge syslog filter failed.') + + self.updates_cmd.append("info-center filter-id bymodule-alias %s %s" + % (filter_feature_name, filter_log_name)) + self.changed = True + + def delete_merge_filter(self, filter_feature_name, filter_log_name): + """delete filter""" + + change_flag = False + if self.is_exist_filter(filter_feature_name, filter_log_name): + for id2name in self.filter_info["filterInfos"]: + if id2name["icFeatureName"] == filter_feature_name and id2name["icFilterLogName"] == filter_log_name: + change_flag = True + if change_flag: + conf_str = CE_NC_DELETE_CHANNEL_FILTER_HEADER + conf_str += "true" + if filter_feature_name: + conf_str += "%s" % filter_feature_name + if filter_log_name: + conf_str += "%s" % filter_log_name + + conf_str += CE_NC_DELETE_CHANNEL_FILTER_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog channel out direct failed.') + self.updates_cmd.append("undo info-center filter-id bymodule-alias %s %s" + % (filter_feature_name, filter_log_name)) + self.changed = True + + def get_server_ip_dict(self): + """ get server ip attributes dict.""" + + server_ip_info = dict() + # get server ip info + is_default_vpn = "false" + if not self.is_default_vpn: + self.is_default_vpn = False + if self.is_default_vpn is True: + is_default_vpn = "true" + if not self.vrf_name: + self.vrf_name = "_public_" + conf_str = CE_NC_GET_SERVER_IP_INFO_HEADER % ( + self.ip_type, self.server_ip, self.vrf_name, is_default_vpn) + conf_str += CE_NC_GET_SERVER_IP_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return server_ip_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + server_ip_info["serverIpInfos"] = list() + syslog_servers = root.findall("syslog/syslogServers/syslogServer") + if syslog_servers: + for syslog_server in syslog_servers: + server_dict = dict() + for ele in syslog_server: + if ele.tag in ["ipType", "serverIp", "vrfName", "level", "serverPort", "facility", "chnlId", + "chnlName", "timestamp", "transportMode", "sslPolicyName", "isDefaultVpn", + "sourceIP", "isBriefFmt"]: + server_dict[ele.tag] = ele.text + server_ip_info["serverIpInfos"].append(server_dict) + return server_ip_info + + def config_merge_loghost(self): + """config loghost ip or dns""" + + conf_str = "" + is_default_vpn = "false" + if self.is_default_vpn is True: + is_default_vpn = "true" + if self.ip_type: + conf_str = CE_NC_MERGE_SERVER_IP_INFO_HEADER % (self.ip_type, self.server_ip, self.vrf_name, + is_default_vpn) + elif self.server_domain: + conf_str = CE_NC_MERGE_SERVER_DNS_INFO_HEADER % ( + self.server_domain, self.vrf_name, is_default_vpn) + if self.level: + conf_str += "%s" % self.level + if self.server_port: + conf_str += "%s" % self.server_port + if self.facility: + conf_str += "%s" % self.facility + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.channel_name: + conf_str += "%s" % self.channel_name + if self.timestamp: + conf_str += "%s" % self.timestamp + if self.transport_mode: + conf_str += "%s" % self.transport_mode + if self.ssl_policy_name: + conf_str += "%s" % self.ssl_policy_name + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.ip_type: + conf_str += CE_NC_MERGE_SERVER_IP_INFO_TAIL + elif self.server_domain: + conf_str += CE_NC_MERGE_SERVER_DNS_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge server loghost failed.') + + cmd = "info-center loghost" + if self.ip_type == "ipv4" and self.server_ip: + cmd += " %s" % self.server_ip + if self.ip_type == "ipv6" and self.server_ip: + cmd += " ipv6 %s" % self.server_ip + if self.server_domain: + cmd += " domain %s" % self.server_domain + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.channel_name: + cmd += " channel %s" % self.channel_name + if self.vrf_name: + if self.vrf_name != "_public_": + cmd += " vpn-instance %s" % self.vrf_name + if self.source_ip: + cmd += " source-ip %s" % self.source_ip + if self.facility: + cmd += " facility %s" % self.facility + if self.server_port: + cmd += " port %s" % self.server_port + if self.level: + cmd += " level %s" % self.level + if self.timestamp: + if self.timestamp == "localtime": + cmd += " local-time" + else: + cmd += " utc" + if self.transport_mode: + cmd += " transport %s" % self.transport_mode + if self.ssl_policy_name: + cmd += " ssl-policy %s" % self.ssl_policy_name + self.updates_cmd.append(cmd) + self.changed = True + + def delete_merge_loghost(self): + """delete loghost ip or dns""" + + conf_str = "" + is_default_vpn = "false" + if self.is_default_vpn is True: + is_default_vpn = "true" + if self.ip_type: + conf_str = CE_NC_DELETE_SERVER_IP_INFO_HEADER % (self.ip_type, self.server_ip, self.vrf_name, + is_default_vpn) + elif self.server_domain: + conf_str = CE_NC_DELETE_SERVER_DNS_INFO_HEADER % ( + self.server_domain, self.vrf_name, is_default_vpn) + if self.level: + conf_str += "%s" % self.level + if self.server_port: + conf_str += "%s" % self.server_port + if self.facility: + conf_str += "%s" % self.facility + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.channel_name: + conf_str += "%s" % self.channel_name + if self.timestamp: + conf_str += "%s" % self.timestamp + if self.transport_mode: + conf_str += "%s" % self.transport_mode + if self.ssl_policy_name: + conf_str += "%s" % self.ssl_policy_name + if self.source_ip: + conf_str += "%s" % self.source_ip + if self.ip_type: + conf_str += CE_NC_DELETE_SERVER_IP_INFO_TAIL + elif self.server_domain: + conf_str += CE_NC_DELETE_SERVER_DNS_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge server loghost failed.') + + cmd = "undo info-center loghost" + if self.ip_type == "ipv4" and self.server_ip: + cmd += " %s" % self.server_ip + if self.ip_type == "ipv6" and self.server_ip: + cmd += " ipv6 %s" % self.server_ip + if self.server_domain: + cmd += " domain %s" % self.server_domain + if self.vrf_name: + if self.vrf_name != "_public_": + cmd += " vpn-instance %s" % self.vrf_name + self.updates_cmd.append(cmd) + self.changed = True + + def get_server_domain_dict(self): + """ get server domain attributes dict""" + + server_domain_info = dict() + # get server domain info + if not self.is_default_vpn: + self.is_default_vpn = False + if not self.vrf_name: + self.vrf_name = "_public_" + conf_str = CE_NC_GET_SERVER_DNS_INFO_HEADER + conf_str += CE_NC_GET_SERVER_DNS_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return server_domain_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + server_domain_info["serverAddressInfos"] = list() + syslog_dnss = root.findall("syslog/syslogDNSs/syslogDNS") + if syslog_dnss: + for syslog_dns in syslog_dnss: + dns_dict = dict() + for ele in syslog_dns: + if ele.tag in ["serverDomain", "vrfName", "level", "serverPort", "facility", "chnlId", + "chnlName", "timestamp", "transportMode", "sslPolicyName", "isDefaultVpn", + "sourceIP", "isBriefFmt"]: + dns_dict[ele.tag] = ele.text + server_domain_info["serverAddressInfos"].append(dns_dict) + + return server_domain_info + + def check_need_loghost_cfg(self): + """ check need cfg""" + + need_cfg = False + find_flag = False + if self.ip_type and self.server_ip: + if self.server_ip_info: + for tmp in self.server_ip_info["serverIpInfos"]: + find_flag = True + if self.ip_type and tmp.get("ipType") != self.ip_type: + find_flag = False + if self.server_ip and tmp.get("serverIp") != self.server_ip: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.level and tmp.get("level") != self.level: + find_flag = False + if self.server_port and tmp.get("serverPort") != self.server_port: + find_flag = False + if self.facility and tmp.get("facility") != self.facility: + find_flag = False + if self.channel_id and tmp.get("chnlId") != self.channel_id: + find_flag = False + if self.channel_name and tmp.get("chnlName") != self.channel_name: + find_flag = False + if self.timestamp and tmp.get("timestamp") != self.timestamp: + find_flag = False + if self.transport_mode and tmp.get("transportMode") != self.transport_mode: + find_flag = False + if self.ssl_policy_name and tmp.get("sslPolicyName") != self.ssl_policy_name: + find_flag = False + if self.source_ip and tmp.get("sourceIP") != self.source_ip: + find_flag = False + if find_flag: + break + elif self.server_domain: + if self.server_domain_info: + for tmp in self.server_domain_info["serverAddressInfos"]: + find_flag = True + if self.server_domain and tmp.get("serverDomain") != self.server_domain: + find_flag = False + if self.vrf_name and tmp.get("vrfName") != self.vrf_name: + find_flag = False + if self.level and tmp.get("level") != self.level: + find_flag = False + if self.server_port and tmp.get("serverPort") != self.server_port: + find_flag = False + if self.facility and tmp.get("facility") != self.facility: + find_flag = False + if self.channel_id and tmp.get("chnlId") != self.channel_id: + find_flag = False + if self.channel_name and tmp.get("chnlName") != self.channel_name: + find_flag = False + if self.timestamp and tmp.get("timestamp") != self.timestamp: + find_flag = False + if self.transport_mode and tmp.get("transportMode") != self.transport_mode: + find_flag = False + if self.ssl_policy_name and tmp.get("sslPolicyName") != self.ssl_policy_name: + find_flag = False + if self.source_ip and tmp.get("sourceIP") != self.source_ip: + find_flag = False + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + elif self.state == "absent": + need_cfg = bool(find_flag) + return need_cfg + + def get_syslog_global(self): + """get syslog global attributes""" + + cur_global_info = dict() + conf_str = CE_NC_GET_CENTER_GLOBAL_INFO_HEADER + if self.info_center_enable: + conf_str += "" + if self.packet_priority: + conf_str += "" + if self.suppress_enable: + conf_str += "" + conf_str += CE_NC_GET_CENTER_GLOBAL_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return cur_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "syslog/globalParam") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["icEnable", "packetPriority", "suppressEnable"]: + cur_global_info[site.tag] = site.text + return cur_global_info + + def merge_syslog_global(self): + """config global""" + + conf_str = CE_NC_MERGE_CENTER_GLOBAL_INFO_HEADER + if self.info_center_enable: + conf_str += "%s" % self.info_center_enable + if self.packet_priority: + if self.state == "present": + packet_priority = self.packet_priority + else: + packet_priority = 0 + conf_str += "%s" % packet_priority + if self.suppress_enable: + conf_str += "%s" % self.suppress_enable + + conf_str += CE_NC_MERGE_CENTER_GLOBAL_INFO_TAIL + + if self.info_center_enable == "true" and self.cur_global_info["icEnable"] != self.info_center_enable: + cmd = "info-center enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.suppress_enable == "true" and self.cur_global_info["suppressEnable"] != self.suppress_enable: + cmd = "info-center statistic-suppress enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.info_center_enable == "false" and self.cur_global_info["icEnable"] != self.info_center_enable: + cmd = "undo info-center enable" + self.updates_cmd.append(cmd) + self.changed = True + if self.suppress_enable == "false" and self.cur_global_info["suppressEnable"] != self.suppress_enable: + cmd = "undo info-center statistic-suppress enable" + self.updates_cmd.append(cmd) + self.changed = True + + if self.state == "present": + if self.packet_priority: + if self.cur_global_info["packetPriority"] != self.packet_priority: + cmd = "info-center syslog packet-priority %s" % self.packet_priority + self.updates_cmd.append(cmd) + self.changed = True + if self.state == "absent": + if self.packet_priority: + if self.cur_global_info["packetPriority"] == self.packet_priority: + cmd = "undo info-center syslog packet-priority %s" % self.packet_priority + self.updates_cmd.append(cmd) + self.changed = True + if self.changed: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge syslog global failed.') + + def get_syslog_logfile(self): + """get syslog logfile""" + + cur_logfile_info = dict() + conf_str = CE_NC_GET_LOG_FILE_INFO_HEADER + conf_str += "log" + if self.logfile_max_num: + conf_str += "" + if self.logfile_max_size: + conf_str += "" + conf_str += CE_NC_GET_LOG_FILE_INFO_TAIL + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return cur_logfile_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + logfile_info = root.findall( + "syslog/icLogFileInfos/icLogFileInfo") + if logfile_info: + for tmp in logfile_info: + for site in tmp: + if site.tag in ["maxFileNum", "maxFileSize"]: + cur_logfile_info[site.tag] = site.text + return cur_logfile_info + + def merge_syslog_logfile(self): + """config logfile""" + + logfile_max_num = "200" + conf_str = CE_NC_MERGE_LOG_FILE_INFO_HEADER + if self.logfile_max_num: + if self.state == "present": + logfile_max_num = self.logfile_max_num + else: + if self.logfile_max_num != "200" and self.cur_logfile_info["maxFileNum"] == self.logfile_max_num: + logfile_max_num = "200" + conf_str += "%s" % logfile_max_num + + if self.logfile_max_size: + logfile_max_size = "32" + if self.state == "present": + logfile_max_size = self.logfile_max_size + else: + if self.logfile_max_size != "32" and self.cur_logfile_info["maxFileSize"] == self.logfile_max_size: + logfile_max_size = "32" + conf_str += "%s" % logfile_max_size + + conf_str += "log" + conf_str += CE_NC_MERGE_LOG_FILE_INFO_TAIL + + if self.state == "present": + if self.logfile_max_num: + if self.cur_logfile_info["maxFileNum"] != self.logfile_max_num: + cmd = "info-center max-logfile-number %s" % self.logfile_max_num + self.updates_cmd.append(cmd) + self.changed = True + if self.logfile_max_size: + if self.cur_logfile_info["maxFileSize"] != self.logfile_max_size: + cmd = "info-center logfile size %s" % self.logfile_max_size + self.updates_cmd.append(cmd) + self.changed = True + if self.state == "absent": + if self.logfile_max_num and self.logfile_max_num != "200": + if self.cur_logfile_info["maxFileNum"] == self.logfile_max_num: + cmd = "undo info-center max-logfile-number" + self.updates_cmd.append(cmd) + self.changed = True + if self.logfile_max_size and self.logfile_max_size != "32": + if self.cur_logfile_info["maxFileSize"] == self.logfile_max_size: + cmd = "undo info-center logfile size" + self.updates_cmd.append(cmd) + self.changed = True + + if self.changed: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge syslog logfile failed.') + + def check_params(self): + """Check all input params""" + + # packet_priority check + if self.packet_priority: + if not self.packet_priority.isdigit(): + self.module.fail_json( + msg='Error: The parameter of packet priority is invalid.') + if int(self.packet_priority) > 7 or int(self.packet_priority) < 0: + self.module.fail_json( + msg='Error: The packet priority must be an integer between 0 and 7.') + + # logfile_max_num check + if self.logfile_max_num: + if not self.logfile_max_num.isdigit(): + self.module.fail_json( + msg='Error: The parameter of logfile_max_num is invalid.') + if int(self.logfile_max_num) > 500 or int(self.logfile_max_num) < 3: + self.module.fail_json( + msg='Error: The logfile_max_num must be an integer between 3 and 500.') + + # channel_id check + if self.channel_id: + if not self.channel_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of channel_id is invalid.') + if int(self.channel_id) > 9 or int(self.channel_id) < 0: + self.module.fail_json( + msg='Error: The channel_id must be an integer between 0 and 9.') + + # channel_cfg_name check + if self.channel_cfg_name: + if len(self.channel_cfg_name) > 30 \ + or len(self.channel_cfg_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: channel_cfg_name is not in the range from 1 to 30.') + + # filter_feature_name check + if self.filter_feature_name: + if len(self.filter_feature_name) > 31 \ + or len(self.filter_feature_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: filter_feature_name is not in the range from 1 to 31.') + + # filter_log_name check + if self.filter_log_name: + if len(self.filter_log_name) > 63 \ + or len(self.filter_log_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: filter_log_name is not in the range from 1 to 63.') + + # server_ip check + if self.server_ip: + if not check_ip_addr(self.server_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address' % self.server_ip) + # source_ip check + if self.source_ip: + if not check_ip_addr(self.source_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address' % self.source_ip) + + # server_domain check + if self.server_domain: + if len(self.server_domain) > 255 \ + or len(self.server_domain.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: server_domain is not in the range from 1 to 255.') + + # vrf_name check + if self.vrf_name: + if len(self.vrf_name) > 31 \ + or len(self.vrf_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: vrf_name is not in the range from 1 to 31.') + + # server_port check + if self.server_port: + if not self.server_port.isdigit(): + self.module.fail_json( + msg='Error: The parameter of server_port is invalid.') + if int(self.server_port) > 65535 or int(self.server_port) < 1: + self.module.fail_json( + msg='Error: The server_port must be an integer between 1 and 65535.') + + # channel_name check + if self.channel_name: + if len(self.channel_name) > 31 \ + or len(self.channel_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: channel_name is not in the range from 1 to 30.') + + # ssl_policy_name check + if self.ssl_policy_name: + if len(self.ssl_policy_name) > 23 \ + or len(self.ssl_policy_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: ssl_policy_name is not in the range from 1 to 23.') + + def get_proposed(self): + """get proposed info""" + + if self.info_center_enable: + self.proposed["info_center_enable"] = self.info_center_enable + if self.packet_priority: + self.proposed["packet_priority"] = self.packet_priority + if self.suppress_enable: + self.proposed["suppress_enable"] = self.suppress_enable + if self.logfile_max_num: + self.proposed["logfile_max_num"] = self.logfile_max_num + if self.logfile_max_size: + self.proposed["logfile_max_size"] = self.logfile_max_size + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.channel_cfg_name: + self.proposed["channel_cfg_name"] = self.channel_cfg_name + if self.channel_out_direct: + self.proposed["channel_out_direct"] = self.channel_out_direct + if self.filter_feature_name: + self.proposed["filter_feature_name"] = self.filter_feature_name + if self.filter_log_name: + self.proposed["filter_log_name"] = self.filter_log_name + if self.ip_type: + self.proposed["ip_type"] = self.ip_type + if self.server_ip: + self.proposed["server_ip"] = self.server_ip + if self.server_domain: + self.proposed["server_domain"] = self.server_domain + if self.vrf_name: + self.proposed["vrf_name"] = self.vrf_name + if self.level: + self.proposed["level"] = self.level + if self.server_port: + self.proposed["server_port"] = self.server_port + if self.facility: + self.proposed["facility"] = self.facility + if self.channel_name: + self.proposed["channel_name"] = self.channel_name + if self.timestamp: + self.proposed["timestamp"] = self.timestamp + if self.ssl_policy_name: + self.proposed["ssl_policy_name"] = self.ssl_policy_name + if self.transport_mode: + self.proposed["transport_mode"] = self.transport_mode + if self.is_default_vpn: + self.proposed["is_default_vpn"] = self.is_default_vpn + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.info_center_enable: + self.existing["info_center_enable"] = self.cur_global_info[ + "icEnable"] + if self.packet_priority: + self.existing["packet_priority"] = self.cur_global_info[ + "packetPriority"] + if self.suppress_enable: + self.existing["suppress_enable"] = self.cur_global_info[ + "suppressEnable"] + if self.logfile_max_num: + self.existing["logfile_max_num"] = self.cur_logfile_info[ + "maxFileNum"] + if self.logfile_max_size: + self.existing["logfile_max_size"] = self.cur_logfile_info[ + "maxFileSize"] + + if self.channel_id and self.channel_cfg_name: + if self.channel_info: + self.existing["channel_id_info"] = self.channel_info[ + "channelInfos"] + if self.channel_out_direct and self.channel_id: + if self.channel_direct_info: + self.existing["channel_out_direct_info"] = self.channel_direct_info[ + "channelDirectInfos"] + if self.filter_feature_name and self.filter_log_name: + if self.filter_info: + self.existing["filter_id_info"] = self.filter_info[ + "filterInfos"] + if self.ip_type: + if self.server_ip_info: + self.existing["server_ip_info"] = self.server_ip_info[ + "serverIpInfos"] + + if self.server_domain: + if self.server_domain_info: + self.existing["server_domain_info"] = self.server_domain_info[ + "serverAddressInfos"] + + def get_end_state(self): + """get end state info""" + + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.cur_global_info = self.get_syslog_global() + if self.logfile_max_num or self.logfile_max_size: + self.cur_logfile_info = self.get_syslog_logfile() + if self.channel_id and self.channel_cfg_name: + self.channel_info = self.get_channel_dict() + if self.channel_out_direct and self.channel_id: + self.channel_direct_info = self.get_channel_direct_dict() + if self.filter_feature_name and self.filter_log_name: + self.filter_info = self.get_filter_dict() + if self.ip_type: + self.server_ip_info = self.get_server_ip_dict() + if self.server_domain: + self.server_domain_info = self.get_server_domain_dict() + + if self.info_center_enable: + self.end_state[ + "info_center_enable"] = self.cur_global_info["icEnable"] + if self.packet_priority: + self.end_state["packet_priority"] = self.cur_global_info[ + "packetPriority"] + if self.suppress_enable: + self.end_state["suppress_enable"] = self.cur_global_info[ + "suppressEnable"] + if self.logfile_max_num: + self.end_state["logfile_max_num"] = self.cur_logfile_info[ + "maxFileNum"] + if self.logfile_max_size: + self.end_state["logfile_max_size"] = self.cur_logfile_info[ + "maxFileSize"] + + if self.channel_id and self.channel_cfg_name: + if self.channel_info: + self.end_state["channel_id_info"] = self.channel_info[ + "channelInfos"] + + if self.channel_out_direct and self.channel_id: + if self.channel_direct_info: + self.end_state["channel_out_direct_info"] = self.channel_direct_info[ + "channelDirectInfos"] + + if self.filter_feature_name and self.filter_log_name: + if self.filter_info: + self.end_state["filter_id_info"] = self.filter_info[ + "filterInfos"] + + if self.ip_type: + if self.server_ip_info: + self.end_state["server_ip_info"] = self.server_ip_info[ + "serverIpInfos"] + + if self.server_domain: + if self.server_domain_info: + self.end_state["server_domain_info"] = self.server_domain_info[ + "serverAddressInfos"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.cur_global_info = self.get_syslog_global() + if self.logfile_max_num or self.logfile_max_size: + self.cur_logfile_info = self.get_syslog_logfile() + if self.channel_id: + self.channel_info = self.get_channel_dict() + if self.channel_out_direct: + self.channel_direct_info = self.get_channel_direct_dict() + if self.filter_feature_name and self.filter_log_name: + self.filter_info = self.get_filter_dict() + if self.ip_type: + self.server_ip_info = self.get_server_ip_dict() + if self.server_domain: + self.server_domain_info = self.get_server_domain_dict() + self.get_existing() + self.get_proposed() + if self.info_center_enable or self.packet_priority or self.suppress_enable: + self.merge_syslog_global() + + if self.logfile_max_num or self.logfile_max_size: + self.merge_syslog_logfile() + + if self.server_ip: + if not self.ip_type: + self.module.fail_json( + msg='Error: ip_type and server_ip must be exist at the same time.') + if self.ip_type: + if not self.server_ip: + self.module.fail_json( + msg='Error: ip_type and server_ip must be exist at the same time.') + + if self.ip_type or self.server_domain or self.channel_id or self.filter_feature_name: + if self.ip_type and self.server_domain: + self.module.fail_json( + msg='Error: ip_type and server_domain can not be exist at the same time.') + if self.channel_id and self.channel_name: + self.module.fail_json( + msg='Error: channel_id and channel_name can not be exist at the same time.') + if self.ssl_policy_name: + if self.transport_mode == "udp": + self.module.fail_json( + msg='Error: transport_mode: udp does not support ssl_policy.') + if not self.transport_mode: + self.module.fail_json( + msg='Error: transport_mode, ssl_policy_name must be exist at the same time.') + if self.ip_type == "ipv6": + if self.vrf_name and self.vrf_name != "_public_": + self.module.fail_json( + msg='Error: ipType:ipv6 only support default vpn:_public_.') + if self.is_default_vpn is True: + if self.vrf_name: + if self.vrf_name != "_public_": + self.module.fail_json( + msg='Error: vrf_name should be _public_ when is_default_vpn is True.') + else: + self.vrf_name = "_public_" + else: + if self.vrf_name == "_public_": + self.module.fail_json( + msg='Error: The default vpn value is _public_, but is_default_vpn is False.') + if self.state == "present": + # info-center channel channel-number name channel-name + if self.channel_id and self.channel_cfg_name: + self.config_merge_syslog_channel( + self.channel_id, self.channel_cfg_name) + # info-center { console | logfile | monitor | snmp | logbuffer + # | trapbuffer } channel channel-number + if self.channel_out_direct and self.channel_id: + self.config_merge_out_direct( + self.channel_out_direct, self.channel_id) + # info-center filter-id bymodule-alias modname alias + if self.filter_feature_name and self.filter_log_name: + self.config_merge_filter( + self.filter_feature_name, self.filter_log_name) + if self.ip_type and self.server_ip: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.config_merge_loghost() + if self.server_domain: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.config_merge_loghost() + + elif self.state == "absent": + if self.channel_id: + self.delete_merge_syslog_channel( + self.channel_id, self.channel_cfg_name) + if self.channel_out_direct: + self.delete_merge_out_direct( + self.channel_out_direct, self.channel_id) + if self.filter_feature_name and self.filter_log_name: + self.delete_merge_filter( + self.filter_feature_name, self.filter_log_name) + if self.ip_type and self.server_ip: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.delete_merge_loghost() + if self.server_domain: + if not self.vrf_name: + self.vrf_name = "_public_" + if self.check_need_loghost_cfg(): + self.delete_merge_loghost() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + info_center_enable=dict(choices=['true', 'false']), + packet_priority=dict(type='str'), + suppress_enable=dict(choices=['true', 'false']), + logfile_max_num=dict(type='str'), + logfile_max_size=dict(choices=['4', '8', '16', '32']), + channel_id=dict(type='str'), + channel_cfg_name=dict(type='str'), + channel_out_direct=dict(choices=['console', 'monitor', + 'trapbuffer', 'logbuffer', 'snmp', 'logfile']), + filter_feature_name=dict(type='str'), + filter_log_name=dict(type='str'), + ip_type=dict(choices=['ipv4', 'ipv6']), + server_ip=dict(type='str'), + server_domain=dict(type='str'), + is_default_vpn=dict(default=False, type='bool'), + vrf_name=dict(type='str'), + level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']), + server_port=dict(type='str'), + facility=dict(choices=['local0', 'local1', 'local2', + 'local3', 'local4', 'local5', 'local6', 'local7']), + channel_name=dict(type='str'), + timestamp=dict(choices=['UTC', 'localtime']), + transport_mode=dict(choices=['tcp', 'udp']), + ssl_policy_name=dict(type='str'), + source_ip=dict(type='str'), + state=dict(choices=['present', 'absent'], default='present') + + ) + argument_spec.update(ce_argument_spec) + module = InfoCenterGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_log.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_log.py new file mode 100644 index 00000000..9bbdf2c9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_log.py @@ -0,0 +1,544 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_log +short_description: Manages information center log configuration on HUAWEI CloudEngine switches. +description: + - Setting the Timestamp Format of Logs. + Configuring the Device to Output Logs to the Log Buffer. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + log_time_stamp: + description: + - Sets the timestamp format of logs. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', + 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', + 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond'] + log_buff_enable: + description: + - Enables the Switch to send logs to the log buffer. + default: no_use + choices: ['no_use','true', 'false'] + log_buff_size: + description: + - Specifies the maximum number of logs in the log buffer. + The value is an integer that ranges from 0 to 10240. If logbuffer-size is 0, logs are not displayed. + module_name: + description: + - Specifies the name of a module. + The value is a module name in registration logs. + channel_id: + description: + - Specifies a channel ID. + The value is an integer ranging from 0 to 9. + log_enable: + description: + - Indicates whether log filtering is enabled. + default: no_use + choices: ['no_use','true', 'false'] + log_level: + description: + - Specifies a log severity. + choices: ['emergencies', 'alert', 'critical', 'error', + 'warning', 'notification', 'informational', 'debugging'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center log test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Setting the timestamp format of logs" + community.network.ce_info_center_log: + log_time_stamp: date_tenthsecond + provider: "{{ cli }}" + + - name: "Enabled to output information to the log buffer" + community.network.ce_info_center_log: + log_buff_enable: true + provider: "{{ cli }}" + + - name: "Set the maximum number of logs in the log buffer" + community.network.ce_info_center_log: + log_buff_size: 100 + provider: "{{ cli }}" + + - name: "Set a rule for outputting logs to a channel" + community.network.ce_info_center_log: + module_name: aaa + channel_id: 1 + log_enable: true + log_level: critical + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_tenthsecond", "state": "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_second"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"log_time_stamp": "date_tenthsecond"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["info-center timestamp log date precision-time tenth-second"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_LOG = """ + + + + + + + + + + %s + %s + + + + + + + +""" + +CE_NC_GET_LOG_GLOBAL = """ + + + + + + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_LOG_STATE = {"0": "true", + "1": "true", + "2": "true", + "3": "false", + "4": "true", + "5": "false", + "6": "true", + "7": "true", + "8": "true", + "9": "true"} + +CHANNEL_DEFAULT_LOG_LEVEL = {"0": "warning", + "1": "warning", + "2": "informational", + "3": "informational", + "4": "warning", + "5": "debugging", + "6": "debugging", + "7": "warning", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterLog(object): + """ + Manages information center log configuration + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.log_time_stamp = self.module.params['log_time_stamp'] + self.log_buff_enable = self.module.params['log_buff_enable'] + self.log_buff_size = self.module.params['log_buff_size'] + self.module_name = self.module.params['module_name'] + self.channel_id = self.module.params['channel_id'] + self.log_enable = self.module.params['log_enable'] + self.log_level = self.module.params['log_level'] + self.state = self.module.params['state'] + + # state + self.log_dict = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_log_dict(self): + """ log config dict""" + + log_dict = dict() + if self.module_name: + if self.module_name.lower() == "default": + conf_str = CE_NC_GET_LOG % (self.module_name.lower(), self.channel_id) + else: + conf_str = CE_NC_GET_LOG % (self.module_name.upper(), self.channel_id) + else: + conf_str = CE_NC_GET_LOG_GLOBAL + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return log_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get global param info + glb = root.find("syslog/globalParam") + if glb: + for attr in glb: + if attr.tag in ["bufferSize", "logTimeStamp", "icLogBuffEn"]: + log_dict[attr.tag] = attr.text + + # get info-center source info + log_dict["source"] = dict() + src = root.find("syslog/icSources/icSource") + if src: + for attr in src: + if attr.tag in ["moduleName", "icChannelId", "icChannelName", "logEnFlg", "logEnLevel"]: + log_dict["source"][attr.tag] = attr.text + + return log_dict + + def config_log_global(self): + """config log global param""" + + xml_str = '' + if self.log_time_stamp: + if self.state == "present" and self.log_time_stamp.upper() != self.log_dict.get("logTimeStamp"): + xml_str += '%s' % self.log_time_stamp.upper() + self.updates_cmd.append( + "info-center timestamp log %s" % TIME_STAMP_DICT.get(self.log_time_stamp)) + elif self.state == "absent" and self.log_time_stamp.upper() == self.log_dict.get("logTimeStamp"): + xml_str += 'DATE_SECOND' # set default + self.updates_cmd.append("undo info-center timestamp log") + else: + pass + + if self.log_buff_enable != 'no_use': + if self.log_dict.get("icLogBuffEn") != self.log_buff_enable: + xml_str += '%s' % self.log_buff_enable + if self.log_buff_enable == "true": + self.updates_cmd.append("info-center logbuffer") + else: + self.updates_cmd.append("undo info-center logbuffer") + + if self.log_buff_size: + if self.state == "present" and self.log_dict.get("bufferSize") != self.log_buff_size: + xml_str += '%s' % self.log_buff_size + self.updates_cmd.append( + "info-center logbuffer size %s" % self.log_buff_size) + elif self.state == "absent" and self.log_dict.get("bufferSize") == self.log_buff_size: + xml_str += '512' + self.updates_cmd.append("undo info-center logbuffer size") + + if xml_str == '': + return "" + else: + xml_str += '' + return xml_str + + def config_log_soruce(self): + """config info-center sources""" + + xml_str = '' + if not self.module_name or not self.channel_id: + return xml_str + + source = self.log_dict["source"] + if self.state == "present": + xml_str = '' + cmd = 'info-center source %s channel %s log' % ( + self.module_name, self.channel_id) + else: + if not source or self.module_name != source.get("moduleName").lower() or \ + self.channel_id != source.get("icChannelId"): + return '' + + if self.log_enable == 'no_use' and not self.log_level: + xml_str = '' + else: + xml_str = '' + cmd = 'undo info-center source %s channel %s log' % ( + self.module_name, self.channel_id) + + xml_str += '%s%s' % ( + self.module_name, self.channel_id) + + # log_enable + if self.log_enable != 'no_use': + if self.state == "present" and (not source or self.log_enable != source.get("logEnFlg")): + xml_str += '%s' % self.log_enable + if self.log_enable == "true": + cmd += ' state on' + else: + cmd += ' state off' + elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): + xml_str += '%s' % CHANNEL_DEFAULT_LOG_STATE.get(self.channel_id) + cmd += ' state' + + # log_level + if self.log_level: + if self.state == "present" and (not source or self.log_level != source.get("logEnLevel")): + xml_str += '%s' % self.log_level + cmd += ' level %s' % self.log_level + elif self.state == "absent" and source and self.log_level == source.get("logEnLevel"): + xml_str += '%s' % CHANNEL_DEFAULT_LOG_LEVEL.get(self.channel_id) + cmd += ' level' + + if xml_str.endswith(""): + if self.log_enable == 'no_use' and not self.log_level and self.state == "absent": + xml_str += '' + self.updates_cmd.append(cmd) + return xml_str + else: + return '' + else: + xml_str += '' + self.updates_cmd.append(cmd) + return xml_str + + def netconf_load_config(self, xml_str): + """load log config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + recv_xml = set_nc_config(self.module, xml_cfg) + self.check_response(recv_xml, "SET_LOG") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check log_buff_size ranges from 0 to 10240 + if self.log_buff_size: + if not self.log_buff_size.isdigit(): + self.module.fail_json( + msg="Error: log_buff_size is not digit.") + if int(self.log_buff_size) < 0 or int(self.log_buff_size) > 10240: + self.module.fail_json( + msg="Error: log_buff_size is not ranges from 0 to 10240.") + + # check channel_id ranging from 0 to 9 + if self.channel_id: + if not self.channel_id.isdigit(): + self.module.fail_json(msg="Error: channel_id is not digit.") + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg="Error: channel_id is not ranges from 0 to 9.") + + # module_name and channel_id must be set at the same time + if bool(self.module_name) != bool(self.channel_id): + self.module.fail_json( + msg="Error: module_name and channel_id must be set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.log_time_stamp: + self.proposed["log_time_stamp"] = self.log_time_stamp + if self.log_buff_enable != 'no_use': + self.proposed["log_buff_enable"] = self.log_buff_enable + if self.log_buff_size: + self.proposed["log_buff_size"] = self.log_buff_size + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.log_enable != 'no_use': + self.proposed["log_enable"] = self.log_enable + if self.log_level: + self.proposed["log_level"] = self.log_level + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.log_dict: + return + + if self.log_time_stamp: + self.existing["log_time_stamp"] = self.log_dict.get("logTimeStamp").lower() + if self.log_buff_enable != 'no_use': + self.existing["log_buff_enable"] = self.log_dict.get("icLogBuffEn") + if self.log_buff_size: + self.existing["log_buff_size"] = self.log_dict.get("bufferSize") + if self.module_name: + self.existing["source"] = self.log_dict.get("source") + + def get_end_state(self): + """get end state info""" + + log_dict = self.get_log_dict() + if not log_dict: + return + + if self.log_time_stamp: + self.end_state["log_time_stamp"] = log_dict.get("logTimeStamp").lower() + if self.log_buff_enable != 'no_use': + self.end_state["log_buff_enable"] = log_dict.get("icLogBuffEn") + if self.log_buff_size: + self.end_state["log_buff_size"] = log_dict.get("bufferSize") + if self.module_name: + self.end_state["source"] = log_dict.get("source") + + def work(self): + """worker""" + + self.check_params() + self.log_dict = self.get_log_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.log_time_stamp or self.log_buff_enable != 'no_use' or self.log_buff_size: + xml_str += self.config_log_global() + + if self.module_name: + xml_str += self.config_log_soruce() + + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + log_time_stamp=dict(required=False, type='str', + choices=['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', + 'shortdate_second', 'shortdate_tenthsecond', 'shortdate_millisecond', + 'formatdate_second', 'formatdate_tenthsecond', 'formatdate_millisecond']), + log_buff_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + log_buff_size=dict(required=False, type='str'), + module_name=dict(required=False, type='str'), + channel_id=dict(required=False, type='str'), + log_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + log_level=dict(required=False, type='str', + choices=['emergencies', 'alert', 'critical', 'error', + 'warning', 'notification', 'informational', 'debugging']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterLog(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_trap.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_trap.py new file mode 100644 index 00000000..165ef23b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_info_center_trap.py @@ -0,0 +1,693 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_info_center_trap +short_description: Manages information center trap configuration on HUAWEI CloudEngine switches. +description: + - Manages information center trap configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + trap_time_stamp: + description: + - Timestamp format of alarm information. + choices: ['date_boot', 'date_second', 'date_tenthsecond', 'date_millisecond', 'shortdate_second', + 'shortdate_tenthsecond', 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond'] + trap_buff_enable: + description: + - Whether a trap buffer is enabled to output information. + default: no_use + choices: ['no_use','true','false'] + trap_buff_size: + description: + - Size of a trap buffer. + The value is an integer ranging from 0 to 1024. The default value is 256. + module_name: + description: + - Module name of the rule. + The value is a string of 1 to 31 case-insensitive characters. The default value is default. + Please use lower-case letter, such as [aaa, acl, arp, bfd]. + channel_id: + description: + - Number of a channel. + The value is an integer ranging from 0 to 9. The default value is 0. + trap_enable: + description: + - Whether a device is enabled to output alarms. + default: no_use + choices: ['no_use','true','false'] + trap_level: + description: + - Trap level permitted to output. + choices: ['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging'] +''' + +EXAMPLES = ''' + +- name: CloudEngine info center trap test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config trap buffer" + community.network.ce_info_center_trap: + state: present + trap_buff_enable: true + trap_buff_size: 768 + provider: "{{ cli }}" + + - name: "Undo trap buffer" + community.network.ce_info_center_trap: + state: absent + trap_buff_enable: true + trap_buff_size: 768 + provider: "{{ cli }}" + + - name: "Config trap module log level" + community.network.ce_info_center_trap: + state: present + module_name: aaa + channel_id: 1 + trap_enable: true + trap_level: error + provider: "{{ cli }}" + + - name: "Undo trap module log level" + community.network.ce_info_center_trap: + state: absent + module_name: aaa + channel_id: 1 + trap_enable: true + trap_level: error + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"state": "present", "trap_buff_enable": "true", "trap_buff_size": "768"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"icTrapBuffEn": "false", "trapBuffSize": "256"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"icTrapBuffEn": "true", "trapBuffSize": "768"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["info-center trapbuffer", "info-center trapbuffer size 768"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get info center trap global +CE_GET_TRAP_GLOBAL_HEADER = """ + + + +""" +CE_GET_TRAP_GLOBAL_TAIL = """ + + + +""" +# merge info center trap global +CE_MERGE_TRAP_GLOBAL_HEADER = """ + + + +""" +CE_MERGE_TRAP_GLOBAL_TAIL = """ + + + +""" + +# get info center trap source +CE_GET_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_GET_TRAP_SOURCE_TAIL = """ + + + + +""" +# merge info center trap source +CE_MERGE_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_MERGE_TRAP_SOURCE_TAIL = """ + + + + +""" +# delete info center trap source +CE_DELETE_TRAP_SOURCE_HEADER = """ + + + + +""" +CE_DELETE_TRAP_SOURCE_TAIL = """ + + + + +""" + +TIME_STAMP_DICT = {"date_boot": "boot", + "date_second": "date precision-time second", + "date_tenthsecond": "date precision-time tenth-second", + "date_millisecond": "date precision-time millisecond", + "shortdate_second": "short-date precision-time second", + "shortdate_tenthsecond": "short-date precision-time tenth-second", + "shortdate_millisecond": "short-date precision-time millisecond", + "formatdate_second": "format-date precision-time second", + "formatdate_tenthsecond": "format-date precision-time tenth-second", + "formatdate_millisecond": "format-date precision-time millisecond"} + +CHANNEL_DEFAULT_TRAP_STATE = {"0": "true", + "1": "true", + "2": "true", + "3": "true", + "4": "false", + "5": "true", + "6": "true", + "7": "true", + "8": "true", + "9": "true"} + +CHANNEL_DEFAULT_TRAP_LEVEL = {"0": "debugging", + "1": "debugging", + "2": "debugging", + "3": "debugging", + "4": "debugging", + "5": "debugging", + "6": "debugging", + "7": "debugging", + "8": "debugging", + "9": "debugging"} + + +class InfoCenterTrap(object): + """ Manages info center trap configuration """ + + def __init__(self, **kwargs): + """ Init function """ + + # argument spec + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # module args + self.state = self.module.params['state'] + self.trap_time_stamp = self.module.params['trap_time_stamp'] or None + self.trap_buff_enable = self.module.params['trap_buff_enable'] + self.trap_buff_size = self.module.params['trap_buff_size'] or None + self.module_name = self.module.params['module_name'] or None + self.channel_id = self.module.params['channel_id'] or None + self.trap_enable = self.module.params['trap_enable'] + self.trap_level = self.module.params['trap_level'] or None + + # cur config + self.cur_global_cfg = dict() + self.cur_source_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Netconf get config """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Netconf set config """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def check_global_args(self): + """ Check global args """ + + need_cfg = False + find_flag = False + self.cur_global_cfg["global_cfg"] = [] + + if self.trap_time_stamp or self.trap_buff_enable != 'no_use' or self.trap_buff_size: + if self.trap_buff_size: + if self.trap_buff_size.isdigit(): + if int(self.trap_buff_size) < 0 or int(self.trap_buff_size) > 1024: + self.module.fail_json( + msg='Error: The value of trap_buff_size is out of [0 - 1024].') + else: + self.module.fail_json( + msg='Error: The trap_buff_size is not digit.') + + conf_str = CE_GET_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "" + if self.trap_buff_enable != 'no_use': + conf_str += "" + if self.trap_buff_size: + conf_str += "" + + conf_str += CE_GET_TRAP_GLOBAL_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_cfg = root.findall("syslog/globalParam") + if global_cfg: + for tmp in global_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["trapTimeStamp", "icTrapBuffEn", "trapBuffSize"]: + tmp_dict[site.tag] = site.text + + self.cur_global_cfg["global_cfg"].append(tmp_dict) + + if self.cur_global_cfg["global_cfg"]: + for tmp in self.cur_global_cfg["global_cfg"]: + find_flag = True + + if self.trap_time_stamp and tmp.get("trapTimeStamp").lower() != self.trap_time_stamp: + find_flag = False + if self.trap_buff_enable != 'no_use' and tmp.get("icTrapBuffEn") != self.trap_buff_enable: + find_flag = False + if self.trap_buff_size and tmp.get("trapBuffSize") != self.trap_buff_size: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_global_cfg["need_cfg"] = need_cfg + + def check_source_args(self): + """ Check source args """ + + need_cfg = False + find_flag = False + self.cur_source_cfg["source_cfg"] = list() + + if self.module_name: + if len(self.module_name) < 1 or len(self.module_name) > 31: + self.module.fail_json( + msg='Error: The module_name is out of [1 - 31].') + + if not self.channel_id: + self.module.fail_json( + msg='Error: Please input channel_id at the same time.') + + if self.channel_id: + if self.channel_id.isdigit(): + if int(self.channel_id) < 0 or int(self.channel_id) > 9: + self.module.fail_json( + msg='Error: The value of channel_id is out of [0 - 9].') + else: + self.module.fail_json( + msg='Error: The channel_id is not digit.') + + conf_str = CE_GET_TRAP_SOURCE_HEADER + + if self.module_name != "default": + conf_str += "%s" % self.module_name.upper() + else: + conf_str += "default" + + if self.channel_id: + conf_str += "" + if self.trap_enable != 'no_use': + conf_str += "" + if self.trap_level: + conf_str += "" + + conf_str += CE_GET_TRAP_SOURCE_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + find_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + source_cfg = root.findall("syslog/icSources/icSource") + if source_cfg: + for tmp in source_cfg: + tmp_dict = dict() + for site in tmp: + if site.tag in ["moduleName", "icChannelId", "trapEnFlg", "trapEnLevel"]: + tmp_dict[site.tag] = site.text + + self.cur_source_cfg["source_cfg"].append(tmp_dict) + + if self.cur_source_cfg["source_cfg"]: + for tmp in self.cur_source_cfg["source_cfg"]: + find_flag = True + + if self.module_name and tmp.get("moduleName").lower() != self.module_name.lower(): + find_flag = False + if self.channel_id and tmp.get("icChannelId") != self.channel_id: + find_flag = False + if self.trap_enable != 'no_use' and tmp.get("trapEnFlg") != self.trap_enable: + find_flag = False + if self.trap_level and tmp.get("trapEnLevel") != self.trap_level: + find_flag = False + + if find_flag: + break + else: + find_flag = False + + if self.state == "present": + need_cfg = bool(not find_flag) + else: + need_cfg = bool(find_flag) + + self.cur_source_cfg["need_cfg"] = need_cfg + + def get_proposed(self): + """ Get proposed """ + + self.proposed["state"] = self.state + + if self.trap_time_stamp: + self.proposed["trap_time_stamp"] = self.trap_time_stamp + if self.trap_buff_enable != 'no_use': + self.proposed["trap_buff_enable"] = self.trap_buff_enable + if self.trap_buff_size: + self.proposed["trap_buff_size"] = self.trap_buff_size + if self.module_name: + self.proposed["module_name"] = self.module_name + if self.channel_id: + self.proposed["channel_id"] = self.channel_id + if self.trap_enable != 'no_use': + self.proposed["trap_enable"] = self.trap_enable + if self.trap_level: + self.proposed["trap_level"] = self.trap_level + + def get_existing(self): + """ Get existing """ + + if self.cur_global_cfg["global_cfg"]: + self.existing["global_cfg"] = self.cur_global_cfg["global_cfg"] + if self.cur_source_cfg["source_cfg"]: + self.existing["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def get_end_state(self): + """ Get end state """ + + self.check_global_args() + if self.cur_global_cfg["global_cfg"]: + self.end_state["global_cfg"] = self.cur_global_cfg["global_cfg"] + + self.check_source_args() + if self.cur_source_cfg["source_cfg"]: + self.end_state["source_cfg"] = self.cur_source_cfg["source_cfg"] + + def merge_trap_global(self): + """ Merge trap global """ + + conf_str = CE_MERGE_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "%s" % self.trap_time_stamp.upper() + if self.trap_buff_enable != 'no_use': + conf_str += "%s" % self.trap_buff_enable + if self.trap_buff_size: + conf_str += "%s" % self.trap_buff_size + + conf_str += CE_MERGE_TRAP_GLOBAL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge trap global failed.') + + if self.trap_time_stamp: + cmd = "info-center timestamp trap " + TIME_STAMP_DICT.get(self.trap_time_stamp) + self.updates_cmd.append(cmd) + if self.trap_buff_enable != 'no_use': + if self.trap_buff_enable == "true": + cmd = "info-center trapbuffer" + else: + cmd = "undo info-center trapbuffer" + self.updates_cmd.append(cmd) + if self.trap_buff_size: + cmd = "info-center trapbuffer size %s" % self.trap_buff_size + self.updates_cmd.append(cmd) + + self.changed = True + + def delete_trap_global(self): + """ Delete trap global """ + + conf_str = CE_MERGE_TRAP_GLOBAL_HEADER + + if self.trap_time_stamp: + conf_str += "DATE_SECOND" + if self.trap_buff_enable != 'no_use': + conf_str += "false" + if self.trap_buff_size: + conf_str += "256" + + conf_str += CE_MERGE_TRAP_GLOBAL_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: delete trap global failed.') + + if self.trap_time_stamp: + cmd = "undo info-center timestamp trap" + self.updates_cmd.append(cmd) + if self.trap_buff_enable != 'no_use': + cmd = "undo info-center trapbuffer" + self.updates_cmd.append(cmd) + if self.trap_buff_size: + cmd = "undo info-center trapbuffer size" + self.updates_cmd.append(cmd) + + self.changed = True + + def merge_trap_source(self): + """ Merge trap source """ + + conf_str = CE_MERGE_TRAP_SOURCE_HEADER + + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.trap_enable != 'no_use': + conf_str += "%s" % self.trap_enable + if self.trap_level: + conf_str += "%s" % self.trap_level + + conf_str += CE_MERGE_TRAP_SOURCE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge trap source failed.') + + cmd = "info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.trap_enable != 'no_use': + if self.trap_enable == "true": + cmd += " trap state on" + else: + cmd += " trap state off" + if self.trap_level: + cmd += " level %s" % self.trap_level + + self.updates_cmd.append(cmd) + self.changed = True + + def delete_trap_source(self): + """ Delete trap source """ + + if self.trap_enable == 'no_use' and not self.trap_level: + conf_str = CE_DELETE_TRAP_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + conf_str += CE_DELETE_TRAP_SOURCE_TAIL + else: + conf_str = CE_MERGE_TRAP_SOURCE_HEADER + if self.module_name: + conf_str += "%s" % self.module_name + if self.channel_id: + conf_str += "%s" % self.channel_id + if self.trap_enable != 'no_use': + conf_str += "%s" % CHANNEL_DEFAULT_TRAP_STATE.get(self.channel_id) + if self.trap_level: + conf_str += "%s" % CHANNEL_DEFAULT_TRAP_LEVEL.get(self.channel_id) + conf_str += CE_MERGE_TRAP_SOURCE_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete trap source failed.') + + cmd = "undo info-center source" + if self.module_name: + cmd += " %s" % self.module_name + if self.channel_id: + cmd += " channel %s" % self.channel_id + if self.trap_enable != 'no_use': + cmd += " trap state" + if self.trap_level: + cmd += " level" + + self.updates_cmd.append(cmd) + self.changed = True + + def work(self): + """ work function """ + + self.check_global_args() + self.check_source_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.cur_global_cfg["need_cfg"]: + self.merge_trap_global() + if self.cur_source_cfg["need_cfg"]: + self.merge_trap_source() + + else: + if self.cur_global_cfg["need_cfg"]: + self.delete_trap_global() + if self.cur_source_cfg["need_cfg"]: + self.delete_trap_source() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + trap_time_stamp=dict(choices=['date_boot', 'date_second', 'date_tenthsecond', + 'date_millisecond', 'shortdate_second', 'shortdate_tenthsecond', + 'shortdate_millisecond', 'formatdate_second', 'formatdate_tenthsecond', + 'formatdate_millisecond']), + trap_buff_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + trap_buff_size=dict(type='str'), + module_name=dict(type='str'), + channel_id=dict(type='str'), + trap_enable=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + trap_level=dict(choices=['emergencies', 'alert', 'critical', 'error', 'warning', 'notification', + 'informational', 'debugging']) + ) + + argument_spec.update(ce_argument_spec) + module = InfoCenterTrap(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface.py new file mode 100644 index 00000000..95bab788 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface.py @@ -0,0 +1,891 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_interface +short_description: Manages physical attributes of interfaces on HUAWEI CloudEngine switches. +description: + - Manages physical attributes of interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module is also used to create logical interfaces such as + vlanif and loopbacks. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/10, Tunnel1. + interface_type: + description: + - Interface type to be configured from the device. + choices: ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'vlanif', 'loopback', 'meth', + 'eth-trunk', 'nve', 'tunnel', 'ethernet', 'fcoe-port', 'fabric-port', 'stack-port', 'null'] + admin_state: + description: + - Specifies the interface management status. + The value is an enumerated type. + up, An interface is in the administrative Up state. + down, An interface is in the administrative Down state. + choices: ['up', 'down'] + description: + description: + - Specifies an interface description. + The value is a string of 1 to 242 case-sensitive characters, + spaces supported but question marks (?) not supported. + mode: + description: + - Manage Layer 2 or Layer 3 state of the interface. + choices: ['layer2', 'layer3'] + l2sub: + description: + - Specifies whether the interface is a Layer 2 sub-interface. + type: bool + default: 'no' + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent', 'default'] +''' + +EXAMPLES = ''' +- name: Interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure an interface is a Layer 3 port and that it has the proper description + community.network.ce_interface: + interface: 10GE1/0/22 + description: 'Configured by Ansible' + mode: layer3 + provider: '{{ cli }}' + + - name: Admin down an interface + community.network.ce_interface: + interface: 10GE1/0/22 + admin_state: down + provider: '{{ cli }}' + + - name: Remove all tunnel interfaces + community.network.ce_interface: + interface_type: tunnel + state: absent + provider: '{{ cli }}' + + - name: Remove all logical interfaces + community.network.ce_interface: + interface_type: '{{ item }}' + state: absent + provider: '{{ cli }}' + with_items: + - loopback + - eth-trunk + - nve + + - name: Admin up all 10GE interfaces + community.network.ce_interface: + interface_type: 10GE + admin_state: up + provider: '{{ cli }}' +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"interface": "10GE1/0/10", "admin_state": "down"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"admin_state": "up", "description": "None", + "interface": "10GE1/0/10", "mode": "layer2"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"admin_state": "down", "description": "None", + "interface": "10GE1/0/10", "mode": "layer2"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/10", "shutdown"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_INTFS = """ + + + + + + %s + + + + + + + + + +""" + + +CE_NC_GET_INTF = """ + + + + + %s + + + + + + + + + + +""" + +CE_NC_XML_CREATE_INTF = """ + + + + %s + %s + + + +""" + +CE_NC_XML_CREATE_INTF_L2SUB = """ + + + + %s + %s + true + + + +""" + +CE_NC_XML_DELETE_INTF = """ + + + + %s + + + +""" + + +CE_NC_XML_MERGE_INTF_DES = """ + + + + %s + %s + + + +""" +CE_NC_XML_MERGE_INTF_STATUS = """ + + + + %s + %s + + + +""" + +CE_NC_XML_MERGE_INTF_L2ENABLE = """ + + + + %s + %s + + + +""" + +ADMIN_STATE_TYPE = ('ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', + 'vlanif', 'meth', 'eth-trunk', 'vbdif', 'tunnel', + 'ethernet', 'stack-port') + +SWITCH_PORT_TYPE = ('ge', '10ge', '25ge', + '4x10ge', '40ge', '100ge', 'eth-trunk') + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_admin_state_enable(iftype): + """admin state disable: loopback nve""" + + return bool(iftype in ADMIN_STATE_TYPE) + + +def is_portswitch_enalbe(iftype): + """"is portswitch? """ + + return bool(iftype in SWITCH_PORT_TYPE) + + +class Interface(object): + """Manages physical attributes of interfaces.""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface info + self.interface = self.module.params['interface'] + self.interface_type = self.module.params['interface_type'] + self.admin_state = self.module.params['admin_state'] + self.description = self.module.params['description'] + self.mode = self.module.params['mode'] + self.l2sub = self.module.params['l2sub'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intfs_info = dict() # all type interface info + self.intf_info = dict() # one interface info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interfaces_dict(self): + """ get interfaces attributes dict.""" + + intfs_info = dict() + conf_str = CE_NC_GET_INTFS % self.interface_type + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return intfs_info + + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + intfs = root.findall("ifm/interfaces/") + if intfs: + for intf in intfs: + intf_type = intf.find("ifPhyType").text.lower() + if intf_type: + if not intfs_info.get(intf_type): + intfs_info[intf_type] = list() + intf_info = dict() + for tmp in intf: + intf_info[tmp.tag] = tmp.text + intfs_info[intf_type].append(intf_info) + return intfs_info + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_INTF % ifname + recv_xml = get_nc_config(self.module, conf_str) + + if "" in recv_xml: + return intf_info + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + intfs = root.findall("ifm/interfaces/interface/") + if intfs: + for intf in intfs: + intf_info[intf.tag] = intf.text + return intf_info + + def create_interface(self, ifname, description, admin_state, mode, l2sub): + """Create interface.""" + + if l2sub: + self.updates_cmd.append("interface %s mode l2" % ifname) + else: + self.updates_cmd.append("interface %s" % ifname) + + if not description: + description = '' + else: + self.updates_cmd.append("description %s" % description) + + if l2sub: + xmlstr = CE_NC_XML_CREATE_INTF_L2SUB % (ifname, description) + else: + xmlstr = CE_NC_XML_CREATE_INTF % (ifname, description) + if admin_state and is_admin_state_enable(self.intf_type): + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, admin_state) + if admin_state == 'up': + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + if mode and is_portswitch_enalbe(self.intf_type): + if mode == "layer2": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append('portswitch') + elif mode == "layer3": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'disable') + self.updates_cmd.append('undo portswitch') + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "CREATE_INTF") + self.changed = True + + def delete_interface(self, ifname): + """ Delete interface.""" + + xmlstr = CE_NC_XML_DELETE_INTF % ifname + conf_str = ' ' + xmlstr + ' ' + self.updates_cmd.append('undo interface %s' % ifname) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_INTF") + self.changed = True + + def delete_interfaces(self, iftype): + """ Delete interfaces with type.""" + + xmlstr = '' + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + xmlstr += CE_NC_XML_DELETE_INTF % intf['ifName'] + self.updates_cmd.append('undo interface %s' % intf['ifName']) + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_INTFS") + self.changed = True + + def merge_interface(self, ifname, description, admin_state, mode): + """ Merge interface attributes.""" + + xmlstr = '' + change = False + self.updates_cmd.append("interface %s" % ifname) + if description and self.intf_info["ifDescr"] != description: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (ifname, description) + self.updates_cmd.append("description %s" % description) + change = True + + if admin_state and is_admin_state_enable(self.intf_type) \ + and self.intf_info["ifAdminStatus"] != admin_state: + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, admin_state) + change = True + if admin_state == "up": + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + + if is_portswitch_enalbe(self.intf_type): + if mode == "layer2" and self.intf_info["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append("portswitch") + change = True + elif mode == "layer3" \ + and self.intf_info["isL2SwitchPort"] != "false": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'disable') + self.updates_cmd.append("undo portswitch") + change = True + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_INTF_ATTR") + self.changed = True + + def merge_interfaces(self, iftype, description, admin_state, mode): + """ Merge interface attributes by type.""" + + xmlstr = '' + change = False + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + if_change = False + self.updates_cmd.append("interface %s" % intf['ifName']) + if description and intf["ifDescr"] != description: + xmlstr += CE_NC_XML_MERGE_INTF_DES % ( + intf['ifName'], description) + self.updates_cmd.append("description %s" % description) + if_change = True + if admin_state and is_admin_state_enable(self.intf_type)\ + and intf["ifAdminStatus"] != admin_state: + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % ( + intf['ifName'], admin_state) + if_change = True + if admin_state == "up": + self.updates_cmd.append("undo shutdown") + else: + self.updates_cmd.append("shutdown") + + if is_portswitch_enalbe(self.intf_type): + if mode == "layer2" \ + and intf["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % ( + intf['ifName'], 'enable') + self.updates_cmd.append("portswitch") + if_change = True + elif mode == "layer3" \ + and intf["isL2SwitchPort"] != "false": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % ( + intf['ifName'], 'disable') + self.updates_cmd.append("undo portswitch") + if_change = True + + if if_change: + change = True + else: + self.updates_cmd.pop() + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_INTFS_ATTR") + self.changed = True + + def default_interface(self, ifname): + """default_interface""" + + change = False + xmlstr = "" + self.updates_cmd.append("interface %s" % ifname) + # set description default + if self.intf_info["ifDescr"]: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (ifname, '') + self.updates_cmd.append("undo description") + change = True + + # set admin_status default + if is_admin_state_enable(self.intf_type) \ + and self.intf_info["ifAdminStatus"] != 'up': + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (ifname, 'up') + self.updates_cmd.append("undo shutdown") + change = True + + # set portswitch default + if is_portswitch_enalbe(self.intf_type) \ + and self.intf_info["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (ifname, 'enable') + self.updates_cmd.append("portswitch") + change = True + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "SET_INTF_DEFAULT") + self.changed = True + + def default_interfaces(self, iftype): + """ Set interface config to default by type.""" + + change = False + xmlstr = '' + intfs_list = self.intfs_info.get(iftype.lower()) + if not intfs_list: + return + + for intf in intfs_list: + if_change = False + self.updates_cmd.append("interface %s" % intf['ifName']) + + # set description default + if intf['ifDescr']: + xmlstr += CE_NC_XML_MERGE_INTF_DES % (intf['ifName'], '') + self.updates_cmd.append("undo description") + if_change = True + + # set admin_status default + if is_admin_state_enable(self.intf_type) and intf["ifAdminStatus"] != 'up': + xmlstr += CE_NC_XML_MERGE_INTF_STATUS % (intf['ifName'], 'up') + self.updates_cmd.append("undo shutdown") + if_change = True + + # set portswitch default + if is_portswitch_enalbe(self.intf_type) and intf["isL2SwitchPort"] != "true": + xmlstr += CE_NC_XML_MERGE_INTF_L2ENABLE % (intf['ifName'], 'enable') + self.updates_cmd.append("portswitch") + if_change = True + + if if_change: + change = True + else: + self.updates_cmd.pop() + + if not change: + return + + conf_str = ' ' + xmlstr + ' ' + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "SET_INTFS_DEFAULT") + self.changed = True + + def check_params(self): + """Check all input params""" + + if not self.interface and not self.interface_type: + self.module.fail_json( + msg='Error: Interface or interface_type must be set.') + if self.interface and self.interface_type: + self.module.fail_json( + msg='Error: Interface or interface_type' + ' can not be set at the same time.') + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: interface name of %s' + ' is error.' % self.interface) + + elif self.interface_type: + self.intf_type = get_interface_type(self.interface_type) + if not self.intf_type or self.intf_type != self.interface_type.replace(" ", "").lower(): + self.module.fail_json( + msg='Error: interface type of %s' + ' is error.' % self.interface_type) + + if not self.intf_type: + self.module.fail_json( + msg='Error: interface or interface type %s is error.') + + # shutdown check + if not is_admin_state_enable(self.intf_type) \ + and self.state == "present" and self.admin_state == "down": + self.module.fail_json( + msg='Error: The %s interface can not' + ' be shutdown.' % self.intf_type) + + # port switch mode check + if not is_portswitch_enalbe(self.intf_type)\ + and self.mode and self.state == "present": + self.module.fail_json( + msg='Error: The %s interface can not manage' + ' Layer 2 or Layer 3 state.' % self.intf_type) + + # check description len + if self.description: + if len(self.description) > 242 \ + or len(self.description.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: interface description ' + 'is not in the range from 1 to 242.') + # check l2sub flag + if self.l2sub: + if not self.interface: + self.module.fail_json(msg='Error: L2sub flag can not be set when there no interface set with.') + if self.interface.count(".") != 1: + self.module.fail_json(msg='Error: Interface name is invalid, it is not sub-interface.') + + def get_proposed(self): + """get_proposed""" + + self.proposed['state'] = self.state + if self.interface: + self.proposed["interface"] = self.interface + if self.interface_type: + self.proposed["interface_type"] = self.interface_type + + if self.state == 'present': + if self.description: + self.proposed["description"] = self.description + if self.mode: + self.proposed["mode"] = self.mode + if self.admin_state: + self.proposed["admin_state"] = self.admin_state + self.proposed["l2sub"] = self.l2sub + + elif self.state == 'default': + if self.description: + self.proposed["description"] = "" + if is_admin_state_enable(self.intf_type) and self.admin_state: + self.proposed["admin_state"] = self.admin_state + if is_portswitch_enalbe(self.intf_type) and self.mode: + self.proposed["mode"] = self.mode + + def get_existing(self): + """get_existing""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + if is_admin_state_enable(self.intf_type): + self.existing["admin_state"] = self.intf_info["ifAdminStatus"] + self.existing["description"] = self.intf_info["ifDescr"] + if is_portswitch_enalbe(self.intf_type): + if self.intf_info["isL2SwitchPort"] == "true": + self.existing["mode"] = "layer2" + else: + self.existing["mode"] = "layer3" + + if self.intfs_info: + intfs = self.intfs_info.get(self.intf_type.lower()) + for intf in intfs: + intf_para = dict() + if intf["ifAdminStatus"]: + intf_para["admin_state"] = intf["ifAdminStatus"] + intf_para["description"] = intf["ifDescr"] + + if intf["isL2SwitchPort"] == "true": + intf_para["mode"] = "layer2" + else: + intf_para["mode"] = "layer3" + self.existing[intf["ifName"]] = intf_para + + def get_end_state(self): + """get_end_state""" + if self.interface: + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + if is_admin_state_enable(self.intf_type): + self.end_state["admin_state"] = end_info["ifAdminStatus"] + self.end_state["description"] = end_info["ifDescr"] + if is_portswitch_enalbe(self.intf_type): + if end_info["isL2SwitchPort"] == "true": + self.end_state["mode"] = "layer2" + else: + self.end_state["mode"] = "layer3" + + if self.interface_type: + end_info = self.get_interfaces_dict() + intfs = end_info.get(self.intf_type.lower()) + for intf in intfs: + intf_para = dict() + if intf["ifAdminStatus"]: + intf_para["admin_state"] = intf["ifAdminStatus"] + intf_para["description"] = intf["ifDescr"] + + if intf["isL2SwitchPort"] == "true": + intf_para["mode"] = "layer2" + else: + intf_para["mode"] = "layer3" + self.end_state[intf["ifName"]] = intf_para + + def work(self): + """worker""" + + self.check_params() + + # single interface config + if self.interface: + self.intf_info = self.get_interface_dict(self.interface) + self.get_existing() + if self.state == 'present': + if not self.intf_info: + # create interface + self.create_interface(self.interface, + self.description, + self.admin_state, + self.mode, + self.l2sub) + else: + # merge interface + if self.description or self.admin_state or self.mode: + self.merge_interface(self.interface, + self.description, + self.admin_state, + self.mode) + + elif self.state == 'absent': + if self.intf_info: + # delete interface + self.delete_interface(self.interface) + else: + # interface does not exist + self.module.fail_json( + msg='Error: interface does not exist.') + + else: # default + if not self.intf_info: + # error, interface does not exist + self.module.fail_json( + msg='Error: interface does not exist.') + else: + self.default_interface(self.interface) + + # interface type config + else: + self.intfs_info = self.get_interfaces_dict() + self.get_existing() + if self.state == 'present': + if self.intfs_info.get(self.intf_type.lower()): + if self.description or self.admin_state or self.mode: + self.merge_interfaces(self.intf_type, + self.description, + self.admin_state, + self.mode) + elif self.state == 'absent': + # delete all interface of this type + if self.intfs_info.get(self.intf_type.lower()): + self.delete_interfaces(self.intf_type) + + else: + # set interfaces config to default + if self.intfs_info.get(self.intf_type.lower()): + self.default_interfaces(self.intf_type) + else: + self.module.fail_json( + msg='Error: no interface in this type.') + + self.get_proposed() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + interface=dict(required=False, type='str'), + admin_state=dict(choices=['up', 'down'], required=False), + description=dict(required=False, default=None), + mode=dict(choices=['layer2', 'layer3'], required=False), + interface_type=dict(required=False), + l2sub=dict(required=False, default=False, type='bool'), + state=dict(choices=['absent', 'present', 'default'], + default='present', required=False), + ) + + argument_spec.update(ce_argument_spec) + interface = Interface(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface_ospf.py new file mode 100644 index 00000000..2c724817 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_interface_ospf.py @@ -0,0 +1,793 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_interface_ospf +short_description: Manages configuration of an OSPF interface instanceon HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF interface instanceon HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/10. + required: true + process_id: + description: + - Specifies a process ID. + The value is an integer ranging from 1 to 4294967295. + required: true + area: + description: + - Ospf area associated with this ospf process. + Valid values are a string, formatted as an IP address + (i.e. "0.0.0.0") or as an integer between 1 and 4294967295. + required: true + cost: + description: + - The cost associated with this interface. + Valid values are an integer in the range from 1 to 65535. + hello_interval: + description: + - Time between sending successive hello packets. + Valid values are an integer in the range from 1 to 65535. + dead_interval: + description: + - Time interval an ospf neighbor waits for a hello + packet before tearing down adjacencies. Valid values are an + integer in the range from 1 to 235926000. + silent_interface: + description: + - Setting to true will prevent this interface from receiving + HELLO packets. Valid values are 'true' and 'false'. + type: bool + default: 'no' + auth_mode: + description: + - Specifies the authentication type. + choices: ['none', 'null', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'] + auth_text_simple: + description: + - Specifies a password for simple authentication. + The value is a string of 1 to 8 characters. + auth_key_id: + description: + - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5. + Valid value is an integer is in the range from 1 to 255. + auth_text_md5: + description: + - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication. + The value is a string of 1 to 255 case-sensitive characters, spaces not supported. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Eth_trunk module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Enables OSPF and sets the cost on an interface + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + cost: 100 + provider: '{{ cli }}' + + - name: Sets the dead interval of the OSPF neighbor + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + dead_interval: 100 + provider: '{{ cli }}' + + - name: Sets the interval for sending Hello packets on an interface + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + hello_interval: 2 + provider: '{{ cli }}' + + - name: Disables an interface from receiving and sending OSPF packets + community.network.ce_interface_ospf: + interface: 10GE1/0/30 + process_id: 1 + area: 100 + silent_interface: true + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100", "interface": "10GE1/0/30", "cost": "100"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "0.0.0.100", "interface": "10GE1/0/30", + "cost": "100", "dead_interval": "40", "hello_interval": "10", + "silent_interface": "false", "auth_mode": "none"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/30", + "ospf enable 1 area 0.0.0.100", + "ospf cost 100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF = """ + + + + + + %s + + + + + %s + + + %s + + + + + + + + + + + + + + + + + + +""" + +CE_NC_XML_BUILD_PROCESS = """ + + + + + + %s + + + %s + %s + + + + + + + +""" + +CE_NC_XML_BUILD_MERGE_INTF = """ + + + %s + + +""" + +CE_NC_XML_BUILD_DELETE_INTF = """ + + + %s + + +""" +CE_NC_XML_SET_IF_NAME = """ + %s +""" + +CE_NC_XML_SET_HELLO = """ + %s +""" + +CE_NC_XML_SET_DEAD = """ + %s +""" + +CE_NC_XML_SET_SILENT = """ + %s +""" + +CE_NC_XML_SET_COST = """ + %s +""" + +CE_NC_XML_SET_AUTH_MODE = """ + %s +""" + + +CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """ + %s +""" + +CE_NC_XML_SET_AUTH_MD5 = """ + %s + %s +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if not addr: + return False + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class InterfaceOSPF(object): + """ + Manages configuration of an OSPF interface instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.interface = self.module.params['interface'] + self.process_id = self.module.params['process_id'] + self.area = self.module.params['area'] + self.cost = self.module.params['cost'] + self.hello_interval = self.module.params['hello_interval'] + self.dead_interval = self.module.params['dead_interval'] + self.silent_interface = self.module.params['silent_interface'] + self.auth_mode = self.module.params['auth_mode'] + self.auth_text_simple = self.module.params['auth_text_simple'] + self.auth_key_id = self.module.params['auth_key_id'] + self.auth_text_md5 = self.module.params['auth_text_md5'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def netconf_set_config(self, xml_str, xml_name): + """netconf set config""" + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_area_ip(self): + """convert integer to ip address""" + + if not self.area.isdigit(): + return self.area + + addr_int = ['0'] * 4 + addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF) + addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF) + addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF) + addr_int[3] = str(int(self.area) & 0xFF) + + return '.'.join(addr_int) + + def get_ospf_dict(self): + """ get one ospf attributes dict.""" + + ospf_info = dict() + conf_str = CE_NC_GET_OSPF % ( + self.process_id, self.get_area_ip(), self.interface) + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return ospf_info + + xml_str = rcv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get process base info + root = ElementTree.fromstring(xml_str) + ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite") + if not ospfsite: + self.module.fail_json(msg="Error: ospf process does not exist.") + + for site in ospfsite: + if site.tag in ["processId", "routerId", "vrfName"]: + ospf_info[site.tag] = site.text + + # get areas info + ospf_info["areaId"] = "" + areas = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area") + if areas: + for area in areas: + if area.tag == "areaId": + ospf_info["areaId"] = area.text + break + + # get interface info + ospf_info["interface"] = dict() + intf = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area/interfaces/interface") + if intf: + for attr in intf: + if attr.tag in ["ifName", "networkType", + "helloInterval", "deadInterval", + "silentEnable", "configCost", + "authenticationMode", "authTextSimple", + "keyId", "authTextMd5"]: + ospf_info["interface"][attr.tag] = attr.text + + return ospf_info + + def set_ospf_interface(self): + """set interface ospf enable, and set its ospf attributes""" + + xml_intf = CE_NC_XML_SET_IF_NAME % self.interface + + # ospf view + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + if self.silent_interface: + xml_intf += CE_NC_XML_SET_SILENT % str(self.silent_interface).lower() + if self.silent_interface: + self.updates_cmd.append("silent-interface %s" % self.interface) + else: + self.updates_cmd.append("undo silent-interface %s" % self.interface) + + # interface view + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("ospf enable %s area %s" % ( + self.process_id, self.get_area_ip())) + if self.cost: + xml_intf += CE_NC_XML_SET_COST % self.cost + self.updates_cmd.append("ospf cost %s" % self.cost) + if self.hello_interval: + xml_intf += CE_NC_XML_SET_HELLO % self.hello_interval + self.updates_cmd.append("ospf timer hello %s" % + self.hello_interval) + if self.dead_interval: + xml_intf += CE_NC_XML_SET_DEAD % self.dead_interval + self.updates_cmd.append("ospf timer dead %s" % self.dead_interval) + if self.auth_mode: + xml_intf += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo ospf authentication-mode") + else: + self.updates_cmd.append("ospf authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_intf += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s" + % (self.auth_mode, self.auth_text_simple)) + elif self.auth_mode in ["hmac-sha256", "md5", "hmac-md5"] and self.auth_key_id: + xml_intf += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s %s" + % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + else: + pass + + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + (CE_NC_XML_BUILD_MERGE_INTF % xml_intf)) + self.netconf_set_config(xml_str, "SET_INTERFACE_OSPF") + self.changed = True + + def merge_ospf_interface(self): + """merge interface ospf attributes""" + + intf_dict = self.ospf_info["interface"] + + # ospf view + xml_ospf = "" + if intf_dict.get("silentEnable") != str(self.silent_interface).lower(): + xml_ospf += CE_NC_XML_SET_SILENT % str(self.silent_interface).lower() + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + if self.silent_interface: + self.updates_cmd.append("silent-interface %s" % self.interface) + else: + self.updates_cmd.append("undo silent-interface %s" % self.interface) + + # interface view + xml_intf = "" + self.updates_cmd.append("interface %s" % self.interface) + if self.cost and intf_dict.get("configCost") != self.cost: + xml_intf += CE_NC_XML_SET_COST % self.cost + self.updates_cmd.append("ospf cost %s" % self.cost) + if self.hello_interval and intf_dict.get("helloInterval") != self.hello_interval: + xml_intf += CE_NC_XML_SET_HELLO % self.hello_interval + self.updates_cmd.append("ospf timer hello %s" % + self.hello_interval) + if self.dead_interval and intf_dict.get("deadInterval") != self.dead_interval: + xml_intf += CE_NC_XML_SET_DEAD % self.dead_interval + self.updates_cmd.append("ospf timer dead %s" % self.dead_interval) + if self.auth_mode: + # NOTE: for security, authentication config will always be update + xml_intf += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo ospf authentication-mode") + else: + self.updates_cmd.append("ospf authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_intf += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s" + % (self.auth_mode, self.auth_text_simple)) + elif self.auth_mode in ["hmac-sha256", "md5", "hmac-md5"] and self.auth_key_id: + xml_intf += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append("ospf authentication-mode %s %s %s" + % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + else: + pass + if not xml_intf: + self.updates_cmd.pop() # remove command: interface + + if not xml_ospf and not xml_intf: + return + + xml_sum = CE_NC_XML_SET_IF_NAME % self.interface + xml_sum += xml_ospf + xml_intf + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + (CE_NC_XML_BUILD_MERGE_INTF % xml_sum)) + self.netconf_set_config(xml_str, "MERGE_INTERFACE_OSPF") + self.changed = True + + def unset_ospf_interface(self): + """set interface ospf disable, and all its ospf attributes will be removed""" + + intf_dict = self.ospf_info["interface"] + xml_sum = "" + xml_intf = CE_NC_XML_SET_IF_NAME % self.interface + if intf_dict.get("silentEnable") == "true": + xml_sum += CE_NC_XML_BUILD_MERGE_INTF % ( + xml_intf + (CE_NC_XML_SET_SILENT % "false")) + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + self.updates_cmd.append( + "undo silent-interface %s" % self.interface) + + xml_sum += CE_NC_XML_BUILD_DELETE_INTF % xml_intf + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, + self.get_area_ip(), + xml_sum) + self.netconf_set_config(xml_str, "DELETE_INTERFACE_OSPF") + self.updates_cmd.append("undo ospf cost") + self.updates_cmd.append("undo ospf timer hello") + self.updates_cmd.append("undo ospf timer dead") + self.updates_cmd.append("undo ospf authentication-mode") + self.updates_cmd.append("undo ospf enable %s area %s" % ( + self.process_id, self.get_area_ip())) + self.changed = True + + def check_params(self): + """Check all input params""" + + self.interface = self.interface.replace(" ", "").upper() + + # interface check + if not get_interface_type(self.interface): + self.module.fail_json(msg="Error: interface is invalid.") + + # process_id check + if not self.process_id.isdigit(): + self.module.fail_json(msg="Error: process_id is not digit.") + if int(self.process_id) < 1 or int(self.process_id) > 4294967295: + self.module.fail_json(msg="Error: process_id must be an integer between 1 and 4294967295.") + + # area check + if self.area.isdigit(): + if int(self.area) < 0 or int(self.area) > 4294967295: + self.module.fail_json(msg="Error: area id (Integer) must be between 0 and 4294967295.") + else: + if not is_valid_v4addr(self.area): + self.module.fail_json(msg="Error: area id is invalid.") + + # area authentication check + if self.state == "present": + if self.auth_mode: + if self.auth_mode == "simple": + if self.auth_text_simple and len(self.auth_text_simple) > 8: + self.module.fail_json( + msg="Error: auth_text_simple is not in the range from 1 to 8.") + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and not self.auth_text_md5: + self.module.fail_json( + msg='Error: auth_key_id and auth_text_md5 should be set at the same time.') + if not self.auth_key_id and self.auth_text_md5: + self.module.fail_json( + msg='Error: auth_key_id and auth_text_md5 should be set at the same time.') + if self.auth_key_id: + if not self.auth_key_id.isdigit(): + self.module.fail_json( + msg="Error: auth_key_id is not digit.") + if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255: + self.module.fail_json( + msg="Error: auth_key_id is not in the range from 1 to 255.") + if self.auth_text_md5 and len(self.auth_text_md5) > 255: + self.module.fail_json( + msg="Error: auth_text_md5 is not in the range from 1 to 255.") + # cost check + if self.cost: + if not self.cost.isdigit(): + self.module.fail_json(msg="Error: cost is not digit.") + if int(self.cost) < 1 or int(self.cost) > 65535: + self.module.fail_json( + msg="Error: cost is not in the range from 1 to 65535") + + # hello_interval check + if self.hello_interval: + if not self.hello_interval.isdigit(): + self.module.fail_json( + msg="Error: hello_interval is not digit.") + if int(self.hello_interval) < 1 or int(self.hello_interval) > 65535: + self.module.fail_json( + msg="Error: hello_interval is not in the range from 1 to 65535") + + # dead_interval check + if self.dead_interval: + if not self.dead_interval.isdigit(): + self.module.fail_json(msg="Error: dead_interval is not digit.") + if int(self.dead_interval) < 1 or int(self.dead_interval) > 235926000: + self.module.fail_json( + msg="Error: dead_interval is not in the range from 1 to 235926000") + + def get_proposed(self): + """get proposed info""" + + self.proposed["interface"] = self.interface + self.proposed["process_id"] = self.process_id + self.proposed["area"] = self.get_area_ip() + self.proposed["cost"] = self.cost + self.proposed["hello_interval"] = self.hello_interval + self.proposed["dead_interval"] = self.dead_interval + self.proposed["silent_interface"] = self.silent_interface + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.auth_mode == "simple": + self.proposed["auth_text_simple"] = self.auth_text_simple + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + self.proposed["auth_key_id"] = self.auth_key_id + self.proposed["auth_text_md5"] = self.auth_text_md5 + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.ospf_info: + return + + if self.ospf_info["interface"]: + self.existing["interface"] = self.interface + self.existing["cost"] = self.ospf_info["interface"].get("configCost") + self.existing["hello_interval"] = self.ospf_info["interface"].get("helloInterval") + self.existing["dead_interval"] = self.ospf_info["interface"].get("deadInterval") + self.existing["silent_interface"] = self.ospf_info["interface"].get("silentEnable") + self.existing["auth_mode"] = self.ospf_info["interface"].get("authenticationMode") + self.existing["auth_text_simple"] = self.ospf_info["interface"].get("authTextSimple") + self.existing["auth_key_id"] = self.ospf_info["interface"].get("keyId") + self.existing["auth_text_md5"] = self.ospf_info["interface"].get("authTextMd5") + self.existing["process_id"] = self.ospf_info["processId"] + self.existing["area"] = self.ospf_info["areaId"] + + def get_end_state(self): + """get end state info""" + + ospf_info = self.get_ospf_dict() + if not ospf_info: + return + + if ospf_info["interface"]: + self.end_state["interface"] = self.interface + self.end_state["cost"] = ospf_info["interface"].get("configCost") + self.end_state["hello_interval"] = ospf_info["interface"].get("helloInterval") + self.end_state["dead_interval"] = ospf_info["interface"].get("deadInterval") + self.end_state["silent_interface"] = ospf_info["interface"].get("silentEnable") + self.end_state["auth_mode"] = ospf_info["interface"].get("authenticationMode") + self.end_state["auth_text_simple"] = ospf_info["interface"].get("authTextSimple") + self.end_state["auth_key_id"] = ospf_info["interface"].get("keyId") + self.end_state["auth_text_md5"] = ospf_info["interface"].get("authTextMd5") + self.end_state["process_id"] = ospf_info["processId"] + self.end_state["area"] = ospf_info["areaId"] + + def work(self): + """worker""" + + self.check_params() + self.ospf_info = self.get_ospf_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.ospf_info or not self.ospf_info["interface"]: + # create ospf area and set interface config + self.set_ospf_interface() + else: + # merge interface ospf area config + self.merge_ospf_interface() + else: + if self.ospf_info and self.ospf_info["interface"]: + # delete interface ospf area config + self.unset_ospf_interface() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + process_id=dict(required=True, type='str'), + area=dict(required=True, type='str'), + cost=dict(required=False, type='str'), + hello_interval=dict(required=False, type='str'), + dead_interval=dict(required=False, type='str'), + silent_interface=dict(required=False, default=False, type='bool'), + auth_mode=dict(required=False, + choices=['none', 'null', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'), + auth_text_simple=dict(required=False, type='str', no_log=True), + auth_key_id=dict(required=False, type='str'), + auth_text_md5=dict(required=False, type='str', no_log=True), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = InterfaceOSPF(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ip_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ip_interface.py new file mode 100644 index 00000000..149a427d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ip_interface.py @@ -0,0 +1,735 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ip_interface +short_description: Manages L3 attributes for IPv4 and IPv6 interfaces on HUAWEI CloudEngine switches. +description: + - Manages Layer 3 attributes for IPv4 and IPv6 interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - Interface must already be a L3 port when using this module. + - Logical interfaces (loopback, vlanif) must be created first. + - C(mask) must be inserted in decimal format (i.e. 24) for + both IPv6 and IPv4. + - A single interface can have multiple IPv6 configured. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/22, vlanif10. + required: true + addr: + description: + - IPv4 or IPv6 Address. + mask: + description: + - Subnet mask for IPv4 or IPv6 Address in decimal format. + version: + description: + - IP address version. + default: v4 + choices: ['v4','v6'] + ipv4_type: + description: + - Specifies an address type. + The value is an enumerated type. + main, primary IP address. + sub, secondary IP address. + default: main + choices: ['main','sub'] + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Ip_interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure ipv4 address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v4 + state: present + addr: 20.20.20.20 + mask: 24 + provider: '{{ cli }}' + + - name: Ensure ipv4 secondary address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v4 + state: present + addr: 30.30.30.30 + mask: 24 + ipv4_type: sub + provider: '{{ cli }}' + + - name: Ensure ipv6 is enabled on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v6 + state: present + provider: '{{ cli }}' + + - name: Ensure ipv6 address is configured on 10GE1/0/22 + community.network.ce_ip_interface: + interface: 10GE1/0/22 + version: v6 + state: present + addr: 2001::db8:800:200c:cccb + mask: 64 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addr": "20.20.20.20", "interface": "10GE1/0/22", "mask": "24"} +existing: + description: k/v pairs of existing IP attributes on the interface + returned: always + type: dict + sample: {"ipv4": [{"ifIpAddr": "11.11.11.11", "subnetMask": "255.255.0.0", "addrType": "main"}], + "interface": "10GE1/0/22"} +end_state: + description: k/v pairs of IP attributes after module execution + returned: always + type: dict + sample: {"ipv4": [{"ifIpAddr": "20.20.20.20", "subnetMask": "255.255.255.0", "addrType": "main"}], + "interface": "10GE1/0/22"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10GE1/0/22", "ip address 20.20.20.20 24"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_INTF = """ + + + + + %s + + + + + + + + + +""" + +CE_NC_ADD_IPV4 = """ + + + + + %s + + + + %s + %s + %s + + + + + + + +""" + +CE_NC_MERGE_IPV4 = """ + + + + + %s + + + + %s + %s + main + + + %s + %s + main + + + + + + + +""" + + +CE_NC_DEL_IPV4 = """ + + + + + %s + + + + %s + %s + %s + + + + + + + +""" + +CE_NC_ADD_IPV6 = """ + + + + + %s + + + + %s + %s + global + + + + + + + +""" + +CE_NC_DEL_IPV6 = """ + + + + + %s + + + + %s + %s + global + + + + + + + +""" + +CE_NC_MERGE_IPV6_ENABLE = """ + + + + + %s + + %s + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if not addr: + return False + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class IpInterface(object): + """ + Manages L3 attributes for IPv4 and IPv6 interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info] + self.interface = self.module.params['interface'] + self.addr = self.module.params['addr'] + self.mask = self.module.params['mask'] + self.version = self.module.params['version'] + self.ipv4_type = self.module.params['ipv4_type'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + # interface info + self.intf_info = dict() + self.intf_type = None + + def __init_module__(self): + """ init module """ + + required_if = [("version", "v4", ("addr", "mask"))] + required_together = [("addr", "mask")] + self.module = AnsibleModule( + argument_spec=self.spec, + required_if=required_if, + required_together=required_together, + supports_check_mode=True + ) + + def netconf_set_config(self, xml_str, xml_name): + """ netconf set config """ + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_INTF % ifname + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return intf_info + + # get interface base info + intf = re.findall( + r'.*(.*).*\s*' + r'(.*).*', rcv_xml) + + if intf: + intf_info = dict(ifName=intf[0][0], + isL2SwitchPort=intf[0][1]) + + # get interface ipv4 address info + ipv4_info = re.findall( + r'.*(.*).*\s*(.*)' + r'.*\s*(.*).*', rcv_xml) + intf_info["am4CfgAddr"] = list() + for info in ipv4_info: + intf_info["am4CfgAddr"].append( + dict(ifIpAddr=info[0], subnetMask=info[1], addrType=info[2])) + + # get interface ipv6 address info + ipv6_info = re.findall( + r'.*.*\s*(.*).*', rcv_xml) + if not ipv6_info: + self.module.fail_json(msg='Error: Fail to get interface %s IPv6 state.' % self.interface) + else: + intf_info["enableFlag"] = ipv6_info[0] + + # get interface ipv6 enable info + ipv6_info = re.findall( + r'.*(.*).*\s*(.*)' + r'.*\s*(.*).*', rcv_xml) + + intf_info["am6CfgAddr"] = list() + for info in ipv6_info: + intf_info["am6CfgAddr"].append( + dict(ifIp6Addr=info[0], addrPrefixLen=info[1], addrType6=info[2])) + + return intf_info + + def convert_len_to_mask(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='Error: IPv4 ipaddress mask length is invalid.') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def is_ipv4_exist(self, addr, maskstr, ipv4_type): + """"Check IPv4 address exist""" + + addrs = self.intf_info["am4CfgAddr"] + if not addrs: + return False + + for address in addrs: + if address["ifIpAddr"] == addr: + return address["subnetMask"] == maskstr and address["addrType"] == ipv4_type + return False + + def get_ipv4_main_addr(self): + """get IPv4 main address""" + + addrs = self.intf_info["am4CfgAddr"] + if not addrs: + return None + + for address in addrs: + if address["addrType"] == "main": + return address + + return None + + def is_ipv6_exist(self, addr, masklen): + """Check IPv6 address exist""" + + addrs = self.intf_info["am6CfgAddr"] + if not addrs: + return False + + for address in addrs: + if address["ifIp6Addr"] == addr.upper(): + if address["addrPrefixLen"] == masklen and address["addrType6"] == "global": + return True + else: + self.module.fail_json( + msg="Error: Input IPv6 address or mask is invalid.") + + return False + + def set_ipv4_addr(self, ifname, addr, mask, ipv4_type): + """Set interface IPv4 address""" + + if not addr or not mask or not type: + return + + maskstr = self.convert_len_to_mask(mask) + if self.state == "present": + if not self.is_ipv4_exist(addr, maskstr, ipv4_type): + # primary IP address + if ipv4_type == "main": + main_addr = self.get_ipv4_main_addr() + if not main_addr: + # no ipv4 main address in this interface + xml_str = CE_NC_ADD_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "ADD_IPV4_ADDR") + else: + # remove old address and set new + xml_str = CE_NC_MERGE_IPV4 % (ifname, main_addr["ifIpAddr"], + main_addr["subnetMask"], + addr, maskstr) + self.netconf_set_config(xml_str, "MERGE_IPV4_ADDR") + # secondary IP address + else: + xml_str = CE_NC_ADD_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "ADD_IPV4_ADDR") + + self.updates_cmd.append("interface %s" % ifname) + if ipv4_type == "main": + self.updates_cmd.append("ip address %s %s" % (addr, maskstr)) + else: + self.updates_cmd.append("ip address %s %s sub" % (addr, maskstr)) + self.changed = True + else: + if self.is_ipv4_exist(addr, maskstr, ipv4_type): + xml_str = CE_NC_DEL_IPV4 % (ifname, addr, maskstr, ipv4_type) + self.netconf_set_config(xml_str, "DEL_IPV4_ADDR") + self.updates_cmd.append("interface %s" % ifname) + if ipv4_type == "main": + self.updates_cmd.append("undo ip address %s %s" % (addr, maskstr)) + else: + self.updates_cmd.append("undo ip address %s %s sub" % (addr, maskstr)) + self.changed = True + + def set_ipv6_addr(self, ifname, addr, mask): + """Set interface IPv6 address""" + + if not addr or not mask: + return + + if self.state == "present": + self.updates_cmd.append("interface %s" % ifname) + if self.intf_info["enableFlag"] == "false": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "true") + self.netconf_set_config(xml_str, "SET_IPV6_ENABLE") + self.updates_cmd.append("ipv6 enable") + self.changed = True + + if not self.is_ipv6_exist(addr, mask): + xml_str = CE_NC_ADD_IPV6 % (ifname, addr, mask) + self.netconf_set_config(xml_str, "ADD_IPV6_ADDR") + + self.updates_cmd.append("ipv6 address %s %s" % (addr, mask)) + self.changed = True + + if not self.changed: + self.updates_cmd.pop() + else: + if self.is_ipv6_exist(addr, mask): + xml_str = CE_NC_DEL_IPV6 % (ifname, addr, mask) + self.netconf_set_config(xml_str, "DEL_IPV6_ADDR") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append( + "undo ipv6 address %s %s" % (addr, mask)) + self.changed = True + + def set_ipv6_enable(self, ifname): + """Set interface IPv6 enable""" + + if self.state == "present": + if self.intf_info["enableFlag"] == "false": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "true") + self.netconf_set_config(xml_str, "SET_IPV6_ENABLE") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("ipv6 enable") + self.changed = True + else: + if self.intf_info["enableFlag"] == "true": + xml_str = CE_NC_MERGE_IPV6_ENABLE % (ifname, "false") + self.netconf_set_config(xml_str, "SET_IPV6_DISABLE") + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("undo ipv6 enable") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check interface type + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + # ipv4 addr and mask check + if self.version == "v4": + if not is_valid_v4addr(self.addr): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.addr) + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: mask is invalid.') + if int(self.mask) > 32 or int(self.mask) < 1: + self.module.fail_json( + msg='Error: mask must be an integer between 1 and 32.') + + # ipv6 mask check + if self.version == "v6": + if self.addr: + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: mask is invalid.') + if int(self.mask) > 128 or int(self.mask) < 1: + self.module.fail_json( + msg='Error: mask must be an integer between 1 and 128.') + + # interface and layer3 check + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: interface %s does not exist.' % self.interface) + + if self.intf_info["isL2SwitchPort"] == "true": + self.module.fail_json(msg='Error: interface %s is layer2.' % self.interface) + + def get_proposed(self): + """get proposed info""" + + self.proposed["state"] = self.state + self.proposed["addr"] = self.addr + self.proposed["mask"] = self.mask + self.proposed["ipv4_type"] = self.ipv4_type + self.proposed["version"] = self.version + self.proposed["interface"] = self.interface + + def get_existing(self): + """get existing info""" + + self.existing["interface"] = self.interface + self.existing["ipv4addr"] = self.intf_info["am4CfgAddr"] + self.existing["ipv6addr"] = self.intf_info["am6CfgAddr"] + self.existing["ipv6enalbe"] = self.intf_info["enableFlag"] + + def get_end_state(self): + """get end state info""" + + intf_info = self.get_interface_dict(self.interface) + self.end_state["interface"] = self.interface + self.end_state["ipv4addr"] = intf_info["am4CfgAddr"] + self.end_state["ipv6addr"] = intf_info["am6CfgAddr"] + self.end_state["ipv6enalbe"] = intf_info["enableFlag"] + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.version == "v4": + self.set_ipv4_addr(self.interface, self.addr, self.mask, self.ipv4_type) + else: + if not self.addr and not self.mask: + self.set_ipv6_enable(self.interface) + else: + self.set_ipv6_addr(self.interface, self.addr, self.mask) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True), + addr=dict(required=False), + version=dict(required=False, choices=['v4', 'v6'], + default='v4'), + mask=dict(type='str', required=False), + ipv4_type=dict(required=False, choices=['main', 'sub'], default='main'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = IpInterface(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_instance.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_instance.py new file mode 100644 index 00000000..db9a7efc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_instance.py @@ -0,0 +1,327 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: ce_is_is_instance +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis process id configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id on HUAWEI CloudEngine devices. +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + instance_id: + description: + - Specifies the id of a isis process.The value is a number of 1 to 4294967295. + required: true + type: int + vpn_name: + description: + - VPN Instance, associate the VPN instance with the corresponding IS-IS process. + type: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + type: str + choices: ['present', 'absent'] +''' + +EXAMPLES = r''' + - name: Set isis process + community.network.ce_is_is_instance: + instance_id: 3 + state: present + + - name: Unset isis process + community.network.ce_is_is_instance: + instance_id: 3 + state: absent + + - name: Check isis process + community.network.ce_is_is_instance: + instance_id: 4294967296 + state: present + + - name: Set vpn name + community.network.ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1 + state: present + + - name: Check vpn name + community.network.ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1234567896321452212221556asdasdasdasdsadvdv + state: present +''' + +RETURN = r''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "instance_id": 1, + "vpn_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "instance_id": 1, + "vpn_name": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "isis 1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INSTANCE = """ + + + %s + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +class ISIS_Instance(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.vpn_name = self.module.params['vpn_name'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_isis_dict(self): + """isis config dict""" + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INSTANCE % self.instance_id)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get isis info + glb = root.find("isiscomm/isSites/isSite") + if glb: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures isis""" + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + + if self.state == "present": + xml_str = "%s" % self.instance_id + self.updates_cmd.append("isis %s" % self.instance_id) + + if self.vpn_name: + xml_str += "%s" % self.vpn_name + self.updates_cmd.append("vpn-instance %s" % self.vpn_name) + else: + # absent + if self.instance_id and str(self.instance_id) == instance.get("instanceId"): + xml_str = "%s" % self.instance_id + self.updates_cmd.append("undo isis %s" % self.instance_id) + + if self.state == "present": + return '' + xml_str + '' + else: + if xml_str: + return '' + xml_str + '' + + def netconf_load_config(self, xml_str): + """load isis config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check instance id + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check vpn_name + if self.vpn_name: + if not is_valid_ip_vpn(self.vpn_name): + self.module.fail_json(msg="Error: Session vpn_name is invalid.") + + def get_proposed(self): + """get proposed info""" + # base config + self.proposed["instance_id"] = self.instance_id + self.proposed["vpn_name"] = self.vpn_name + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + + self.end_state["instance"] = isis_dict.get("instance") + + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + cfg_str = self.config_session() + if cfg_str: + xml_str += cfg_str + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + argument_spec = dict( + instance_id=dict(required=True, type='int'), + vpn_name=dict(required=False, type='str'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_Instance(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_interface.py new file mode 100644 index 00000000..0574cfe5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_interface.py @@ -0,0 +1,785 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_is_is_interface +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis interface configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id on HUAWEI CloudEngine devices. +notes: + - Interface must already be a L3 port when using this module. + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + instance_id: + description: + - Specifies the id of a isis process. + The value is a number of 1 to 4294967295. + required: true + type: int + ifname: + description: + - A L3 interface. + required: true + type: str + leveltype: + description: + - level type for three types. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + level1dispriority: + description: + - the dispriority of the level1. + The value is a number of 1 to 127. + type: int + level2dispriority: + description: + - the dispriority of the level1. + The value is a number of 1 to 127. + type: int + silentenable: + description: + - enable the interface can send isis message. + The value is a bool type. + type: bool + silentcost: + description: + - Specifies whether the routing cost of the silent interface is 0. + The value is a bool type. + type: bool + typep2penable: + description: + - Simulate the network type of the interface as P2P. + The value is a bool type. + type: bool + snpacheck: + description: + - Enable SNPA check for LSPs and SNPs. + The value is a bool type. + type: bool + p2pnegotiationmode: + description: + - Set the P2P neighbor negotiation type. + type: str + choices: ['2_way', '3_way', '3_wayonly'] + p2ppeeripignore: + description: + - When the P2P hello packet is received, no IP address check is performed. + The value is a bool type. + type: bool + ppposicpcheckenable: + description: + - Interface for setting PPP link protocol to check OSICP negotiation status. + The value is a bool type. + type: bool + level1cost: + description: + - Specifies the link cost of the interface when performing Level-1 SPF calculation. + The value is a number of 0 to 16777215. + type: int + level2cost: + description: + - Specifies the link cost of the interface when performing Level-2 SPF calculation. + The value is a number of 0 to 16777215. + type: int + bfdstaticen: + description: + - Configure static BFD on a specific interface enabled with ISIS. + The value is a bool type. + type: bool + bfdblocken: + description: + - Blocking interfaces to dynamically create BFD features. + The value is a bool type. + type: bool + state: + description: + - Determines whether the config should be present or not on the device. + type: str + default: 'present' + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + - name: "create vlan and config vlanif" + ce_config: + lines: 'vlan {{ test_vlan_id }},quit,interface {{test_intf_vlanif}},ip address {{test_vlanif_ip}} 24' + match: none + + - name: "create eth-trunk and config eth-trunk" + ce_config: + lines: 'interface {{test_intf_trunk}},undo portswitch,ip address {{test_trunk_ip}} 24' + match: none + + - name: "create vpn instance" + ce_config: + lines: 'ip vpn-instance {{test_vpn}},ipv4-family' + match: none + + - name: Set isis circuit-level + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + leveltype: level_1_2 + state: present + + - name: Set isis level1dispriority + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + level1dispriority: 0 + state: present + + - name: Set isis level2dispriority + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + level2dispriority: 0 + state: present + + - name: Set isis silentenable + community.network.ce_is_is_interface: + instance_id: 3 + ifname: Eth-Trunk10 + silentenable: true + state: present + + - name: Set vpn name + ce_is_is_instance: + instance_id: 22 + vpn_name: vpn1 + state: present +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "addr_type": null, + "create_type": null, + "dest_addr": null, + "out_if_name": "10GE1/0/1", + "session_name": "bfd_l2link", + "src_addr": null, + "state": "present", + "use_default_ip": true, + "vrf_name": null + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INTERFACE = """ + + + %s + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_INTERFACE = """ + + + %s + + + %s + + + + +""" + +CE_NC_DELETE_ISIS_INTERFACE = """ + + + %s + + + %s + + + + +""" + +CE_NC_GET_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_BFDINTERFACE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_ip_addr(ipaddr): + """check ip address, Supports IPv4 and IPv6""" + + if not ipaddr or '\x00' in ipaddr: + return False + + try: + res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_NUMERICHOST) + return bool(res) + except socket.gaierror: + err = sys.exc_info()[1] + if err.args[0] == socket.EAI_NONAME: + return False + raise + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +class ISIS_Instance(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.ifname = self.module.params['ifname'] + self.leveltype = self.module.params['leveltype'] + self.level1dispriority = self.module.params['level1dispriority'] + self.level2dispriority = self.module.params['level2dispriority'] + self.silentenable = self.module.params['silentenable'] + self.silentcost = self.module.params['silentcost'] + self.typep2penable = self.module.params['typep2penable'] + self.snpacheck = self.module.params['snpacheck'] + self.p2pnegotiationmode = self.module.params['p2pnegotiationmode'] + self.p2ppeeripignore = self.module.params['p2ppeeripignore'] + self.ppposicpcheckenable = self.module.params['ppposicpcheckenable'] + self.level1cost = self.module.params['level1cost'] + self.level2cost = self.module.params['level2cost'] + self.bfdstaticen = self.module.params['bfdstaticen'] + self.bfdblocken = self.module.params['bfdblocken'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + mutually_exclusive = [["level1dispriority", "level2dispriority"], + ["level1cost", "level2cost"]] + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def get_isis_dict(self): + """bfd config dict""" + + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INTERFACE % self.instance_id)) + if self.bfdstaticen or self.bfdblocken: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_BFDINTERFACE % self.instance_id)) + + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # + glb = root.find("isiscomm/isSites/isSite/isCircuits/isCircuit") + if self.bfdstaticen or self.bfdblocken: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isCircMts/isCircMt") + if glb: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + if self.ifname: + xml_str = "%s" % self.ifname + self.updates_cmd.append("interface %s" % self.ifname) + if self.state == "present": + self.updates_cmd.append("isis enable %s" % self.instance_id) + + if self.leveltype: + if self.leveltype == "level_1": + xml_str += "level_1" + self.updates_cmd.append("isis circuit-level level-1") + elif self.leveltype == "level_2": + xml_str += "level_2" + self.updates_cmd.append("isis circuit-level level-2") + elif self.leveltype == "level_1_2": + xml_str += "level_1_2" + self.updates_cmd.append("isis circuit-level level-1-2") + if self.level1dispriority is not None: + xml_str += "%s" % self.level1dispriority + self.updates_cmd.append("isis dis-priority %s level-1" % self.level1dispriority) + if self.level2dispriority is not None: + xml_str += "%s" % self.level2dispriority + self.updates_cmd.append("isis dis-priority %s level-2" % self.level2dispriority) + if self.p2pnegotiationmode: + if self.p2pnegotiationmode == "2_way": + xml_str += "2_way" + self.updates_cmd.append("isis ppp-negotiation 2-way") + elif self.p2pnegotiationmode == "3_way": + xml_str += "3_way" + self.updates_cmd.append("isis ppp-negotiation 3-way") + elif self.p2pnegotiationmode == "3_wayonly": + xml_str += "3_wayonly" + self.updates_cmd.append("isis ppp-negotiation only") + if self.level1cost is not None: + xml_str += "%s" % self.level1cost + self.updates_cmd.append("isis cost %s level-1" % self.level1cost) + if self.level2cost is not None: + xml_str += "%s" % self.level2cost + self.updates_cmd.append("isis cost %s level-2" % self.level2cost) + + else: + # absent + self.updates_cmd.append("undo isis enable") + if self.leveltype and self.leveltype == instance.get("circuitLevelType"): + xml_str += "level_1_2" + self.updates_cmd.append("undo isis circuit-level") + if self.level1dispriority is not None and self.level1dispriority == instance.get("level1DisPriority"): + xml_str += "64" + self.updates_cmd.append("undo isis dis-priority %s level-1" % self.level1dispriority) + if self.level2dispriority is not None and self.level2dispriority == instance.get("level2dispriority"): + xml_str += "64" + self.updates_cmd.append("undo isis dis-priority %s level-2" % self.level2dispriority) + if self.p2pnegotiationmode and self.p2pnegotiationmode == instance.get("p2pNegotiationMode"): + xml_str += "" + self.updates_cmd.append("undo isis ppp-negotiation") + if self.level1cost is not None and self.level1cost == instance.get("level1Cost"): + xml_str += "" + self.updates_cmd.append("undo isis cost %s level-1" % self.level1cost) + if self.level2cost is not None and self.level2cost == instance.get("level2Cost"): + xml_str += "" + self.updates_cmd.append("undo isis cost %s level-2" % self.level2cost) + + if self.silentenable and instance.get("silentEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis silent") + elif not self.silentenable and instance.get("silentEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis silent") + + if self.silentcost and instance.get("silentCost", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis silent advertise-zero-cost") + elif not self.silentcost and instance.get("silentCost", "false") == "true": + xml_str += "false" + + if self.typep2penable and instance.get("typeP2pEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis circuit-type p2p") + elif not self.typep2penable and instance.get("typeP2pEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis circuit-type") + + if self.snpacheck and instance.get("snpaCheck", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis circuit-type p2p strict-snpa-check") + elif not self.snpacheck and instance.get("snpaCheck", "false") == "true": + xml_str += "false" + + if self.p2ppeeripignore and instance.get("p2pPeerIPIgnore", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis peer-ip-ignore") + elif not self.p2ppeeripignore and instance.get("p2pPeerIPIgnore", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis peer-ip-ignore") + + if self.ppposicpcheckenable and instance.get("pPPOsicpCheckEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis ppp-osicp-check") + elif not self.ppposicpcheckenable and instance.get("pPPOsicpCheckEnable", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis ppp-osicp-check") + if self.bfdstaticen and instance.get("bfdStaticEn", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis bfd static") + elif not self.bfdstaticen and instance.get("bfdStaticEn", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis bfd static") + if self.bfdblocken and instance.get("bfdBlockEn", "false") == "false": + xml_str += "true" + self.updates_cmd.append("isis bfd block") + elif not self.bfdblocken and instance.get("bfdBlockEn", "false") == "true": + xml_str += "false" + self.updates_cmd.append("undo isis bfd block") + + if self.state == "present": + if self.bfdstaticen is not None or self.bfdblocken is not None: + return CE_NC_MERGE_ISIS_BFDINTERFACE % (self.instance_id, xml_str) + return CE_NC_MERGE_ISIS_INTERFACE % (self.instance_id, xml_str) + else: + if self.bfdstaticen is not None or self.bfdblocken is not None: + return CE_NC_DELETE_ISIS_BFDINTERFACE % (self.instance_id, xml_str) + return CE_NC_DELETE_ISIS_INTERFACE % (self.instance_id, xml_str) + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + + # check instance id + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check level1dispriority + if self.level1dispriority is not None: + if self.level1dispriority < 0 or self.level1dispriority > 127: + self.module.fail_json(msg="Error: level1dispriority is not ranges from 0 to 127.") + + if self.level2dispriority is not None: + if self.level2dispriority < 0 or self.level2dispriority > 127: + self.module.fail_json(msg="Error: level2dispriority is not ranges from 0 to 127.") + + if self.level1cost is not None: + if self.level1cost < 0 or self.level1cost > 16777215: + self.module.fail_json(msg="Error: level1cost is not ranges from 0 to 16777215.") + + if self.level2cost is not None: + if self.level2cost < 0 or self.level2cost > 16777215: + self.module.fail_json(msg="Error: level2cost is not ranges from 0 to 16777215.") + + def get_proposed(self): + """get proposed info""" + self.proposed["instance_id"] = self.instance_id + self.proposed["ifname"] = self.ifname + self.proposed["leveltype"] = self.leveltype + self.proposed["level1dispriority"] = self.level1dispriority + self.proposed["level2dispriority"] = self.level2dispriority + self.proposed["silentenable"] = self.silentenable + self.proposed["silentcost"] = self.silentcost + self.proposed["typep2penable"] = self.typep2penable + self.proposed["snpacheck"] = self.snpacheck + self.proposed["p2pnegotiationmode"] = self.p2pnegotiationmode + self.proposed["p2ppeeripignore"] = self.p2ppeeripignore + self.proposed["ppposicpcheckenable"] = self.ppposicpcheckenable + self.proposed["level1cost"] = self.level1cost + self.proposed["level2cost"] = self.level2cost + self.proposed["bfdstaticen"] = self.bfdstaticen + self.proposed["bfdblocken"] = self.bfdblocken + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + else: + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + else: + self.end_state["instance"] = isis_dict.get("instance") + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + xml_str += self.config_session() + + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + instance_id=dict(required=True, type='int'), + ifname=dict(required=True, type='str'), + leveltype=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + level1dispriority=dict(required=False, type='int'), + level2dispriority=dict(required=False, type='int'), + silentenable=dict(required=False, type='bool'), + silentcost=dict(required=False, type='bool'), + typep2penable=dict(required=False, type='bool'), + snpacheck=dict(required=False, type='bool'), + p2pnegotiationmode=dict(required=False, type='str', choices=['2_way', '3_way', '3_wayonly']), + p2ppeeripignore=dict(required=False, type='bool'), + ppposicpcheckenable=dict(required=False, type='bool'), + level1cost=dict(required=False, type='int'), + level2cost=dict(required=False, type='int'), + bfdstaticen=dict(required=False, type='bool'), + bfdblocken=dict(required=False, type='bool'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_Instance(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_view.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_view.py new file mode 100644 index 00000000..aa5da948 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_is_is_view.py @@ -0,0 +1,1952 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_is_is_view +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages isis view configuration on HUAWEI CloudEngine devices. +description: + - Manages isis process id, creates a isis instance id or deletes a process id + on HUAWEI CloudEngine devices. +options: + coststyle: + description: + - Specifies the cost style. + type: str + choices: ['narrow', 'wide', 'transition', 'ntransition', 'wtransition'] + cost_type: + description: + - Specifies the cost type. + type: str + choices: ['external', 'internal'] + defaultmode: + description: + - Specifies the default mode. + type: str + choices: ['always', 'matchDefault', 'matchAny'] + export_policytype: + description: + - Specifies the default mode. + type: str + choices: ['aclNumOrName', 'ipPrefix', 'routePolicy'] + export_protocol: + description: + - Specifies the export router protocol. + type: str + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all'] + impotr_leveltype: + description: + - Specifies the export router protocol. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + islevel: + description: + - Specifies the isis level. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + level_type: + description: + - Specifies the isis level type. + type: str + choices: ['level_1', 'level_2', 'level_1_2'] + penetration_direct: + description: + - Specifies the penetration direct. + type: str + choices: ['level2-level1', 'level1-level2'] + protocol: + description: + - Specifies the protocol. + type: str + choices: ['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all'] + aclnum_or_name: + description: + - Specifies the acl number or name for isis. + type: str + allow_filter: + description: + - Specifies the alow filter or not. + type: bool + allow_up_down: + description: + - Specifies the alow up or down. + type: bool + autocostenable: + description: + - Specifies the alow auto cost enable. + type: bool + autocostenablecompatible: + description: + - Specifies the alow auto cost enable compatible. + type: bool + avoid_learning: + description: + - Specifies the alow avoid learning. + type: bool + bfd_min_tx: + description: + - Specifies the bfd min sent package. + type: int + bfd_min_rx: + description: + - Specifies the bfd min received package. + type: int + bfd_multiplier_num: + description: + - Specifies the bfd multiplier number. + type: int + cost: + description: + - Specifies the bfd cost. + type: int + description: + description: + - Specifies description of isis. + type: str + enablelevel1tolevel2: + description: + - Enable level1 to level2. + type: bool + export_aclnumorname: + description: + - Specifies export acl number or name. + type: str + export_ipprefix: + description: + - Specifies export ip prefix. + type: str + export_processid: + description: + - Specifies export process id. + type: int + export_routepolicyname: + description: + - Specifies export route policy name. + type: str + import_aclnumorname: + description: + - Specifies import acl number or name. + type: str + import_cost: + description: + - Specifies import cost. + type: int + import_ipprefix: + description: + - Specifies import ip prefix. + type: str + import_route_policy: + description: + - Specifies import route policy. + type: str + import_routepolicy_name: + description: + - Specifies import route policy name. + type: str + import_routepolicyname: + description: + - Specifies import route policy name. + type: str + import_tag: + description: + - Specifies import tag. + type: int + inheritcost: + description: + - Enable inherit cost. + type: bool + instance_id: + description: + - Specifies instance id. + type: int + ip_address: + description: + - Specifies ip address. + type: str + ip_prefix_name: + description: + - Specifies ip prefix name. + type: str + max_load: + description: + - Specifies route max load. + type: int + mode_routepolicyname: + description: + - Specifies the mode of route polic yname. + type: str + mode_tag: + description: + - Specifies the tag of mode. + type: int + netentity: + description: + - Specifies the netentity. + type: str + permitibgp: + description: + - Specifies the permitibgp. + type: bool + processid: + description: + - Specifies the process id. + type: int + relaxspfLimit: + description: + - Specifies enable the relax spf limit. + type: bool + route_policy_name: + description: + - Specifies the route policy name. + type: str + stdbandwidth: + description: + - Specifies the std band width. + type: int + stdlevel1cost: + description: + - Specifies the std level1 cost. + type: int + stdlevel2cost: + description: + - Specifies the std level2 cost. + type: int + tag: + description: + - Specifies the isis tag. + type: int + weight: + description: + - Specifies the isis weight. + type: int + preference_value: + description: + - Specifies the preference value. + type: int + state: + description: + - Determines whether the config should be present or not on the device. + default: present + type: str + choices: ['present', 'absent'] +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +''' + +EXAMPLES = ''' + - name: Set isis description + community.network.ce_is_is_view: + instance_id: 3 + description: abcdeggfs + state: present + + - name: Set isis islevel + community.network.ce_is_is_view: + instance_id: 3 + islevel: level_1 + state: present + - name: Set isis coststyle + community.network.ce_is_is_view: + instance_id: 3 + coststyle: narrow + state: present + + - name: Set isis stdlevel1cost + community.network.ce_is_is_view: + instance_id: 3 + stdlevel1cost: 63 + state: present + + - name: Set isis stdlevel2cost + community.network.ce_is_is_view: + instance_id: 3 + stdlevel2cost: 63 + state: present + + - name: Set isis stdbandwidth + community.network.ce_is_is_view: + instance_id: 3 + stdbandwidth: 1 + state: present +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "state": "present" + } +existing: + description: k/v pairs of existing configuration + returned: always + type: dict + sample: { + "session": {} + } +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: { + "session": { + "addrType": "IPV4", + "createType": "SESS_STATIC", + "destAddr": null, + "outIfName": "10GE1/0/1", + "sessName": "bfd_l2link", + "srcAddr": null, + "useDefaultIp": "true", + "vrfName": null + } + } +updates: + description: commands sent to the device + returned: always + type: list + sample: [ + "bfd bfd_l2link bind peer-ip default-ip interface 10ge1/0/1" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import sys +import socket +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_ISIS = """ + + + %s + + +""" + +CE_NC_GET_ISIS_INSTANCE = """ + + + %s + + + + + + + + + + + +""" + +CE_NC_GET_ISIS_ENTITY = """ + + + %s + + + + + + + +""" + +CE_NC_CREAT_ISIS_ENTITY = """ + + + %s + + + %s + + + + +""" + +CE_NC_DELATE_ISIS_ENTITY = """ + + + %s + + + %s + + + + +""" + +CE_NC_GET_ISIS_PREFERENCE = """ + + + %s + + + + + + + + + + + + +""" + +CE_NC_MREGE_ISIS_PREFERENCE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_PREFERENCE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + + + + + +""" + +CE_NC_MERGE_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + %s + + + + +""" + +CE_NC_DELETE_ISIS_MAXLOAD = """ + + + %s + + + afIpv4 + 0 + 32 + + + + +""" + +CE_NC_GET_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + %s + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_NEXTHOP = """ + + + %s + + + afIpv4 + 0 + + + %s + 1 + + + + + + +""" + +CE_NC_GET_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_LEAKROUTELEVEL2 = """ + + + %s + + + afIpv4 + 0 + + + 0 + + + + false + + + + + + +""" + +CE_NC_GET_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_LEAKROUTELEVEL1 = """ + + + %s + + + afIpv4 + 0 + + + 0 + + + + false + false + + + + + + +""" + +CE_NC_GET_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_DELETE_ISIS_DEFAULTROUTE = """ + + + %s + + + afIpv4 + 0 + + + always + 0 + 0 + level_2 + false + + + + + + +""" + +CE_NC_GET_ISIS_IMPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_IMPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_EXPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_EXPORTROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_IMPORTIPROUTE = """ + + + %s + + + afIpv4 + 0 + + + + + + + + + + + + +""" + +CE_NC_MERGE_ISIS_IMPORTIPROUTE = """ + + + %s + + + afIpv4 + 0 + + + %s + + + + + + +""" + +CE_NC_GET_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + + + + + + + +""" + +CE_NC_MERGE_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + %s + + + + +""" + +CE_NC_DELETE_ISIS_BFDLINK = """ + + + %s + + + afIpv4 + 0 + 3 + 3 + 3 + + + + +""" + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def check_ip_addr(ipaddr): + """check ip address, Supports IPv4 and IPv6""" + + if not ipaddr or '\x00' in ipaddr: + return False + + try: + res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_NUMERICHOST) + return bool(res) + except socket.gaierror: + err = sys.exc_info()[1] + if err.args[0] == socket.EAI_NONAME: + return False + raise + + return True + + +def check_default_ip(ipaddr): + """check the default multicast IP address""" + + # The value ranges from 224.0.0.107 to 224.0.0.250 + if not check_ip_addr(ipaddr): + return False + + if ipaddr.count(".") != 3: + return False + + ips = ipaddr.split(".") + if ips[0] != "224" or ips[1] != "0" or ips[2] != "0": + return False + + if not ips[3].isdigit() or int(ips[3]) < 107 or int(ips[3]) > 250: + return False + + return True + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class ISIS_View(object): + """Manages ISIS Instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.instance_id = self.module.params['instance_id'] + self.description = self.module.params['description'] + self.islevel = self.module.params['islevel'] + self.coststyle = self.module.params['coststyle'] + self.relaxspfLimit = self.module.params['relaxspfLimit'] + self.stdlevel1cost = self.module.params['stdlevel1cost'] + self.stdlevel2cost = self.module.params['stdlevel2cost'] + self.stdbandwidth = self.module.params['stdbandwidth'] + self.autocostenable = self.module.params['autocostenable'] + self.autocostenablecompatible = self.module.params['autocostenablecompatible'] + self.netentity = self.module.params['netentity'] + self.preference_value = self.module.params['preference_value'] + self.route_policy_name = self.module.params['route_policy_name'] + self.max_load = self.module.params['max_load'] + self.ip_address = self.module.params['ip_address'] + self.weight = self.module.params['weight'] + self.aclnum_or_name = self.module.params['aclnum_or_name'] + self.ip_prefix_name = self.module.params['ip_prefix_name'] + self.import_routepolicy_name = self.module.params['import_routepolicy_name'] + self.tag = self.module.params['tag'] + self.allow_filter = self.module.params['allow_filter'] + self.allow_up_down = self.module.params['allow_up_down'] + self.penetration_direct = self.module.params['penetration_direct'] + self.enablelevel1tolevel2 = self.module.params['enablelevel1tolevel2'] + self.defaultmode = self.module.params['defaultmode'] + self.mode_routepolicyname = self.module.params['mode_routepolicyname'] + self.cost = self.module.params['cost'] + self.mode_tag = self.module.params['mode_tag'] + self.level_type = self.module.params['level_type'] + self.avoid_learning = self.module.params['avoid_learning'] + self.protocol = self.module.params['protocol'] + self.processid = self.module.params['processid'] + self.cost_type = self.module.params['cost_type'] + self.import_cost = self.module.params['import_cost'] + self.import_tag = self.module.params['import_tag'] + self.impotr_leveltype = self.module.params['impotr_leveltype'] + self.import_route_policy = self.module.params['import_route_policy'] + self.inheritcost = self.module.params['inheritcost'] + self.permitibgp = self.module.params['permitibgp'] + self.avoid_learning = self.module.params['avoid_learning'] + self.export_protocol = self.module.params['export_protocol'] + self.export_policytype = self.module.params['export_policytype'] + self.export_processid = self.module.params['export_processid'] + self.export_aclnumorname = self.module.params['export_aclnumorname'] + self.export_ipprefix = self.module.params['export_ipprefix'] + self.export_routepolicyname = self.module.params['export_routepolicyname'] + self.import_aclnumorname = self.module.params['import_aclnumorname'] + self.import_ipprefix = self.module.params['import_ipprefix'] + self.import_routepolicyname = self.module.params['import_routepolicyname'] + self.bfd_min_rx = self.module.params['bfd_min_rx'] + self.bfd_min_tx = self.module.params['bfd_min_tx'] + self.bfd_multiplier_num = self.module.params['bfd_multiplier_num'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.isis_dict = dict() + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + mutually_exclusive = [["stdlevel1cost", "stdlevel2cost"], + ["aclnum_or_name", "ip_prefix_name", "import_routepolicy_name"], + ["export_aclnumorname", "import_ipprefix", "import_routepolicyname"]] + required_together = [('ip_address', 'weight')] + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=mutually_exclusive, + required_together=required_together, + supports_check_mode=True) + + def get_isis_dict(self): + """bfd config dict""" + + isis_dict = dict() + isis_dict["instance"] = dict() + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_INSTANCE % self.instance_id)) + + if self.netentity: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_ENTITY % self.instance_id)) + + if self.route_policy_name or self.preference_value: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_PREFERENCE % self.instance_id)) + if self.max_load: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_MAXLOAD % self.instance_id)) + if self.ip_address: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_NEXTHOP % self.instance_id)) + if self.penetration_direct and self.penetration_direct == "level2-level1": + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_LEAKROUTELEVEL2 % self.instance_id)) + elif self.penetration_direct and self.penetration_direct == "level1-level2": + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_LEAKROUTELEVEL1 % self.instance_id)) + elif self.defaultmode: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_DEFAULTROUTE % self.instance_id)) + elif self.protocol: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_IMPORTROUTE % self.instance_id)) + elif self.export_protocol: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_EXPORTROUTE % self.instance_id)) + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_BFDLINK % self.instance_id)) + elif self.import_aclnumorname or self.import_ipprefix or self.import_ipprefix: + conf_str = CE_NC_GET_ISIS % ( + (CE_NC_GET_ISIS_IMPORTIPROUTE % self.instance_id)) + xml_str = get_nc_config(self.module, conf_str) + + if "" in xml_str: + return isis_dict + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get bfd global info + if self.netentity: + glb = root.find("isiscomm/isSites/isSite/isNetEntitys/isNetEntity") + elif self.route_policy_name or self.preference_value: + glb = root.find("isiscomm/isSites/isSite//isSiteMTs/isSiteMT/isPreferences/isPreference") + elif self.max_load: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT") + elif self.ip_address: + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isNextHopWeights/isNextHopWeight") + elif self.penetration_direct and self.penetration_direct == "level2-level1": + glb = root.find("isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isLeakRouteLevel2ToLevel1s/isLeakRouteLevel2ToLevel1") + elif self.penetration_direct and self.penetration_direct == "level1-level2": + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isLeakRouteLevel1ToLevel2s/isLeakRouteLevel1ToLevel2") + elif self.defaultmode: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isDefaultRoutes/isDefaultRoute") + elif self.protocol: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isImportRoutes/isImportRoute") + elif self.export_protocol: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isFilterExports/isFilterExport") + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT") + elif self.import_aclnumorname or self.import_ipprefix or self.import_ipprefix: + glb = root.find( + "isiscomm/isSites/isSite/isSiteMTs/isSiteMT/isFilterImports/isFilterImport") + else: + glb = root.find("isiscomm/isSites/isSite") + + if glb is not None: + for attr in glb: + isis_dict["instance"][attr.tag] = attr.text + + return isis_dict + + def config_session(self): + """configures bfd session""" + + xml_str = "" + instance = self.isis_dict["instance"] + if not self.instance_id: + return xml_str + xml_str = "%s" % self.instance_id + self.updates_cmd.append("isis %s" % self.instance_id) + cmd_list = list() + + if self.state == "present": + if self.description and self.description != instance.get("description"): + xml_str += "%s" % self.description + self.updates_cmd.append("description %s" % self.description) + + if self.islevel and self.islevel != instance.get("isLevel"): + xml_str += "%s" % self.islevel + self.updates_cmd.append("is-level %s" % self.islevel) + + if self.coststyle: + if self.coststyle != instance.get("costStyle"): + xml_str += "%s" % self.coststyle + self.updates_cmd.append("cost-style %s" % self.coststyle) + if self.relaxspfLimit and instance.get("relaxSpfLimit", "false") == "false": + xml_str += "true" + self.updates_cmd.append("cost-style %s relax-spf-limit" % self.coststyle) + elif not self.relaxspfLimit and instance.get("relaxSpfLimit", "false") == "true": + xml_str += "false" + self.updates_cmd.append("cost-style %s" % self.coststyle) + + if self.stdlevel1cost and str(self.stdlevel1cost) != instance.get("stdLevel1Cost"): + xml_str += "%s" % self.stdlevel1cost + self.updates_cmd.append("circuit-cost %s level-1" % self.stdlevel1cost) + + if self.stdlevel2cost and str(self.stdlevel2cost) != instance.get("stdLevel2Cost"): + xml_str += "%s" % self.stdlevel2cost + self.updates_cmd.append("circuit-cost %s level-2" % self.stdlevel2cost) + + if self.stdbandwidth and str(self.stdbandwidth) != instance.get("stdbandwidth"): + xml_str += "%s" % self.stdbandwidth + self.updates_cmd.append("bandwidth-reference %s" % self.stdbandwidth) + + if self.netentity and self.netentity != instance.get("netEntity"): + xml_str = CE_NC_CREAT_ISIS_ENTITY % (self.instance_id, self.netentity) + self.updates_cmd.append("network-entity %s" % self.netentity) + + if self.preference_value or self.route_policy_name: + xml_str = "" + cmd_session = "preference" + if self.preference_value and str(self.preference_value) != instance.get("preferenceValue"): + xml_str = "%s" % self.preference_value + cmd_session += " %s" % self.preference_value + if self.route_policy_name and self.route_policy_name != instance.get("routePolicyName"): + xml_str += "%s" % self.route_policy_name + cmd_session += " route-policy %s" % self.route_policy_name + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + xml_str = CE_NC_MREGE_ISIS_PREFERENCE % (self.instance_id, xml_str) + + if self.max_load and str(self.max_load) != instance.get("maxLoadBalancing"): + xml_str = CE_NC_MERGE_ISIS_MAXLOAD % (self.instance_id, self.max_load) + self.updates_cmd.append("maximum load-balancing %s" % self.max_load) + + if self.ip_address: + xml_str = CE_NC_MERGE_ISIS_NEXTHOP % (self.instance_id, self.ip_address, self.weight) + self.updates_cmd.append("nexthop %s weight %s" % (self.ip_address, self.weight)) + + if self.penetration_direct: + xml_str = "" + if self.penetration_direct == "level2-level1": + cmd_session = "import-route isis level-2 into level-1" + elif self.penetration_direct == "level1-level2": + cmd_session = "import-route isis level-1 into level-2" + if self.aclnum_or_name: + xml_str = "%s" % self.aclnum_or_name + xml_str += "aclNumOrName" + if isinstance(self.aclnum_or_name, int): + cmd_session += " filter-policy %s" % self.aclnum_or_name + elif isinstance(self.aclnum_or_name, str): + cmd_session += " filter-policy acl-name %s" % self.aclnum_or_name + if self.ip_prefix_name: + xml_str = "%s" % self.ip_prefix_name + xml_str += "ipPrefix" + cmd_session += " filter-policy ip-prefix %s" % self.ip_prefix_name + if self.import_routepolicy_name: + xml_str = "%s" % self.import_routepolicy_name + xml_str += "routePolicy" + cmd_session += " filter-policy route-policy %s" % self.import_routepolicy_name + if self.tag: + xml_str += "%s" % self.tag + cmd_session += " tag %s" % self.tag + if self.allow_filter or self.allow_up_down: + cmd_session += " direct" + if self.allow_filter: + xml_str += "true" + cmd_session += " allow-filter-policy" + if self.allow_up_down: + xml_str += "true" + cmd_session += " allow-up-down-bit" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + if self.enablelevel1tolevel2: + xml_str += "true" + self.updates_cmd.append("undo import-route isis level-1 into level-2 disable") + + if self.defaultmode: + cmd_session = "default-route-advertise" + if self.defaultmode == "always": + xml_str = "always" + cmd_session += " always" + elif self.defaultmode == "matchDefault": + xml_str = "matchDefault" + cmd_session += " match default" + elif self.defaultmode == "matchAny": + xml_str = "matchAny" + xml_str += "routePolicy" + xml_str += "%s" % self.mode_routepolicyname + cmd_session += " route-policy %s" % self.mode_routepolicyname + if self.cost is not None: + xml_str += "%s" % self.cost + cmd_session += " cost %s" % self.cost + if self.mode_tag: + xml_str += "%s" % self.mode_tag + cmd_session += " tag %s" % self.mode_tag + if self.level_type: + if self.level_type == "level_1": + xml_str += "level_1" + cmd_session += " level-1" + elif self.level_type == "level_2": + xml_str += "level_2" + cmd_session += " level-2" + elif self.level_type == "level_1_2": + xml_str += "level_1_2" + cmd_session += " level-1-2" + if self.avoid_learning: + xml_str += "true" + cmd_session += " avoid-learning" + elif not self.avoid_learning: + xml_str += "false" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.protocol: + cmd_session = "import-route" + if self.protocol == "rip": + xml_str = "rip" + cmd_session += " rip" + elif self.protocol == "isis": + xml_str = "isis" + cmd_session += " isis" + elif self.protocol == "ospf": + xml_str = "ospf" + cmd_session += " ospf" + elif self.protocol == "static": + xml_str = "static" + cmd_session += " static" + elif self.protocol == "direct": + xml_str = "direct" + cmd_session += " direct" + elif self.protocol == "bgp": + xml_str = "bgp" + cmd_session += " bgp" + if self.permitibgp: + xml_str += "true" + cmd_session += " permit-ibgp" + if self.protocol == "rip" or self.protocol == "isis" or self.protocol == "ospf": + xml_str += "%s" % self.processid + cmd_session += " %s" % self.processid + if self.inheritcost: + xml_str += "%s" % self.inheritcost + cmd_session += " inherit-cost" + if self.cost_type: + if self.cost_type == "external": + xml_str += "external" + cmd_session += " cost-type external" + elif self.cost_type == "internal": + xml_str += "internal" + cmd_session += " cost-type internal" + if self.import_cost: + xml_str += "%s" % self.import_cost + cmd_session += " cost %s" % self.import_cost + if self.import_tag: + xml_str += "%s" % self.import_tag + cmd_session += " tag %s" % self.import_tag + if self.import_route_policy: + xml_str += "routePolicy" + xml_str += "%s" % self.import_route_policy + cmd_session += " route-policy %s" % self.import_route_policy + if self.impotr_leveltype: + if self.impotr_leveltype == "level_1": + cmd_session += " level-1" + elif self.impotr_leveltype == "level_2": + cmd_session += " level-2" + elif self.impotr_leveltype == "level_1_2": + cmd_session += " level-1-2" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + xml_str = "" + self.updates_cmd.append("bfd all-interfaces enable") + cmd_session = "bfd all-interfaces" + if self.bfd_min_rx: + xml_str += "%s" % self.bfd_min_rx + cmd_session += " min-rx-interval %s" % self.bfd_min_rx + if self.bfd_min_tx: + xml_str += "%s" % self.bfd_min_tx + cmd_session += " min-tx-interval %s" % self.bfd_min_tx + if self.bfd_multiplier_num: + xml_str += "%s" % self.bfd_multiplier_num + cmd_session += " detect-multiplier %s" % self.bfd_multiplier_num + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.export_protocol: + cmd_session = "filter-policy" + if self.export_aclnumorname: + xml_str = "aclNumOrName" + xml_str += "%s" % self.export_aclnumorname + if isinstance(self.export_aclnumorname, int): + cmd_session += " %s" % self.export_aclnumorname + elif isinstance(self.export_aclnumorname, str): + cmd_session += " acl-name %s" % self.export_aclnumorname + if self.export_ipprefix: + xml_str = "ipPrefix" + xml_str += "%s" % self.export_ipprefix + cmd_session += " ip-prefix %s" % self.export_ipprefix + if self.export_routepolicyname: + xml_str = "routePolicy" + xml_str += "%s" % self.export_routepolicyname + cmd_session += " route-policy %s" % self.export_routepolicyname + xml_str += "%s" % self.export_protocol + cmd_session += " export %s" % self.export_protocol + if self.export_processid is not None: + xml_str += "%s" % self.export_processid + cmd_session += " %s" % self.export_processid + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.import_ipprefix or self.import_aclnumorname or self.import_routepolicyname: + cmd_session = "filter-policy" + if self.import_aclnumorname: + xml_str = "aclNumOrName" + xml_str += "%s" % self.import_aclnumorname + if isinstance(self.import_aclnumorname, int): + cmd_session += " %s" % self.import_aclnumorname + elif isinstance(self.import_aclnumorname, str): + cmd_session += " acl-name %s" % self.import_aclnumorname + if self.import_ipprefix: + xml_str = "ipPrefix" + xml_str += "%s" % self.import_ipprefix + cmd_session += " ip-prefix %s" % self.import_ipprefix + if self.import_routepolicyname: + xml_str = "routePolicy" + xml_str += "%s" % self.import_routepolicyname + cmd_session += " route-policy %s" % self.import_routepolicyname + cmd_session += "import" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + else: + # absent + if self.description and self.description == instance.get("description"): + xml_str += "%s" % self.description + self.updates_cmd.append("undo description") + + if self.islevel and self.islevel == instance.get("isLevel"): + xml_str += "level_1_2" + self.updates_cmd.append("undo is-level") + + if self.coststyle and self.coststyle == instance.get("costStyle"): + xml_str += "%s" % ("narrow") + xml_str += "false" + self.updates_cmd.append("undo cost-style") + + if self.stdlevel1cost and str(self.stdlevel1cost) == instance.get("stdLevel1Cost"): + xml_str += "%s" % self.stdlevel1cost + self.updates_cmd.append("undo circuit-cost %s level-1" % self.stdlevel1cost) + + if self.stdlevel2cost and str(self.stdlevel2cost) == instance.get("stdLevel2Cost"): + xml_str += "%s" % self.stdlevel2cost + self.updates_cmd.append("undo circuit-cost %s level-2" % self.stdlevel2cost) + + if self.stdbandwidth and str(self.stdbandwidth) == instance.get("stdbandwidth"): + xml_str += "100" + self.updates_cmd.append("undo bandwidth-reference") + + if self.netentity and self.netentity == instance.get("netEntity"): + xml_str = CE_NC_DELATE_ISIS_ENTITY % (self.instance_id, self.netentity) + self.updates_cmd.append("undo network-entity %s" % self.netentity) + + if self.preference_value or self.route_policy_name: + xml_str = "" + if self.preference_value and str(self.preference_value) == instance.get("preferenceValue"): + xml_str = "%s" % self.preference_value + if self.route_policy_name and self.route_policy_name == instance.get("routePolicyName"): + xml_str += "%s" % self.route_policy_name + self.updates_cmd.append("undo preference") + elif not self.preference_value and self.route_policy_name and self.route_policy_name == instance.get("routePolicyName"): + xml_str = "%s" % self.route_policy_name + self.updates_cmd.append("undo preference") + xml_str = CE_NC_DELETE_ISIS_PREFERENCE % (self.instance_id, xml_str) + + if self.max_load and str(self.max_load) == instance.get("maxLoadBalancing"): + xml_str = CE_NC_DELETE_ISIS_MAXLOAD % self.instance_id + self.updates_cmd.append("undo maximum load-balancing") + + if self.ip_address: + xml_str = CE_NC_DELETE_ISIS_NEXTHOP % (self.instance_id, self.ip_address) + self.updates_cmd.append("undo nexthop %s" % self.ip_address) + + if self.penetration_direct: + if self.penetration_direct == "level2-level1": + self.updates_cmd.append("undo import-route isis level-2 into level-1") + elif self.penetration_direct == "level1-level2": + self.updates_cmd.append("undo import-route isis level-1 into level-2") + self.updates_cmd.append("import-route isis level-1 into level-2 disable") + + if self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num is not None: + xml_str = CE_NC_DELETE_ISIS_BFDLINK % self.instance_id + self.updates_cmd.append("undo bfd all-interfaces enable") + cmd_session = "undo bfd all-interfaces" + if self.bfd_min_rx: + cmd_session += " min-rx-interval %s" % self.bfd_min_rx + if self.bfd_min_tx: + cmd_session += " min-tx-interval %s" % self.bfd_min_tx + if self.bfd_multiplier_num: + cmd_session += " detect-multiplier %s" % self.bfd_multiplier_num + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.defaultmode: + xml_str = CE_NC_DELETE_ISIS_DEFAULTROUTE % self.instance_id + self.updates_cmd.append("undo default-route-advertise") + + if self.protocol: + if self.protocol == "rip" or self.protocol == "isis" or self.protocol == "ospf": + self.updates_cmd.append("undo import-route %s %s" % (self.protocol, self.processid)) + else: + self.updates_cmd.append("undo import-route %s" % self.protocol) + + if self.export_protocol: + cmd_session = "undo filter-policy" + if self.export_aclnumorname: + if isinstance(self.export_aclnumorname, int): + cmd_session += " %s" % self.export_aclnumorname + elif isinstance(self.export_aclnumorname, str): + cmd_session += " acl-name %s" % self.export_aclnumorname + if self.export_ipprefix: + cmd_session += " ip-prefix %s" % self.export_ipprefix + if self.export_routepolicyname: + cmd_session += " route-policy %s" % self.export_routepolicyname + cmd_session += " export %s" % self.export_protocol + if self.export_processid is not None: + cmd_session += " %s" % self.export_processid + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + if self.import_ipprefix or self.import_aclnumorname or self.import_routepolicyname: + cmd_session = "undo filter-policy" + if self.import_aclnumorname: + if isinstance(self.import_aclnumorname, int): + cmd_session += " %s" % self.import_aclnumorname + elif isinstance(self.import_aclnumorname, str): + cmd_session += " acl-name %s" % self.import_aclnumorname + if self.import_ipprefix: + cmd_session += " ip-prefix %s" % self.import_ipprefix + if self.import_routepolicyname: + cmd_session += " route-policy %s" % self.import_routepolicyname + cmd_session += " import" + cmd_list.insert(0, cmd_session) + self.updates_cmd.extend(cmd_list) + + if self.autocostenable and instance.get("stdAutoCostEnable", "false") == "false": + xml_str += "true" + self.updates_cmd.append("auto-cost enable") + elif not self.autocostenable and instance.get("stdAutoCostEnable", "false") == "true": + xml_str += "false" + xml_str += "false" + self.updates_cmd.append("undo auto-cost enable") + + if self.autocostenable: + if self.autocostenablecompatible and instance.get("stdAutoCostEnableCompatible", "false") == "false": + xml_str += "true" + self.updates_cmd.append("auto-cost enable compatible") + elif not self.autocostenablecompatible and instance.get("stdAutoCostEnableCompatible", "false") == "true": + xml_str += "false" + self.updates_cmd.append("auto-cost enable") + + if self.state == "present": + if self.netentity or self.preference_value or self.route_policy_name or self.max_load or self.ip_address: + return xml_str + elif self.penetration_direct: + if self.penetration_direct == "level2-level1": + return CE_NC_MERGE_ISIS_LEAKROUTELEVEL2 % (self.instance_id, xml_str) + elif self.penetration_direct == "level1-level2": + return CE_NC_MERGE_ISIS_LEAKROUTELEVEL1 % (self.instance_id, xml_str) + elif self.defaultmode: + return CE_NC_MERGE_ISIS_DEFAULTROUTE % (self.instance_id, xml_str) + elif self.protocol: + return CE_NC_MERGE_ISIS_IMPORTROUTE % (self.instance_id, xml_str) + elif self.export_protocol: + return CE_NC_MERGE_ISIS_EXPORTROUTE % (self.instance_id, xml_str) + elif self.import_routepolicyname or self.import_aclnumorname or self.import_ipprefix: + return CE_NC_MERGE_ISIS_IMPORTIPROUTE % (self.instance_id, xml_str) + elif self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num: + return CE_NC_MERGE_ISIS_BFDLINK % (self.instance_id, xml_str) + else: + return '' + xml_str + '' + else: + if self.netentity or self.preference_value or self.route_policy_name or self.max_load \ + or self.ip_address or self.defaultmode or self.bfd_min_rx or self.bfd_min_tx or self.bfd_multiplier_num is not None: + return xml_str + else: + return '' + xml_str + '' + + def netconf_load_config(self, xml_str): + """load bfd config by netconf""" + + if not xml_str: + return + if xml_str == "%s" % self.instance_id: + pass + else: + xml_cfg = """ + + + %s + + """ % xml_str + set_nc_config(self.module, xml_cfg) + self.changed = True + + def check_params(self): + """Check all input params""" + levelcost = 16777215 + if not self.instance_id: + self.module.fail_json(msg="Error: Missing required arguments: instance_id.") + + if self.instance_id: + if self.instance_id < 1 or self.instance_id > 4294967295: + self.module.fail_json(msg="Error: Instance id is not ranges from 1 to 4294967295.") + + # check description + if self.description: + if len(self.description) < 1 or len(self.description) > 80: + self.module.fail_json(msg="Error: description is invalid.") + + # + if self.stdbandwidth: + if self.stdbandwidth < 1 or self.stdbandwidth > 2147483648: + self.module.fail_json(msg="Error: stdbandwidth is not ranges from 1 to 2147483648.") + + if self.relaxspfLimit is not None and not self.coststyle: + self.module.fail_json(msg="Error: relaxspfLimit must set after coststyle.") + + if self.coststyle: + if self.coststyle != "wide" and self.coststyle != "wtransition": + levelcost = 63 + else: + levelcost = 16777215 + if self.stdlevel1cost: + if self.stdlevel1cost < 1 or self.stdlevel1cost > levelcost: + self.module.fail_json(msg="Error: stdlevel1cost is not ranges from 1 to %s." % levelcost) + + if self.stdlevel2cost: + if self.stdlevel2cost < 1 or self.stdlevel2cost > levelcost: + self.module.fail_json(msg="Error: stdlevel2cost is not ranges from 1 to %s." % levelcost) + + if self.coststyle: + if self.coststyle != "ntransition" and self.coststyle != "transition": + if self.relaxspfLimit: + self.module.fail_json(msg="Error: relaxspfLimit can not be set while the coststyle is not ntransition or transition") + + if self.autocostenablecompatible: + if not self.autocostenable: + self.module.fail_json(msg="Error: you shoule enable the autocostenable first.") + + if self.preference_value: + if self.preference_value < 1 or self.preference_value > 255: + self.module.fail_json(msg="Error: preference_value is not ranges from 1 to 255.") + + if self.route_policy_name: + if len(self.route_policy_name) < 1 or len(self.route_policy_name) > 200: + self.module.fail_json(msg="Error: route_policy_name is invalid.") + + if self.max_load: + if self.max_load < 1 or self.max_load > 32: + self.module.fail_json(msg="Error: max_load is not ranges from 1 to 32.") + + if self.weight: + if self.weight < 1 or self.weight > 254: + self.module.fail_json(msg="Error: weight is not ranges from 1 to 254.") + + if self.aclnum_or_name: + if isinstance(self.aclnum_or_name, int): + if self.aclnum_or_name < 2000 or self.aclnum_or_name > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.aclnum_or_name, str): + if len(self.aclnum_or_name) < 1 or len(self.aclnum_or_name) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + if self.ip_prefix_name: + if len(self.ip_prefix_name) < 1 or len(self.ip_prefix_name) > 169: + self.module.fail_json(msg="Error: ip_prefix_name is invalid.") + if self.import_routepolicy_name: + if len(self.import_routepolicy_name) < 1 or len(self.import_routepolicy_name) > 200: + self.module.fail_json(msg="Error: import_routepolicy_name is invalid.") + if self.tag: + if self.tag < 1 or self.tag > 4294967295: + self.module.fail_json(msg="Error: tag is not ranges from 1 to 4294967295.") + + if self.mode_routepolicyname: + if len(self.mode_routepolicyname) < 1 or len(self.mode_routepolicyname) > 200: + self.module.fail_json(msg="Error: mode_routepolicyname is invalid.") + if self.cost is not None: + if self.cost < 0 or self.cost > 4261412864: + self.module.fail_json(msg="Error: cost is not ranges from 0 to 4261412864.") + if self.mode_tag: + if self.mode_tag < 1 or self.mode_tag > 4294967295: + self.module.fail_json(msg="Error: mode_tag is not ranges from 1 to 4294967295.") + + if self.processid is not None: + if self.processid < 0 or self.processid > 4294967295: + self.module.fail_json(msg="Error: processid is not ranges from 0 to 4294967295.") + + if self.import_cost is not None: + if self.import_cost < 0 or self.import_cost > 4261412864: + self.module.fail_json(msg="Error: import_cost is not ranges from 0 to 4261412864.") + + if self.import_tag: + if self.import_tag < 1 or self.import_tag > 4294967295: + self.module.fail_json(msg="Error: import_tag is not ranges from 1 to 4294967295.") + + if self.export_aclnumorname: + if isinstance(self.export_aclnumorname, int): + if self.export_aclnumorname < 2000 or self.export_aclnumorname > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.export_aclnumorname, str): + if len(self.export_aclnumorname) < 1 or len(self.export_aclnumorname) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + + if self.export_processid: + if self.export_processid < 1 or self.export_processid > 4294967295: + self.module.fail_json(msg="Error: export_processid is not ranges from 1 to 4294967295.") + + if self.export_ipprefix: + if len(self.export_ipprefix) < 1 or len(self.export_ipprefix) > 169: + self.module.fail_json(msg="Error: export_ipprefix is invalid.") + + if self.export_routepolicyname: + if len(self.export_routepolicyname) < 1 or len(self.export_routepolicyname) > 200: + self.module.fail_json(msg="Error: export_routepolicyname is invalid.") + + if self.bfd_min_rx: + if self.bfd_min_rx < 50 or self.bfd_min_rx > 1000: + self.module.fail_json(msg="Error: bfd_min_rx is not ranges from 50 to 1000.") + + if self.bfd_min_tx: + if self.bfd_min_tx < 50 or self.bfd_min_tx > 1000: + self.module.fail_json(msg="Error: bfd_min_tx is not ranges from 50 to 1000.") + + if self.bfd_multiplier_num: + if self.bfd_multiplier_num < 3 or self.bfd_multiplier_num > 50: + self.module.fail_json(msg="Error: bfd_multiplier_num is not ranges from 3 to 50.") + + if self.import_routepolicyname: + if len(self.import_routepolicyname) < 1 or len(self.import_routepolicyname) > 200: + self.module.fail_json(msg="Error: import_routepolicyname is invalid.") + + if self.import_aclnumorname: + if isinstance(self.import_aclnumorname, int): + if self.import_aclnumorname < 2000 or self.import_aclnumorname > 2999: + self.module.fail_json(msg="Error: acl_num is not ranges from 2000 to 2999.") + elif isinstance(self.import_aclnumorname, str): + if len(self.import_aclnumorname) < 1 or len(self.import_aclnumorname) > 32: + self.module.fail_json(msg="Error: acl_name is invalid.") + + def get_proposed(self): + """get proposed info""" + # base config + self.proposed["instance_id"] = self.instance_id + self.proposed["description"] = self.description + self.proposed["islevel"] = self.islevel + self.proposed["coststyle"] = self.coststyle + self.proposed["relaxspfLimit"] = self.relaxspfLimit + self.proposed["stdlevel1cost"] = self.stdlevel1cost + self.proposed["stdlevel2cost"] = self.stdlevel2cost + self.proposed["stdbandwidth"] = self.stdbandwidth + self.proposed["autocostenable"] = self.autocostenable + self.proposed["autocostenablecompatible"] = self.autocostenablecompatible + self.proposed["netentity"] = self.netentity + self.proposed["preference_value"] = self.preference_value + self.proposed["route_policy_name"] = self.route_policy_name + self.proposed["max_load"] = self.max_load + self.proposed["ip_address"] = self.ip_address + self.proposed["weight"] = self.weight + self.proposed["penetration_direct"] = self.penetration_direct + self.proposed["aclnum_or_name"] = self.aclnum_or_name + self.proposed["ip_prefix_name"] = self.ip_prefix_name + self.proposed["import_routepolicy_name"] = self.import_routepolicy_name + self.proposed["tag"] = self.tag + self.proposed["allow_filter"] = self.allow_filter + self.proposed["allow_up_down"] = self.allow_up_down + self.proposed["enablelevel1tolevel2"] = self.enablelevel1tolevel2 + self.proposed["protocol"] = self.protocol + self.proposed["processid"] = self.processid + self.proposed["cost_type"] = self.cost_type + self.proposed["import_cost"] = self.import_cost + self.proposed["import_tag"] = self.import_tag + self.proposed["import_route_policy"] = self.import_route_policy + self.proposed["impotr_leveltype"] = self.impotr_leveltype + self.proposed["inheritcost"] = self.inheritcost + self.proposed["permitibgp"] = self.permitibgp + self.proposed["export_protocol"] = self.export_protocol + self.proposed["export_policytype"] = self.export_policytype + self.proposed["export_processid"] = self.export_processid + self.proposed["export_aclnumorname"] = self.export_aclnumorname + self.proposed["export_ipprefix"] = self.export_ipprefix + self.proposed["export_routepolicyname"] = self.export_routepolicyname + self.proposed["import_aclnumorname"] = self.import_aclnumorname + self.proposed["import_ipprefix"] = self.import_ipprefix + self.proposed["import_routepolicyname"] = self.import_routepolicyname + self.proposed["bfd_min_rx"] = self.bfd_min_rx + self.proposed["bfd_min_tx"] = self.bfd_min_tx + self.proposed["bfd_multiplier_num"] = self.bfd_multiplier_num + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.isis_dict: + self.existing["instance"] = None + else: + self.existing["instance"] = self.isis_dict.get("instance") + + def get_end_state(self): + """get end state info""" + + isis_dict = self.get_isis_dict() + if not isis_dict: + self.end_state["instance"] = None + else: + self.end_state["instance"] = isis_dict.get("instance") + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.isis_dict = self.get_isis_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.instance_id: + xml_str += self.config_session() + # update to device + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + instance_id=dict(required=True, type='int'), + description=dict(required=False, type='str'), + islevel=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + coststyle=dict(required=False, type='str', choices=['narrow', 'wide', 'transition', 'ntransition', 'wtransition']), + relaxspfLimit=dict(required=False, type='bool'), + stdlevel1cost=dict(required=False, type='int'), + stdlevel2cost=dict(required=False, type='int'), + stdbandwidth=dict(required=False, type='int'), + autocostenable=dict(required=False, type='bool'), + autocostenablecompatible=dict(required=False, type='bool'), + netentity=dict(required=False, type='str'), + preference_value=dict(required=False, type='int'), + route_policy_name=dict(required=False, type='str'), + max_load=dict(required=False, type='int'), + ip_address=dict(required=False, type='str'), + weight=dict(required=False, type='int'), + penetration_direct=dict(required=False, type='str', choices=['level2-level1', 'level1-level2']), + aclnum_or_name=dict(required=False, type='str'), + ip_prefix_name=dict(required=False, type='str'), + import_routepolicy_name=dict(required=False, type='str'), + tag=dict(required=False, type='int'), + allow_filter=dict(required=False, type='bool'), + allow_up_down=dict(required=False, type='bool'), + enablelevel1tolevel2=dict(required=False, type='bool'), + defaultmode=dict(required=False, type='str', choices=['always', 'matchDefault', 'matchAny']), + mode_routepolicyname=dict(required=False, type='str'), + cost=dict(required=False, type='int'), + mode_tag=dict(required=False, type='int'), + level_type=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + avoid_learning=dict(required=False, type='bool'), + protocol=dict(required=False, type='str', choices=['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all']), + processid=dict(required=False, type='int'), + cost_type=dict(required=False, type='str', choices=['external', 'internal']), + import_cost=dict(required=False, type='int'), + import_tag=dict(required=False, type='int'), + import_route_policy=dict(required=False, type='str'), + impotr_leveltype=dict(required=False, type='str', choices=['level_1', 'level_2', 'level_1_2']), + inheritcost=dict(required=False, type='bool'), + permitibgp=dict(required=False, type='bool'), + export_protocol=dict(required=False, type='str', choices=['direct', 'ospf', 'isis', 'static', 'rip', 'bgp', 'ospfv3', 'all']), + export_policytype=dict(required=False, type='str', choices=['aclNumOrName', 'ipPrefix', 'routePolicy']), + export_processid=dict(required=False, type='int'), + export_aclnumorname=dict(required=False, type='str'), + export_ipprefix=dict(required=False, type='str'), + export_routepolicyname=dict(required=False, type='str'), + import_aclnumorname=dict(required=False, type='str'), + import_ipprefix=dict(required=False, type='str'), + import_routepolicyname=dict(required=False, type='str'), + bfd_min_rx=dict(required=False, type='int'), + bfd_min_tx=dict(required=False, type='int'), + bfd_multiplier_num=dict(required=False, type='int'), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + + module = ISIS_View(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lacp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lacp.py new file mode 100644 index 00000000..fdfb2a6d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lacp.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ce_lacp +version_added: '0.2.0' +short_description: Manages Eth-Trunk interfaces on HUAWEI CloudEngine switches +description: + - Manages Eth-Trunk specific configuration parameters on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - C(state=absent) removes the Eth-Trunk config and interface if it already exists. If members to be removed are not explicitly + passed, all existing members (if any), are removed, and Eth-Trunk removed. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + trunk_id: + description: + - Eth-Trunk interface number. + The value is an integer. + The value range depends on the assign forward eth-trunk mode command. + When 256 is specified, the value ranges from 0 to 255. + When 512 is specified, the value ranges from 0 to 511. + When 1024 is specified, the value ranges from 0 to 1023. + type: int + mode: + description: + - Specifies the working mode of an Eth-Trunk interface. + default: null + choices: ['Manual','Dynamic','Static'] + type: str + preempt_enable: + description: + - Specifies lacp preempt enable of Eth-Trunk lacp. + The value is an boolean 'true' or 'false'. + type: bool + state_flapping: + description: + - Lacp dampening state-flapping. + type: bool + port_id_extension_enable: + description: + - Enable the function of extending the LACP negotiation port number. + type: bool + unexpected_mac_disable: + description: + - Lacp dampening unexpected-mac disable. + type: bool + system_id: + description: + - Link Aggregation Control Protocol System ID,interface Eth-Trunk View. + - Formate 'X-X-X',X is hex(a,aa,aaa, or aaaa) + type: str + timeout_type: + description: + - Lacp timeout type,that may be 'Fast' or 'Slow'. + choices: ['Slow', 'Fast'] + type: str + fast_timeout: + description: + - When lacp timeout type is 'Fast', user-defined time can be a number(3~90). + type: int + mixed_rate_link_enable: + description: + - Value of max active linknumber. + type: bool + preempt_delay: + description: + - Value of preemption delay time. + type: int + collector_delay: + description: + - Value of delay time in units of 10 microseconds. + type: int + max_active_linknumber: + description: + - Max active linknumber in link aggregation group. + type: int + select: + description: + - Select priority or speed to preempt. + choices: ['Speed', 'Prority'] + type: str + priority: + description: + - The priority of eth-trunk member interface. + type: int + global_priority: + description: + - Configure lacp priority on system-view. + type: int + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] + type: str +''' +EXAMPLES = r''' + - name: Ensure Eth-Trunk100 is created, and set to mode lacp-static + community.network.ce_lacp: + trunk_id: 100 + mode: 'lacp-static' + state: present + - name: Ensure Eth-Trunk100 is created, add two members, and set global priority to 1231 + community.network.ce_lacp: + trunk_id: 100 + global_priority: 1231 + state: present + - name: Ensure Eth-Trunk100 is created, and set mode to Dynamic and configure other options + community.network.ce_lacp: + trunk_id: 100 + mode: Dynamic + preempt_enable: True, + state_flapping: True, + port_id_extension_enable: True, + unexpected_mac_disable: True, + timeout_type: Fast, + fast_timeout: 123, + mixed_rate_link_enable: True, + preempt_delay: 23, + collector_delay: 33, + state: present +''' + +RETURN = r''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"trunk_id": "100", "members": ['10GE1/0/24','10GE1/0/25'], "mode": "lacp-static"} +existing: + description: k/v pairs of existing Eth-Trunk + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "manual"} +end_state: + description: k/v pairs of Eth-Trunk info after module execution + returned: always + type: dict + sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [ + {"memberIfName": "10GE1/0/24", "memberIfState": "Down"}, + {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}], + "min_links": "1", "mode": "lacp-static"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Eth-Trunk 100", + "mode lacp-static", + "interface 10GE1/0/25", + "eth-trunk 100"] +''' + +import xml.etree.ElementTree as ET +import re +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +LACP = {'trunk_id': 'ifName', + 'mode': 'workMode', + 'preempt_enable': 'isSupportPrmpt', + 'state_flapping': 'dampStaFlapEn', + 'port_id_extension_enable': 'trunkPortIdExt', + 'unexpected_mac_disable': 'dampUnexpMacEn', + 'system_id': 'trunkSysMac', + 'timeout_type': 'rcvTimeoutType', + 'fast_timeout': 'fastTimeoutUserDefinedValue', + 'mixed_rate_link_enable': 'mixRateEnable', + 'preempt_delay': 'promptDelay', + 'collector_delay': 'collectMaxDelay', + 'max_active_linknumber': 'maxActiveNum', + 'select': 'selectPortStd', + 'weight': 'weight', + 'priority': 'portPriority', + 'global_priority': 'priority' + } + + +def has_element(parent, xpath): + """get or create a element by xpath""" + ele = parent.find('./' + xpath) + if ele is not None: + return ele + ele = parent + lpath = xpath.split('/') + for p in lpath: + e = parent.find('.//' + p) + if e is None: + e = ET.SubElement(ele, p) + ele = e + return ele + + +def bulid_xml(kwargs, operation='get'): + """create a xml tree by dictionary with operation,get,merge and delete""" + attrib = {'xmlns': "http://www.huawei.com/netconf/vrp", + 'content-version': "1.0", 'format-version': "1.0"} + + root = ET.Element('ifmtrunk') + for key in kwargs.keys(): + if key in ('global_priority',): + xpath = 'lacpSysInfo' + elif key in ('priority',): + xpath = 'TrunkIfs/TrunkIf/TrunkMemberIfs/TrunkMemberIf/lacpPortInfo/lacpPort' + elif key in ['preempt_enable', 'timeout_type', 'fast_timeout', 'select', 'preempt_delay', + 'max_active_linknumber', 'collector_delay', 'mixed_rate_link_enable', + 'state_flapping', 'unexpected_mac_disable', 'system_id', + 'port_id_extension_enable']: + xpath = 'TrunkIfs/TrunkIf/lacpTrunk' + elif key in ('trunk_id', 'mode'): + xpath = 'TrunkIfs/TrunkIf' + if xpath != '': + parent = has_element(root, xpath) + element = ET.SubElement(parent, LACP[key]) + if operation == 'merge': + parent.attrib = dict(operation=operation) + element.text = str(kwargs[key]) + if key == 'mode': + element.text = str(kwargs[key]) + if key == 'trunk_id': + element.text = 'Eth-Trunk' + str(kwargs[key]) + root.attrib = attrib + config = ET.tostring(root) + if operation == 'merge' or operation == 'delete': + return '%s' % to_native(config) + return '%s' % to_native(config) + + +def check_param(kwargs): + """check args list,the boolean or list values cloud not be checked,because they are limit by args list in main""" + + for key in kwargs: + if kwargs[key] is None: + continue + if key == 'trunk_id': + value = int(kwargs[key]) + # maximal value is 1024,although the value is limit by command 'assign forward eth-trunk mode ' + if value < 0 or value > 1024: + return 'Error: Wrong Value of Eth-Trunk interface number' + elif key == 'system_id': + # X-X-X ,X is hex(4 bit) + if not re.match(r'[0-9a-f]{1,4}\-[0-9a-f]{1,4}\-[0-9a-f]{1,4}', kwargs[key], re.IGNORECASE): + return 'Error: The system-id is invalid.' + values = kwargs[key].split('-') + flag = 0 + # all 'X' is 0,that is invalid value + for v in values: + if len(v.strip('0')) < 1: + flag += 1 + if flag == 3: + return 'Error: The system-id is invalid.' + elif key == 'timeout_type': + # select a value from choices, choices=['Slow','Fast'],it's checked by AnsibleModule + pass + elif key == 'fast_timeout': + value = int(kwargs[key]) + if value < 3 or value > 90: + return 'Error: Wrong Value of timeout,fast user-defined value<3-90>' + rtype = str(kwargs.get('timeout_type')) + if rtype == 'Slow': + return 'Error: Short timeout period for receiving packets is need,when user define the time.' + elif key == 'preempt_delay': + value = int(kwargs[key]) + if value < 0 or value > 180: + return 'Error: Value of preemption delay time is from 0 to 180' + elif key == 'collector_delay': + value = int(kwargs[key]) + if value < 0 or value > 65535: + return 'Error: Value of collector delay time is from 0 to 65535' + elif key == 'max_active_linknumber': + value = int(kwargs[key]) + if value < 0 or value > 64: + return 'Error: Value of collector delay time is from 0 to 64' + elif key == 'priority' or key == 'global_priority': + value = int(kwargs[key]) + if value < 0 or value > 65535: + return 'Error: Value of priority is from 0 to 65535' + return 'ok' + + +def xml_to_dict(args): + """transfer xml string into dict """ + rdict = dict() + args = re.sub(r'xmlns=\".+?\"', '', args) + root = ET.fromstring(args) + ifmtrunk = root.find('.//ifmtrunk') + if ifmtrunk is not None: + try: + ifmtrunk_iter = ET.Element.iter(ifmtrunk) + except AttributeError: + ifmtrunk_iter = ifmtrunk.getiterator() + + for ele in ifmtrunk_iter: + if ele.text is not None and len(ele.text.strip()) > 0: + rdict[ele.tag] = ele.text + return rdict + + +def compare_config(module, kwarg_exist, kwarg_end): + """compare config between exist and end""" + dic_command = {'isSupportPrmpt': 'lacp preempt enable', + 'rcvTimeoutType': 'lacp timeout', # lacp timeout fast user-defined 23 + 'fastTimeoutUserDefinedValue': 'lacp timeout user-defined', + 'selectPortStd': 'lacp select', + 'promptDelay': 'lacp preempt delay', + 'maxActiveNum': 'lacp max active-linknumber', + 'collectMaxDelay': 'lacp collector delay', + 'mixRateEnable': 'lacp mixed-rate link enable', + 'dampStaFlapEn': 'lacp dampening state-flapping', + 'dampUnexpMacEn': 'lacp dampening unexpected-mac disable', + 'trunkSysMac': 'lacp system-id', + 'trunkPortIdExt': 'lacp port-id-extension enable', + 'portPriority': 'lacp priority', # interface 10GE1/0/1 + 'lacpMlagPriority': 'lacp m-lag priority', + 'lacpMlagSysId': 'lacp m-lag system-id', + 'priority': 'lacp priority' + } + rlist = list() + exist = set(kwarg_exist.keys()) + end = set(kwarg_end.keys()) + undo = exist - end + add = end - exist + update = end & exist + + for key in undo: + if key in dic_command: + rlist.append('undo ' + dic_command[key]) + for key in add: + if key in dic_command: + rlist.append(dic_command[key] + ' ' + kwarg_end[key]) + for key in update: + if kwarg_exist[key] != kwarg_end[key] and key in dic_command: + if kwarg_exist[key] == 'true' and kwarg_end[key] == 'false': + rlist.append('undo ' + dic_command[key]) + elif kwarg_exist[key] == 'false' and kwarg_end[key] == 'true': + rlist.append(dic_command[key]) + else: + rlist.append(dic_command[key] + ' ' + kwarg_end[key].lower()) + return rlist + + +class Lacp(object): + """ + Manages Eth-Trunk interfaces LACP. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.trunk_id = self.module.params['trunk_id'] + self.mode = self.module.params['mode'] + self.param = dict() + + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """ init AnsibleModule """ + + self.module = AnsibleModule( + argument_spec=self.spec, + mutually_exclusive=[['trunk_id', 'global_priority']], + required_one_of=[['trunk_id', 'global_priority']], + supports_check_mode=True) + + def check_params(self): + """check module params """ + for key in self.module.params.keys(): + if key in LACP.keys() and self.module.params[key] is not None: + self.param[key] = self.module.params[key] + if isinstance(self.module.params[key], bool): + self.param[key] = str(self.module.params[key]).lower() + msg = check_param(self.param) + if msg != 'ok': + self.module.fail_json(msg=msg) + + def get_existing(self): + """get existing""" + xml_str = bulid_xml(self.param) + xml = get_nc_config(self.module, xml_str) + return xml_to_dict(xml) + + def get_proposed(self): + """get proposed""" + proposed = dict(state=self.state) + proposed.update(self.param) + return proposed + + def get_end_state(self): + """ get end_state""" + xml_str = bulid_xml(self.param) + xml = get_nc_config(self.module, xml_str) + return xml_to_dict(xml) + + def work(self): + """worker""" + + self.check_params() + existing = self.get_existing() + proposed = self.get_proposed() + + # deal present or absent + if self.state == "present": + operation = 'merge' + else: + operation = 'delete' + + xml_str = bulid_xml(self.param, operation=operation) + set_nc_config(self.module, xml_str) + end_state = self.get_end_state() + + self.results['proposed'] = proposed + self.results['existing'] = existing + self.results['end_state'] = end_state + updates_cmd = compare_config(self.module, existing, end_state) + self.results['updates'] = updates_cmd + if updates_cmd: + self.results['changed'] = True + else: + self.results['changed'] = False + + self.module.exit_json(**self.results) + + +def main(): + + argument_spec = dict( + mode=dict(required=False, + choices=['Manual', 'Dynamic', 'Static'], + type='str'), + trunk_id=dict(required=False, type='int'), + preempt_enable=dict(required=False, type='bool'), + state_flapping=dict(required=False, type='bool'), + port_id_extension_enable=dict(required=False, type='bool'), + unexpected_mac_disable=dict(required=False, type='bool'), + system_id=dict(required=False, type='str'), + timeout_type=dict(required=False, type='str', choices=['Slow', 'Fast']), + fast_timeout=dict(required=False, type='int'), + mixed_rate_link_enable=dict(required=False, type='bool'), + preempt_delay=dict(required=False, type='int'), + collector_delay=dict(required=False, type='int'), + max_active_linknumber=dict(required=False, type='int'), + select=dict(required=False, type='str', choices=['Speed', 'Prority']), + priority=dict(required=False, type='int'), + global_priority=dict(required=False, type='int'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + module = Lacp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_link_status.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_link_status.py new file mode 100644 index 00000000..0e3c467b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_link_status.py @@ -0,0 +1,564 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_link_status +short_description: Get interface link status on HUAWEI CloudEngine switches. +description: + - Get interface link status on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - Current physical state shows an interface's physical status. + - Current link state shows an interface's link layer protocol status. + - Current IPv4 state shows an interface's IPv4 protocol status. + - Current IPv6 state shows an interface's IPv6 protocol status. + - Inbound octets(bytes) shows the number of bytes that an interface received. + - Inbound unicast(pkts) shows the number of unicast packets that an interface received. + - Inbound multicast(pkts) shows the number of multicast packets that an interface received. + - Inbound broadcast(pkts) shows the number of broadcast packets that an interface received. + - Inbound error(pkts) shows the number of error packets that an interface received. + - Inbound drop(pkts) shows the total number of packets that were sent to the interface but dropped by an interface. + - Inbound rate(byte/sec) shows the rate at which an interface receives bytes within an interval. + - Inbound rate(pkts/sec) shows the rate at which an interface receives packets within an interval. + - Outbound octets(bytes) shows the number of the bytes that an interface sent. + - Outbound unicast(pkts) shows the number of unicast packets that an interface sent. + - Outbound multicast(pkts) shows the number of multicast packets that an interface sent. + - Outbound broadcast(pkts) shows the number of broadcast packets that an interface sent. + - Outbound error(pkts) shows the total number of packets that an interface sent but dropped by the remote interface. + - Outbound drop(pkts) shows the number of dropped packets that an interface sent. + - Outbound rate(byte/sec) shows the rate at which an interface sends bytes within an interval. + - Outbound rate(pkts/sec) shows the rate at which an interface sends packets within an interval. + - Speed shows the rate for an Ethernet interface. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - For the interface parameter, you can enter C(all) to display information about all interfaces, + an interface type such as C(40GE) to display information about interfaces of the specified type, + or full name of an interface such as C(40GE1/0/22) or C(vlanif10) + to display information about the specific interface. + required: true +''' + +EXAMPLES = ''' + +- name: Link status test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Get specified interface link status information + community.network.ce_link_status: + interface: 40GE1/0/1 + provider: "{{ cli }}" + + - name: Get specified interface type link status information + community.network.ce_link_status: + interface: 40GE + provider: "{{ cli }}" + + - name: Get all interfaces link status information + community.network.ce_link_status: + interface: all + provider: "{{ cli }}" +''' + +RETURN = ''' +result: + description: Interface link status information + returned: always + type: dict + sample: { + "40ge2/0/8": { + "Current IPv4 state": "down", + "Current IPv6 state": "down", + "Current link state": "up", + "Current physical state": "up", + "Inbound broadcast(pkts)": "0", + "Inbound drop(pkts)": "0", + "Inbound error(pkts)": "0", + "Inbound multicast(pkts)": "20151", + "Inbound octets(bytes)": "7314813", + "Inbound rate(byte/sec)": "11", + "Inbound rate(pkts/sec)": "0", + "Inbound unicast(pkts)": "0", + "Outbound broadcast(pkts)": "1", + "Outbound drop(pkts)": "0", + "Outbound error(pkts)": "0", + "Outbound multicast(pkts)": "20152", + "Outbound octets(bytes)": "7235021", + "Outbound rate(byte/sec)": "11", + "Outbound rate(pkts/sec)": "0", + "Outbound unicast(pkts)": "0", + "Speed": "40GE" + } + } +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, get_nc_next + +CE_NC_GET_PORT_SPEED = """ + + + + + %s + + + + + + + +""" + +CE_NC_GET_INT_STATISTICS = """ + + + + + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +INTERFACE_ALL = 1 +INTERFACE_TYPE = 2 +INTERFACE_FULL_NAME = 3 + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + return 'ge' + elif interface.upper().startswith('10GE'): + return '10ge' + elif interface.upper().startswith('25GE'): + return '25ge' + elif interface.upper().startswith('4X10GE'): + return '4x10ge' + elif interface.upper().startswith('40GE'): + return '40ge' + elif interface.upper().startswith('100GE'): + return '100ge' + elif interface.upper().startswith('VLANIF'): + return 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + return 'loopback' + elif interface.upper().startswith('METH'): + return 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + return 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + return 'vbdif' + elif interface.upper().startswith('NVE'): + return 'nve' + elif interface.upper().startswith('TUNNEL'): + return 'tunnel' + elif interface.upper().startswith('ETHERNET'): + return 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + return 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + return 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + return 'stack-Port' + elif interface.upper().startswith('NULL'): + return 'null' + else: + return None + + +def is_ethernet_port(interface): + """Judge whether it is ethernet port""" + + ethernet_port = ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'meth'] + if_type = get_interface_type(interface) + if if_type in ethernet_port: + return True + return False + + +class LinkStatus(object): + """Get interface link status information""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface name + self.interface = self.module.params['interface'] + self.interface = self.interface.replace(' ', '').lower() + self.param_type = None + self.if_type = None + + # state + self.results = dict() + self.result = dict() + + def check_params(self): + """Check all input params""" + + if not self.interface: + self.module.fail_json(msg='Error: Interface name cannot be empty.') + + if self.interface and self.interface != 'all': + if not self.if_type: + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def show_result(self): + """Show result""" + + self.results['result'] = self.result + + self.module.exit_json(**self.results) + + def get_intf_dynamic_info(self, dyn_info, intf_name): + """Get interface dynamic information""" + + if not intf_name: + return + + if dyn_info: + for eles in dyn_info: + if eles.tag in ["ifPhyStatus", "ifV4State", "ifV6State", "ifLinkStatus"]: + if eles.tag == "ifPhyStatus": + self.result[intf_name][ + 'Current physical state'] = eles.text + elif eles.tag == "ifLinkStatus": + self.result[intf_name][ + 'Current link state'] = eles.text + elif eles.tag == "ifV4State": + self.result[intf_name][ + 'Current IPv4 state'] = eles.text + elif eles.tag == "ifV6State": + self.result[intf_name][ + 'Current IPv6 state'] = eles.text + + def get_intf_statistics_info(self, stat_info, intf_name): + """Get interface statistics information""" + + if not intf_name: + return + + if_type = get_interface_type(intf_name) + if if_type == 'fcoe-port' or if_type == 'nve' or if_type == 'tunnel' or \ + if_type == 'vbdif' or if_type == 'vlanif': + return + + if stat_info: + for eles in stat_info: + if eles.tag in ["receiveByte", "sendByte", "rcvUniPacket", "rcvMutiPacket", "rcvBroadPacket", + "sendUniPacket", "sendMutiPacket", "sendBroadPacket", "rcvErrorPacket", + "rcvDropPacket", "sendErrorPacket", "sendDropPacket"]: + if eles.tag == "receiveByte": + self.result[intf_name][ + 'Inbound octets(bytes)'] = eles.text + elif eles.tag == "rcvUniPacket": + self.result[intf_name][ + 'Inbound unicast(pkts)'] = eles.text + elif eles.tag == "rcvMutiPacket": + self.result[intf_name][ + 'Inbound multicast(pkts)'] = eles.text + elif eles.tag == "rcvBroadPacket": + self.result[intf_name][ + 'Inbound broadcast(pkts)'] = eles.text + elif eles.tag == "rcvErrorPacket": + self.result[intf_name][ + 'Inbound error(pkts)'] = eles.text + elif eles.tag == "rcvDropPacket": + self.result[intf_name][ + 'Inbound drop(pkts)'] = eles.text + elif eles.tag == "sendByte": + self.result[intf_name][ + 'Outbound octets(bytes)'] = eles.text + elif eles.tag == "sendUniPacket": + self.result[intf_name][ + 'Outbound unicast(pkts)'] = eles.text + elif eles.tag == "sendMutiPacket": + self.result[intf_name][ + 'Outbound multicast(pkts)'] = eles.text + elif eles.tag == "sendBroadPacket": + self.result[intf_name][ + 'Outbound broadcast(pkts)'] = eles.text + elif eles.tag == "sendErrorPacket": + self.result[intf_name][ + 'Outbound error(pkts)'] = eles.text + elif eles.tag == "sendDropPacket": + self.result[intf_name][ + 'Outbound drop(pkts)'] = eles.text + + def get_intf_cleared_stat(self, clr_stat, intf_name): + """Get interface cleared state information""" + + if not intf_name: + return + + if_type = get_interface_type(intf_name) + if if_type == 'fcoe-port' or if_type == 'nve' or if_type == 'tunnel' or \ + if_type == 'vbdif' or if_type == 'vlanif': + return + + if clr_stat: + for eles in clr_stat: + if eles.tag in ["inByteRate", "inPacketRate", "outByteRate", "outPacketRate"]: + if eles.tag == "inByteRate": + self.result[intf_name][ + 'Inbound rate(byte/sec)'] = eles.text + elif eles.tag == "inPacketRate": + self.result[intf_name][ + 'Inbound rate(pkts/sec)'] = eles.text + elif eles.tag == "outByteRate": + self.result[intf_name][ + 'Outbound rate(byte/sec)'] = eles.text + elif eles.tag == "outPacketRate": + self.result[intf_name][ + 'Outbound rate(pkts/sec)'] = eles.text + + def get_all_interface_info(self, intf_type=None): + """Get interface information by all or by interface type""" + + xml_str = CE_NC_GET_INT_STATISTICS % '' + con_obj = get_nc_next(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + intfs_info = root.findall("ifm/interfaces/interface") + if not intfs_info: + return + + intf_name = '' + flag = False + for eles in intfs_info: + if eles.tag == "interface": + for ele in eles: + if ele.tag in ["ifName", "ifDynamicInfo", "ifStatistics", "ifClearedStat"]: + if ele.tag == "ifName": + intf_name = ele.text.lower() + if intf_type: + if get_interface_type(intf_name) != intf_type.lower(): + break + else: + flag = True + self.init_interface_data(intf_name) + if is_ethernet_port(intf_name): + self.get_port_info(intf_name) + if ele.tag == "ifDynamicInfo": + self.get_intf_dynamic_info(ele, intf_name) + elif ele.tag == "ifStatistics": + self.get_intf_statistics_info(ele, intf_name) + elif ele.tag == "ifClearedStat": + self.get_intf_cleared_stat(ele, intf_name) + if intf_type and not flag: + self.module.fail_json( + msg='Error: %s interface type does not exist.' % intf_type.upper()) + + def get_interface_info(self): + """Get interface information""" + + xml_str = CE_NC_GET_INT_STATISTICS % self.interface.upper() + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + self.module.fail_json( + msg='Error: %s interface does not exist.' % self.interface.upper()) + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + intf_info = root.find("ifm/interfaces/interface") + if intf_info: + for eles in intf_info: + if eles.tag in ["ifDynamicInfo", "ifStatistics", "ifClearedStat"]: + if eles.tag == "ifDynamicInfo": + self.get_intf_dynamic_info(eles, self.interface) + elif eles.tag == "ifStatistics": + self.get_intf_statistics_info(eles, self.interface) + elif eles.tag == "ifClearedStat": + self.get_intf_cleared_stat(eles, self.interface) + + def init_interface_data(self, intf_name): + """Init interface data""" + + # init link status data + self.result[intf_name] = dict() + self.result[intf_name]['Current physical state'] = 'down' + self.result[intf_name]['Current link state'] = 'down' + self.result[intf_name]['Current IPv4 state'] = 'down' + self.result[intf_name]['Current IPv6 state'] = 'down' + self.result[intf_name]['Inbound octets(bytes)'] = '--' + self.result[intf_name]['Inbound unicast(pkts)'] = '--' + self.result[intf_name]['Inbound multicast(pkts)'] = '--' + self.result[intf_name]['Inbound broadcast(pkts)'] = '--' + self.result[intf_name]['Inbound error(pkts)'] = '--' + self.result[intf_name]['Inbound drop(pkts)'] = '--' + self.result[intf_name]['Inbound rate(byte/sec)'] = '--' + self.result[intf_name]['Inbound rate(pkts/sec)'] = '--' + self.result[intf_name]['Outbound octets(bytes)'] = '--' + self.result[intf_name]['Outbound unicast(pkts)'] = '--' + self.result[intf_name]['Outbound multicast(pkts)'] = '--' + self.result[intf_name]['Outbound broadcast(pkts)'] = '--' + self.result[intf_name]['Outbound error(pkts)'] = '--' + self.result[intf_name]['Outbound drop(pkts)'] = '--' + self.result[intf_name]['Outbound rate(byte/sec)'] = '--' + self.result[intf_name]['Outbound rate(pkts/sec)'] = '--' + self.result[intf_name]['Speed'] = '--' + + def get_port_info(self, interface): + """Get port information""" + + if_type = get_interface_type(interface) + if if_type == 'meth': + xml_str = CE_NC_GET_PORT_SPEED % interface.lower().replace('meth', 'MEth') + else: + xml_str = CE_NC_GET_PORT_SPEED % interface.upper() + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get link status information + root = ElementTree.fromstring(xml_str) + port_info = root.find("devm/ports/port") + if port_info: + for eles in port_info: + if eles.tag == "ethernetPort": + for ele in eles: + if ele.tag == 'speed': + self.result[interface]['Speed'] = ele.text + + def get_link_status(self): + """Get link status information""" + + if self.param_type == INTERFACE_FULL_NAME: + self.init_interface_data(self.interface) + self.get_interface_info() + if is_ethernet_port(self.interface): + self.get_port_info(self.interface) + elif self.param_type == INTERFACE_TYPE: + self.get_all_interface_info(self.interface) + else: + self.get_all_interface_info() + + def get_intf_param_type(self): + """Get the type of input interface parameter""" + + if self.interface == 'all': + self.param_type = INTERFACE_ALL + return + + if self.if_type == self.interface: + self.param_type = INTERFACE_TYPE + return + + self.param_type = INTERFACE_FULL_NAME + + def work(self): + """Worker""" + + self.if_type = get_interface_type(self.interface) + self.check_params() + self.get_intf_param_type() + self.get_link_status() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + ) + argument_spec.update(ce_argument_spec) + linkstatus_obj = LinkStatus(argument_spec) + linkstatus_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp.py new file mode 100644 index 00000000..13f6f7b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp.py @@ -0,0 +1,788 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- + +module: ce_lldp +version_added: '0.2.0' +short_description: Manages LLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages LLDP configuration on HUAWEI CloudEngine switches. +author: + - xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + lldpenable: + description: + - Set global LLDP enable state. + required: false + choices: ['enabled', 'disabled'] + type: str + mdnstatus: + description: + - Set global MDN enable state. + required: false + choices: ['rxOnly', 'disabled'] + type: str + interval: + description: + - Frequency at which LLDP advertisements are sent (in seconds). + required: false + type: int + hold_multiplier: + description: + - Time multiplier for device information in neighbor devices. + required: false + type: int + restart_delay: + description: + - Specifies the delay time of the interface LLDP module from disabled state to re enable. + required: false + type: int + transmit_delay: + description: + - Delay time for sending LLDP messages. + required: false + type: int + notification_interval: + description: + - Suppression time for sending LLDP alarm. + required: false + type: int + fast_count: + description: + - The number of LLDP messages sent to the neighbor nodes by the specified device. + required: false + type: int + mdn_notification_interval: + description: + - Delay time for sending MDN neighbor information change alarm. + required: false + type: int + management_address: + description: + - The management IP address of LLDP. + required: false + default: null + type: str + bind_name: + description: + - Binding interface name. + required: false + default: null + type: str + state: + description: + - Manage the state of the resource. + required: false + default: present + type: str + choices: ['present','absent'] +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + community.network.ce_lldp: + lldpenable: enabled + + - name: "Configure global MDN enable state" + community.network.ce_lldp: + mdnstatus: rxOnly + + - name: "Configure LLDP transmit interval and ensure global LLDP state is already enabled" + community.network.ce_lldp: + enable: enable + interval: 32 + + - name: "Configure LLDP transmit multiplier hold and ensure global LLDP state is already enabled" + community.network.ce_lldp: + enable: enable + hold_multiplier: 5 + + - name: "Configure the delay time of the interface LLDP module from disabled state to re enable" + community.network.ce_lldp: + enable: enable + restart_delay: 3 + + - name: "Reset the delay time for sending LLDP messages" + community.network.ce_lldp: + enable: enable + transmit_delay: 4 + + - name: "Configure device to send neighbor device information change alarm delay time" + community.network.ce_lldp: + lldpenable: enabled + notification_interval: 6 + + - name: "Configure the number of LLDP messages sent to the neighbor nodes by the specified device" + community.network.ce_lldp: + enable: enable + fast_count: 5 + + - name: "Configure the delay time for sending MDN neighbor information change alarm" + community.network.ce_lldp: + enable: enable + mdn_notification_interval: 6 + - name: "Configuring the management IP address of LLDP" + community.network.ce_lldp: + enable: enable + management_address: 10.1.0.1 + + - name: "Configuring LLDP to manage the binding relationship between IP addresses and interfaces" + community.network.ce_lldp: + enable: enable + bind_name: LoopBack2 +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "mdnstatus": "rxOnly", + "interval": "32", + "hold_multiplier": "5", + "restart_delay": "3", + "transmit_delay": "4", + "notification_interval": "6", + "fast_count": "5", + "mdn_notification_interval": "6", + "management_address": "10.1.0.1", + "bind_name": "LoopBack2", + "state": "present" + } +existing: + description: k/v pairs of existing global LLDP configuration. + returned: always + type: dict + sample: { + "lldpenable": "disabled", + "mdnstatus": "disabled" + } +end_state: + description: k/v pairs of global LLDP configuration after module execution. + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "mdnstatus": "rxOnly", + "interval": "32", + "hold_multiplier": "5", + "restart_delay": "3", + "transmit_delay": "4", + "notification_interval": "6", + "fast_count": "5", + "mdn_notification_interval": "6", + "management_address": "10.1.0.1", + "bind_name": "LoopBack2" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "lldp enable", + "lldp mdn enable", + "lldp transmit interval 32", + "lldp transmit multiplier 5", + "lldp restart 3", + "lldp transmit delay 4", + "lldp trap-interval 6", + "lldp fast-count 5", + "lldp mdn trap-interval 6", + "lldp management-address 10.1.0.1", + "lldp management-address bind interface LoopBack 2" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_GLOBAL_LLDP_CONFIG = """ + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER = """ + + + + +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HOLD_MULTIPLIER = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_RESTART_DELAY = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TRANSMIT_DELAY = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_NOTIFICATION_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_FAST_COUNT = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MDN_NOTIFICATION_INTERVAL = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MANAGEMENT_ADDRESS = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_BIND_NAME = """ + %s +""" + +CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL = """ + + + + +""" + + +class Lldp(object): + """Manage global lldp enable configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + self.lldpenable = self.module.params['lldpenable'] or None + self.interval = self.module.params['interval'] or None + self.mdnstatus = self.module.params['mdnstatus'] or None + self.hold_multiplier = self.module.params['hold_multiplier'] or None + self.restart_delay = self.module.params['restart_delay'] or None + self.transmit_delay = self.module.params['transmit_delay'] or None + self.notification_interval = self.module.params['notification_interval'] or None + self.fast_count = self.module.params['fast_count'] or None + self.mdn_notification_interval = self.module.params['mdn_notification_interval'] or None + self.management_address = self.module.params['management_address'] + self.bind_name = self.module.params['bind_name'] + self.state = self.module.params['state'] + self.lldp_conf = dict() + self.conf_exsit = False + self.conf_exsit_lldp = False + self.enable_flag = 0 + self.check_params() + self.existing_state_value = dict() + self.existing_end_state_value = dict() + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def is_valid_v4addr(self): + """check if ipv4 addr is valid""" + if self.management_address.find('.') != -1: + addr_list = self.management_address.split('.') + if self.management_address == "0.0.0.0": + self.module.fail_json(msg='Error: The management address is 0.0.0.0 .') + if len(addr_list) != 4: + self.module.fail_json(msg='Error: Invalid IPV4 address.') + for each_num in addr_list: + each_num_tmp = str(each_num) + if not each_num_tmp.isdigit(): + self.module.fail_json(msg='Error: The ip address is not digit.') + if (int(each_num) > 255) or (int(each_num) < 0): + self.module.fail_json( + msg='Error: The value of ip address is out of [0 - 255].') + else: + self.module.fail_json(msg='Error: Invalid IP address.') + + def check_params(self): + """Check all input params""" + + if self.interval: + if int(self.interval) < 5 or int(self.interval) > 32768: + self.module.fail_json( + msg='Error: The value of interval is out of [5 - 32768].') + + if self.hold_multiplier: + if int(self.hold_multiplier) < 2 or int(self.hold_multiplier) > 10: + self.module.fail_json( + msg='Error: The value of hold_multiplier is out of [2 - 10].') + + if self.restart_delay: + if int(self.restart_delay) < 1 or int(self.restart_delay) > 10: + self.module.fail_json( + msg='Error: The value of restart_delay is out of [1 - 10].') + + if self.transmit_delay: + if int(self.transmit_delay) < 1 or int(self.transmit_delay) > 8192: + self.module.fail_json( + msg='Error: The value of transmit_delay is out of [1 - 8192].') + + if self.notification_interval: + if int(self.notification_interval) < 5 or int(self.notification_interval) > 3600: + self.module.fail_json( + msg='Error: The value of notification_interval is out of [5 - 3600].') + + if self.fast_count: + if int(self.fast_count) < 1 or int(self.fast_count) > 8: + self.module.fail_json( + msg='Error: The value of fast_count is out of [1 - 8].') + + if self.mdn_notification_interval: + if int(self.mdn_notification_interval) < 5 or int(self.mdn_notification_interval) > 3600: + self.module.fail_json( + msg='Error: The value of mdn_notification_interval is out of [5 - 3600].') + + if self.management_address: + self.is_valid_v4addr() + + if self.bind_name: + if (len(self.bind_name) < 1) or (len(self.bind_name) > 63): + self.module.fail_json( + msg='Error: Bind_name length is between 1 and 63.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_lldp(self): + """Configure lldp enabled and mdn enabled parameters""" + + if self.state == 'present': + if (self.enable_flag == 1 and self.lldpenable == 'enabled') and not self.conf_exsit: + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if self.lldpenable == 'enabled' and not self.conf_exsit: + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if (self.enable_flag == 1) and not self.conf_exsit: + if self.mdnstatus: + xml_str = CE_NC_MERGE_GLOBA_MDNENABLE_CONFIG % self.mdnstatus + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "MDN_ENABLE_CONFIG") + + if (self.lldpenable == 'enabled' or self.enable_flag == 1) and not self.conf_exsit_lldp: + if self.hold_multiplier: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HOLD_MULTIPLIER % self.hold_multiplier) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_INTERVAL % self.interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.restart_delay: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_RESTART_DELAY % self.restart_delay) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.transmit_delay: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TRANSMIT_DELAY % self.transmit_delay) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.notification_interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_NOTIFICATION_INTERVAL % self.notification_interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.fast_count: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_FAST_COUNT % self.fast_count) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.mdn_notification_interval: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MDN_NOTIFICATION_INTERVAL % self.mdn_notification_interval) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.management_address: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_MANAGEMENT_ADDRESS % self.management_address) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.bind_name: + xml_str = CE_NC_MERGE_GLOBAL_LLDP_CONFIG_HEADER + \ + (CE_NC_MERGE_GLOBAL_LLDP_CONFIG_BIND_NAME % self.bind_name) + \ + CE_NC_MERGE_GLOBAL_LLDP_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_CONFIG_INTERVAL") + + if self.lldpenable == 'disabled' and not self.conf_exsit: + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_DISABLE_CONFIG") + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_lldp_exist_config(self): + """Get lldp existed configure""" + + lldp_config = list() + lldp_dict = dict() + + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable", "mdnStatus"]: + lldp_dict[ele_enable.tag] = ele_enable.text + + if self.state == "present": + cur_lldp_cfg = dict(lldpenable=lldp_dict['lldpEnable'], mdnstatus=lldp_dict['mdnStatus']) + exp_lldp_cfg = dict(lldpenable=self.lldpenable, mdnstatus=self.mdnstatus) + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + if cur_lldp_cfg == exp_lldp_cfg: + self.conf_exsit = True + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'], mdnstatus=lldp_dict['mdnStatus'])) + + conf_str = CE_NC_GET_GLOBAL_LLDP_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + pass + + else: + xml_str = conf_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("lldp/lldpSys/lldpSysParameter") + for nexthop in ntpsite: + for ele in nexthop: + if ele.tag in ["messageTxInterval", "messageTxHoldMultiplier", "reinitDelay", "txDelay", + "notificationInterval", "fastMessageCount", "mdnNotificationInterval", + "configManAddr", "bindifName"]: + lldp_dict[ele.tag] = ele.text + + if self.state == "present": + cur_ntp_cfg = dict(interval=lldp_dict['messageTxInterval'], + hold_multiplier=lldp_dict['messageTxHoldMultiplier'], + restart_delay=lldp_dict['reinitDelay'], + transmit_delay=lldp_dict['txDelay'], + notification_interval=lldp_dict['notificationInterval'], + fast_count=lldp_dict['fastMessageCount'], + mdn_notification_interval=lldp_dict['mdnNotificationInterval'], + management_address=lldp_dict['configManAddr'], + bind_name=lldp_dict['bindifName']) + + exp_ntp_cfg = dict(interval=self.interval, hold_multiplier=self.hold_multiplier, + restart_delay=self.restart_delay, transmit_delay=self.transmit_delay, + notification_interval=self.notification_interval, + fast_count=self.fast_count, mdn_notification_interval=self.mdn_notification_interval, + management_address=self.management_address, bind_name=self.bind_name) + + if cur_ntp_cfg == exp_ntp_cfg: + self.conf_exsit_lldp = True + + lldp_config.append(dict(interval=lldp_dict['messageTxInterval'], + hold_multiplier=lldp_dict['messageTxHoldMultiplier'], + restart_delay=lldp_dict['reinitDelay'], transmit_delay=lldp_dict['txDelay'], + notification_interval=lldp_dict['notificationInterval'], + fast_count=lldp_dict['fastMessageCount'], + mdn_notification_interval=lldp_dict['mdnNotificationInterval'], + management_address=lldp_dict['configManAddr'], + bind_name=lldp_dict['bindifName'])) + + tmp_dict = dict() + str_1 = str(lldp_config) + temp_1 = str_1.replace('[', '').replace(']', '').replace('{', '').replace('}', '').replace('\'', '') + if temp_1: + tmp_2 = temp_1.split(',') + for i in tmp_2: + tmp_value = re.match(r'(.*):(.*)', i) + key_tmp = tmp_value.group(1) + key_value = tmp_value.group(2) + tmp_dict[key_tmp] = key_value + return tmp_dict + + def get_existing(self): + """Get existing info""" + + self.existing = self.get_lldp_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.enable_flag == 1: + if self.lldpenable == 'enabled': + self.proposed = dict(lldpenable=self.lldpenable) + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + elif self.lldpenable == 'disabled': + self.proposed = dict(lldpenable=self.lldpenable) + self.changed = True + else: + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + else: + if self.lldpenable == 'enabled': + self.proposed = dict(lldpenable=self.lldpenable) + self.changed = True + if self.mdnstatus: + self.proposed = dict(mdnstatus=self.mdnstatus) + if self.enable_flag == 1 or self.lldpenable == 'enabled': + if self.interval: + self.proposed = dict(interval=self.interval) + if self.hold_multiplier: + self.proposed = dict(hold_multiplier=self.hold_multiplier) + if self.restart_delay: + self.proposed = dict(restart_delay=self.restart_delay) + if self.transmit_delay: + self.proposed = dict(transmit_delay=self.transmit_delay) + if self.notification_interval: + self.proposed = dict(notification_interval=self.notification_interval) + if self.fast_count: + self.proposed = dict(fast_count=self.fast_count) + if self.mdn_notification_interval: + self.proposed = dict(mdn_notification_interval=self.mdn_notification_interval) + if self.management_address: + self.proposed = dict(management_address=self.management_address) + if self.bind_name: + self.proposed = dict(bind_name=self.bind_name) + + def get_end_state(self): + """Get end state info""" + + self.end_state = self.get_lldp_exist_config() + existing_key_list = self.existing.keys() + end_state_key_list = self.end_state.keys() + for i in end_state_key_list: + for j in existing_key_list: + if i == j and self.existing[i] != self.end_state[j]: + self.changed = True + + def get_update_cmd(self): + """Get updated commands""" + + if self.conf_exsit and self.conf_exsit_lldp: + return + + if self.state == "present": + if self.lldpenable == "enabled": + self.updates_cmd.append("lldp enable") + + if self.mdnstatus: + self.updates_cmd.append("lldp mdn enable") + if self.mdnstatus == "rxOnly": + self.updates_cmd.append("lldp mdn enable") + else: + self.updates_cmd.append("undo lldp mdn enable") + if self.interval: + self.updates_cmd.append("lldp transmit interval %s" % self.interval) + if self.hold_multiplier: + self.updates_cmd.append("lldp transmit multiplier %s" % self.hold_multiplier) + if self.restart_delay: + self.updates_cmd.append("lldp restart %s" % self.restart_delay) + if self.transmit_delay: + self.updates_cmd.append("lldp transmit delay %s" % self.transmit_delay) + if self.notification_interval: + self.updates_cmd.append("lldp trap-interval %s" % self.notification_interval) + if self.fast_count: + self.updates_cmd.append("lldp fast-count %s" % self.fast_count) + if self.mdn_notification_interval: + self.updates_cmd.append("lldp mdn trap-interval %s" % self.mdn_notification_interval) + if self.management_address: + self.updates_cmd.append("lldp management-address %s" % self.management_address) + if self.bind_name: + self.updates_cmd.append("lldp management-address bind interface %s" % self.bind_name) + elif self.lldpenable == "disabled": + self.updates_cmd.append("undo lldp enable") + else: + if self.enable_flag == 1: + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + self.updates_cmd.append("lldp mdn enable") + else: + self.updates_cmd.append("undo lldp mdn enable") + if self.interval: + self.updates_cmd.append("lldp transmit interval %s" % self.interval) + if self.hold_multiplier: + self.updates_cmd.append("lldp transmit multiplier %s" % self.hold_multiplier) + if self.restart_delay: + self.updates_cmd.append("lldp restart %s" % self.restart_delay) + if self.transmit_delay: + self.updates_cmd.append("lldp transmit delay %s" % self.transmit_delay) + if self.notification_interval: + self.updates_cmd.append("lldp trap-interval %s" % self.notification_interval) + if self.fast_count: + self.updates_cmd.append("lldp fast-count %s" % self.fast_count) + if self.mdn_notification_interval: + self.updates_cmd.append("lldp mdn trap-interval %s" % self.mdn_notification_interval) + if self.management_address: + self.updates_cmd.append("lldp management-address %s" % self.management_address) + if self.bind_name: + self.updates_cmd.append("lldp management-address bind interface %s" % self.bind_name) + + def work(self): + """Execute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_lldp() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + lldpenable=dict(required=False, choices=['enabled', 'disabled']), + mdnstatus=dict(required=False, choices=['rxOnly', 'disabled']), + interval=dict(required=False, type='int'), + hold_multiplier=dict(required=False, type='int'), + restart_delay=dict(required=False, type='int'), + transmit_delay=dict(required=False, type='int'), + notification_interval=dict(required=False, type='int'), + fast_count=dict(required=False, type='int'), + mdn_notification_interval=dict(required=False, type='int'), + management_address=dict(required=False, type='str'), + bind_name=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + lldp_obj = Lldp(argument_spec) + lldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp_interface.py new file mode 100644 index 00000000..648d9118 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_lldp_interface.py @@ -0,0 +1,1381 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ce_lldp_interface +version_added: '0.2.0' +short_description: Manages INTERFACE LLDP configuration on HUAWEI CloudEngine switches. +description: + - Manages INTERFACE LLDP configuration on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + lldpenable: + description: + - Set global LLDP enable state. + type: str + choices: ['enabled', 'disabled'] + function_lldp_interface_flag: + description: + - Used to distinguish between command line functions. + type: str + choices: ['disableINTERFACE','tlvdisableINTERFACE','tlvenableINTERFACE','intervalINTERFACE'] + type_tlv_disable: + description: + - Used to distinguish between command line functions. + type: str + choices: ['basic_tlv', 'dot3_tlv'] + type_tlv_enable: + description: + - Used to distinguish between command line functions. + type: str + choices: ['dot1_tlv','dcbx'] + lldpadminstatus: + description: + - Set interface lldp enable state. + type: str + choices: ['txOnly', 'rxOnly', 'txAndRx', 'disabled'] + ifname: + description: + - Interface name. + type: str + txinterval: + description: + - LLDP send message interval. + type: int + txprotocolvlanid: + description: + - Set tx protocol vlan id. + type: int + txvlannameid: + description: + - Set tx vlan name id. + type: int + vlannametxenable: + description: + - Set vlan name tx enable or not. + type: bool + manaddrtxenable: + description: + - Make it able to send management address TLV. + type: bool + portdesctxenable: + description: + - Enabling the ability to send a description of TLV. + type: bool + syscaptxenable: + description: + - Enable the ability to send system capabilities TLV. + type: bool + sysdesctxenable: + description: + - Enable the ability to send system description TLV. + type: bool + sysnametxenable: + description: + - Enable the ability to send system name TLV. + type: bool + portvlantxenable: + description: + - Enable port vlan tx. + type: bool + protovlantxenable: + description: + - Enable protocol vlan tx. + type: bool + protoidtxenable: + description: + - Enable the ability to send protocol identity TLV. + type: bool + macphytxenable: + description: + - Enable MAC/PHY configuration and state TLV to be sent. + type: bool + linkaggretxenable: + description: + - Enable the ability to send link aggregation TLV. + type: bool + maxframetxenable: + description: + - Enable the ability to send maximum frame length TLV. + type: bool + eee: + description: + - Enable the ability to send EEE TLV. + type: bool + dcbx: + description: + - Enable the ability to send DCBX TLV. + type: bool + state: + description: + - Manage the state of the resource. + type: str + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + ce_lldp_interface_interface: + lldpenable: enabled + + - name: "Configure interface lldp enable state" + community.network.ce_lldp_interface: + function_lldp_interface_flag: disableINTERFACE + ifname: 10GE1/0/1 + lldpadminstatus: rxOnly + - name: "Configure LLDP transmit interval and ensure global LLDP state is already enabled" + community.network.ce_lldp_interface: + function_lldp_interface_flag: intervalINTERFACE + ifname: 10GE1/0/1 + txinterval: 4 + + - name: "Configure basic-tlv: management-address TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + manaddrtxenable: true + + - name: "Configure basic-tlv: prot description TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + portdesctxenable: true + + - name: "Configure basic-tlv: system capabilities TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + syscaptxenable: true + + - name: "Configure basic-tlv: system description TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + sysdesctxenable: true + + - name: "Configure basic-tlv: system name TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: basic_tlv + ifname: 10GE1/0/1 + sysnametxenable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, link aggregation TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + linkAggreTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, MAC/PHY configuration/status TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + macPhyTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, maximum frame size TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + maxFrameTxEnable: true + + - name: "TLV types that are forbidden to be published on the configuration interface, EEE TLV" + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvdisableINTERFACE + type_tlv_disable: dot3_tlv + ifname: 10GE1/0/1 + eee: true + + - name: "Configure the interface to publish an optional DCBX TLV type " + community.network.ce_lldp_interface: + function_lldp_interface_flag: tlvenableINTERFACE + ifname: 10GE1/0/1 + type_tlv_enable: dcbx +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "lldpadminstatus": "rxOnly", + "function_lldp_interface_flag": "tlvenableINTERFACE", + "type_tlv_enable": "dot1_tlv", + "ifname": "10GE1/0/1", + "state": "present" + } +existing: + description: k/v pairs of existing global LLDP configration + returned: always + type: dict + sample: { + "lldpenable": "disabled", + "ifname": "10GE1/0/1", + "lldpadminstatus": "txAndRx" + } +end_state: + description: k/v pairs of global DLDP configration after module execution + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "lldpadminstatus": "rxOnly", + "function_lldp_interface_flag": "tlvenableINTERFACE", + "type_tlv_enable": "dot1_tlv", + "ifname": "10GE1/0/1" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "lldp enable", + "interface 10ge 1/0/1", + "undo lldp disable", + "lldp tlv-enable dot1-tlv vlan-name 4", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_INTERFACE_LLDP_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_LLDP_CONFIG = """ + + + + + %s + %s + + + + +""" + +CE_NC_GET_INTERFACE_INTERVAl_CONFIG = """ + + + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_INTERVAl_CONFIG = """ + + + + + %s + + %s + + + + + +""" + +CE_NC_GET_INTERFACE_TLV_ENABLE_CONFIG = """ + + + + + + + + + + + + + +""" + +CE_NC_GET_INTERFACE_TLV_DISABLE_CONFIG = """ + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER = """ + + + + + %s + +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_PROTOIDTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_DCBX = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MANADDRTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_PORTDESCTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSCAPTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSDESCTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSNAMETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_LINKAGGRETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MACPHYTXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MAXFRAMETXENABLE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_EEE = """ + %s +""" + +CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL = """ + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('PORT-GROUP'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + return iftype.lower() + + +class Lldp_interface(object): + """Manage global lldp enable configuration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + self.lldpenable = self.module.params['lldpenable'] or None + self.function_lldp_interface_flag = self.module.params['function_lldp_interface_flag'] + self.type_tlv_disable = self.module.params['type_tlv_disable'] + self.type_tlv_enable = self.module.params['type_tlv_enable'] + self.ifname = self.module.params['ifname'] + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.ifname = self.module.params['ifname'] + self.lldpadminstatus = self.module.params['lldpadminstatus'] + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + self.ifname = self.module.params['ifname'] + self.manaddrtxenable = self.module.params['manaddrtxenable'] + self.portdesctxenable = self.module.params['portdesctxenable'] + self.syscaptxenable = self.module.params['syscaptxenable'] + self.sysdesctxenable = self.module.params['sysdesctxenable'] + self.sysnametxenable = self.module.params['sysnametxenable'] + if self.type_tlv_disable == 'dot3_tlv': + self.ifname = self.module.params['ifname'] + self.macphytxenable = self.module.params['macphytxenable'] + self.linkaggretxenable = self.module.params['linkaggretxenable'] + self.maxframetxenable = self.module.params['maxframetxenable'] + self.eee = self.module.params['eee'] + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + self.ifname = self.module.params['ifname'] + self.protoidtxenable = self.module.params['protoidtxenable'] + if self.type_tlv_enable == 'dcbx': + self.ifname = self.module.params['ifname'] + self.dcbx = self.module.params['dcbx'] + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + self.ifname = self.module.params['ifname'] + self.txinterval = self.module.params['txinterval'] + self.state = self.module.params['state'] + + self.lldp_conf = dict() + self.conf_disable_exsit = False + self.conf_interface_lldp_disable_exsit = False + self.conf_interval_exsit = False + self.conf_tlv_disable_exsit = False + self.conf_tlv_enable_exsit = False + self.enable_flag = 0 + self.check_params() + self.existing_state_value = dict() + self.existing_end_state_value = dict() + self.interface_lldp_info = list() + + # state + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_params(self): + """Check all input params""" + + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json(msg='Error: ifname name of %s is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json(msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.txinterval: + if int(self.txinterval) < 1 or int(self.txinterval) > 32768: + self.module.fail_json( + msg='Error: The value of txinterval is out of [1 - 32768].') + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'dot1_tlv': + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + def check_response(self, xml_str, xml_name): + """Check if response message is already OK""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_lldp_enable_pre_config(self): + """Get lldp enable configure""" + + lldp_dict = dict() + lldp_config = list() + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable"]: + lldp_dict[ele_enable.tag] = ele_enable.text + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'])) + return lldp_config + + def get_interface_lldp_disable_pre_config(self): + """Get interface undo lldp disable configure""" + lldp_dict = dict() + interface_lldp_disable_dict = dict() + if self.enable_flag == 1: + conf_enable_str = CE_NC_GET_INTERFACE_LLDP_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + if "" in conf_enable_obj: + return + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_enable_str) + lldp_disable_enable = root.findall("lldp/lldpInterfaces/lldpInterface") + for nexthop_enable in lldp_disable_enable: + name = nexthop_enable.find("ifName") + status = nexthop_enable.find("lldpAdminStatus") + if name is not None and status is not None: + interface_lldp_disable_dict[name.text] = status.text + return interface_lldp_disable_dict + + def get_interface_lldp_disable_config(self): + lldp_config = list() + interface_lldp_disable_dict_tmp = dict() + if self.state == "present": + if self.ifname: + interface_lldp_disable_dict_tmp = self.get_interface_lldp_disable_pre_config() + key_list = interface_lldp_disable_dict_tmp.keys() + if len(key_list) != 0: + for key in key_list: + if key == self.ifname: + if interface_lldp_disable_dict_tmp[key] != self.lldpadminstatus: + self.conf_interface_lldp_disable_exsit = True + else: + self.conf_interface_lldp_disable_exsit = False + elif self.ifname not in key_list: + self.conf_interface_lldp_disable_exsit = True + elif (len(key_list) == 0) and self.ifname and self.lldpadminstatus: + self.conf_interface_lldp_disable_exsit = True + lldp_config.append(interface_lldp_disable_dict_tmp) + return lldp_config + + def get_interface_tlv_disable_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_TLV_DISABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + lldp_tlvdisable_ifname = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in lldp_tlvdisable_ifname: + ifname_tmp = ele.find("ifName") + manaddrtxenable_tmp = ele.find("tlvTxEnable/manAddrTxEnable") + portdesctxenable_tmp = ele.find("tlvTxEnable/portDescTxEnable") + syscaptxenable_tmp = ele.find("tlvTxEnable/sysCapTxEnable") + sysdesctxenable_tmp = ele.find("tlvTxEnable/sysDescTxEnable") + sysnametxenable_tmp = ele.find("tlvTxEnable/sysNameTxEnable") + linkaggretxenable_tmp = ele.find("tlvTxEnable/linkAggreTxEnable") + macphytxenable_tmp = ele.find("tlvTxEnable/macPhyTxEnable") + maxframetxenable_tmp = ele.find("tlvTxEnable/maxFrameTxEnable") + eee_tmp = ele.find("tlvTxEnable/eee") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if ifname_tmp is not None and manaddrtxenable_tmp is not None: + if manaddrtxenable_tmp.text is not None: + cur_interface_mdn_cfg["manaddrtxenable"] = manaddrtxenable_tmp.text + if ifname_tmp is not None and portdesctxenable_tmp is not None: + if portdesctxenable_tmp.text is not None: + cur_interface_mdn_cfg['portdesctxenable'] = portdesctxenable_tmp.text + if ifname_tmp is not None and syscaptxenable_tmp is not None: + if syscaptxenable_tmp.text is not None: + cur_interface_mdn_cfg['syscaptxenable'] = syscaptxenable_tmp.text + if ifname_tmp is not None and sysdesctxenable_tmp is not None: + if sysdesctxenable_tmp.text is not None: + cur_interface_mdn_cfg['sysdesctxenable'] = sysdesctxenable_tmp.text + if ifname_tmp is not None and sysnametxenable_tmp is not None: + if sysnametxenable_tmp.text is not None: + cur_interface_mdn_cfg['sysnametxenable'] = sysnametxenable_tmp.text + if ifname_tmp is not None and linkaggretxenable_tmp is not None: + if linkaggretxenable_tmp.text is not None: + cur_interface_mdn_cfg['linkaggretxenable'] = linkaggretxenable_tmp.text + if ifname_tmp is not None and macphytxenable_tmp is not None: + if macphytxenable_tmp.text is not None: + cur_interface_mdn_cfg['macphytxenable'] = macphytxenable_tmp.text + if ifname_tmp is not None and maxframetxenable_tmp is not None: + if maxframetxenable_tmp.text is not None: + cur_interface_mdn_cfg['maxframetxenable'] = maxframetxenable_tmp.text + if ifname_tmp is not None and eee_tmp is not None: + if eee_tmp.text is not None: + cur_interface_mdn_cfg['eee'] = eee_tmp.text + if self.state == "present": + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.manaddrtxenable: + exp_interface_mdn_cfg['manaddrtxenable'] = self.manaddrtxenable + if self.portdesctxenable: + exp_interface_mdn_cfg['portdesctxenable'] = self.portdesctxenable + if self.syscaptxenable: + exp_interface_mdn_cfg['syscaptxenable'] = self.syscaptxenable + if self.sysdesctxenable: + exp_interface_mdn_cfg['sysdesctxenable'] = self.sysdesctxenable + if self.sysnametxenable: + exp_interface_mdn_cfg['sysnametxenable'] = self.sysnametxenable + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if key == "ifname" and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'])) + if "manaddrtxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(manaddrtxenable=cur_interface_mdn_cfg['manaddrtxenable'])) + if "portdesctxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(portdesctxenable=cur_interface_mdn_cfg['portdesctxenable'])) + if "syscaptxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(syscaptxenable=cur_interface_mdn_cfg['syscaptxenable'])) + if "sysdesctxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(sysdesctxenable=cur_interface_mdn_cfg['sysdesctxenable'])) + if "sysnametxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(sysnametxenable=cur_interface_mdn_cfg['sysnametxenable'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_disable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_disable_exsit = True + return lldp_config + + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.linkaggretxenable: + exp_interface_mdn_cfg['linkaggretxenable'] = self.linkaggretxenable + if self.macphytxenable: + exp_interface_mdn_cfg['macphytxenable'] = self.macphytxenable + if self.maxframetxenable: + exp_interface_mdn_cfg['maxframetxenable'] = self.maxframetxenable + if self.eee: + exp_interface_mdn_cfg['eee'] = self.eee + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if key == "ifname" and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'])) + if "linkaggretxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(linkaggretxenable=cur_interface_mdn_cfg['linkaggretxenable'])) + if "macphytxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(macphytxenable=cur_interface_mdn_cfg['macphytxenable'])) + if "maxframetxenable" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(maxframetxenable=cur_interface_mdn_cfg['maxframetxenable'])) + if "eee" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(eee=cur_interface_mdn_cfg['eee'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_disable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_disable_exsit = True + return lldp_config + return lldp_config + + def get_interface_tlv_enable_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_TLV_ENABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + lldpenablesite = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in lldpenablesite: + ifname_tmp = ele.find("ifName") + protoidtxenable_tmp = ele.find("tlvTxEnable/protoIdTxEnable") + dcbx_tmp = ele.find("tlvTxEnable/dcbx") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if ifname_tmp is not None and protoidtxenable_tmp is not None: + if protoidtxenable_tmp.text is not None: + cur_interface_mdn_cfg["protoidtxenable"] = protoidtxenable_tmp.text + if ifname_tmp is not None and dcbx_tmp is not None: + if dcbx_tmp.text is not None: + cur_interface_mdn_cfg['dcbx'] = dcbx_tmp.text + if self.state == "present": + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.protoidtxenable: + exp_interface_mdn_cfg['protoidtxenable'] = self.protoidtxenable + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "protoidtxenable" == str(key) and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(protoidtxenable=cur_interface_mdn_cfg['protoidtxenable'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_enable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_enable_exsit = True + return lldp_config + if self.type_tlv_enable == 'dcbx': + if self.ifname: + exp_interface_mdn_cfg['ifname'] = self.ifname + if self.dcbx: + exp_interface_mdn_cfg['dcbx'] = self.dcbx + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "dcbx" == key and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(dcbx=cur_interface_mdn_cfg['dcbx'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_tlv_enable_exsit = True + self.changed = True + return lldp_config + else: + self.conf_tlv_enable_exsit = True + return lldp_config + return lldp_config + + def get_interface_interval_config(self): + lldp_config = list() + lldp_dict = dict() + cur_interface_mdn_cfg = dict() + exp_interface_mdn_cfg = dict() + interface_lldp_disable_dict_tmp2 = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1: + if interface_lldp_disable_dict_tmp2[self.ifname] != 'disabled': + conf_str = CE_NC_GET_INTERFACE_INTERVAl_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '') + xml_str = xml_str.replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "") + xml_str = xml_str.replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + txintervalsite = root.findall("lldp/lldpInterfaces/lldpInterface") + for ele in txintervalsite: + ifname_tmp = ele.find("ifName") + txinterval_tmp = ele.find("msgInterval/txInterval") + if ifname_tmp is not None: + if ifname_tmp.text is not None: + cur_interface_mdn_cfg["ifname"] = ifname_tmp.text + if txinterval_tmp is not None: + if txinterval_tmp.text is not None: + cur_interface_mdn_cfg["txinterval"] = txinterval_tmp.text + if self.state == "present": + if self.ifname: + exp_interface_mdn_cfg["ifname"] = self.ifname + if self.txinterval: + exp_interface_mdn_cfg["txinterval"] = self.txinterval + if self.ifname == ifname_tmp.text: + key_list = exp_interface_mdn_cfg.keys() + key_list_cur = cur_interface_mdn_cfg.keys() + if len(key_list) != 0: + for key in key_list: + if "txinterval" == str(key) and self.ifname == cur_interface_mdn_cfg['ifname']: + lldp_config.append(dict(ifname=cur_interface_mdn_cfg['ifname'], txinterval=exp_interface_mdn_cfg['txinterval'])) + if key in key_list_cur: + if str(exp_interface_mdn_cfg[key]) != str(cur_interface_mdn_cfg[key]): + self.conf_interval_exsit = True + lldp_config.append(cur_interface_mdn_cfg) + return lldp_config + else: + self.conf_interval_exsit = True + return lldp_config + return lldp_config + + def config_global_lldp_enable(self): + if self.state == 'present': + if self.enable_flag == 0 and self.lldpenable == 'enabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.lldpenable == 'disabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + + def config_interface_lldp_disable_config(self): + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.enable_flag == 1 and self.conf_interface_lldp_disable_exsit: + if self.ifname: + xml_str = CE_NC_MERGE_INTERFACE_LLDP_CONFIG % (self.ifname, self.lldpadminstatus) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_LLDP_DISABLE_CONFIG") + self.changed = True + + def config_interface_tlv_disable_config(self): + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.enable_flag == 1 and self.conf_tlv_disable_exsit: + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + if self.portdesctxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_PORTDESCTXENABLE % self.portdesctxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_PORTDESCTXENABLE") + self.changed = True + if self.manaddrtxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MANADDRTXENABLE % self.manaddrtxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MANADDRTXENABLE") + self.changed = True + if self.syscaptxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSCAPTXENABLE % self.syscaptxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSCAPTXENABLE") + self.changed = True + if self.sysdesctxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSDESCTXENABLE % self.sysdesctxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSDESCTXENABLE") + self.changed = True + if self.sysnametxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_SYSNAMETXENABLE % self.sysnametxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_SYSNAMETXENABLE") + self.changed = True + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + if self.linkaggretxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_LINKAGGRETXENABLE % self.linkaggretxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_LINKAGGRETXENABLE") + self.changed = True + if self.macphytxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MACPHYTXENABLE % self.macphytxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MACPHYTXENABLE") + self.changed = True + if self.maxframetxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_MAXFRAMETXENABLE % self.maxframetxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_MAXFRAMETXENABLE") + self.changed = True + if self.eee: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_DISABLE_EEE % self.eee) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_DISABLE_EEE") + self.changed = True + + def config_interface_tlv_enable_config(self): + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.enable_flag == 1 and self.conf_tlv_enable_exsit: + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + if self.protoidtxenable: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_PROTOIDTXENABLE % self.protoidtxenable) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_ENABLE_DOT1_PORT_VLAN") + self.changed = True + if self.type_tlv_enable == 'dcbx': + if self.ifname: + if self.dcbx: + xml_str = (CE_NC_MERGE_INTERFACE_TLV_CONFIG_HEADER % self.ifname) + \ + (CE_NC_MERGE_INTERFACE_TLV_CONFIG_ENABLE_DCBX % self.dcbx) + \ + CE_NC_MERGE_INTERFACE_TLV_CONFIG_TAIL + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "TLV_ENABLE_DCBX_VLAN") + self.changed = True + + def config_interface_interval_config(self): + if self.function_lldp_interface_flag == 'intervalINTERFACE': + tmp = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1 and self.conf_interval_exsit and tmp[self.ifname] != 'disabled': + if self.ifname: + if self.txinterval: + xml_str = CE_NC_MERGE_INTERFACE_INTERVAl_CONFIG % (self.ifname, self.txinterval) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_INTERVAL_CONFIG") + self.changed = True + + def get_existing(self): + """get existing information""" + self.get_lldp_enable_pre_config() + if self.lldpenable: + self.existing['globalLLDPENABLE'] = self.get_lldp_enable_pre_config() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.existing['disableINTERFACE'] = self.get_interface_lldp_disable_config() + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.existing['tlvdisableINTERFACE'] = self.get_interface_tlv_disable_config() + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.existing['tlvenableINTERFACE'] = self.get_interface_tlv_enable_config() + if self.function_lldp_interface_flag == 'intervalINTERFACE': + self.existing['intervalINTERFACE'] = self.get_interface_interval_config() + + def get_proposed(self): + """get proposed""" + if self.lldpenable: + self.proposed = dict(lldpenable=self.lldpenable) + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.enable_flag == 1: + self.proposed = dict(ifname=self.ifname, lldpadminstatus=self.lldpadminstatus) + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.enable_flag == 1: + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + if self.manaddrtxenable: + self.proposed = dict(ifname=self.ifname, manaddrtxenable=self.manaddrtxenable) + if self.portdesctxenable: + self.proposed = dict(ifname=self.ifname, portdesctxenable=self.portdesctxenable) + if self.syscaptxenable: + self.proposed = dict(ifname=self.ifname, syscaptxenable=self.syscaptxenable) + if self.sysdesctxenable: + self.proposed = dict(ifname=self.ifname, sysdesctxenable=self.sysdesctxenable) + if self.sysnametxenable: + self.proposed = dict(ifname=self.ifname, sysnametxenable=self.sysnametxenable) + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + if self.linkaggretxenable: + self.proposed = dict(ifname=self.ifname, linkaggretxenable=self.linkaggretxenable) + if self.macphytxenable: + self.proposed = dict(ifname=self.ifname, macphytxenable=self.macphytxenable) + if self.maxframetxenable: + self.proposed = dict(ifname=self.ifname, maxframetxenable=self.maxframetxenable) + if self.eee: + self.proposed = dict(ifname=self.ifname, eee=self.eee) + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.enable_flag == 1: + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + if self.protoidtxenable: + self.proposed = dict(ifname=self.ifname, protoidtxenable=self.protoidtxenable) + if self.type_tlv_enable == 'dcbx': + if self.ifname: + if self.dcbx: + self.proposed = dict(ifname=self.ifname, dcbx=self.dcbx) + if self.function_lldp_interface_flag == 'intervalINTERFACE': + tmp1 = self.get_interface_lldp_disable_pre_config() + if self.enable_flag == 1 and tmp1[self.ifname] != 'disabled': + self.proposed = dict(ifname=self.ifname, txinterval=self.txinterval) + + def config_lldp_interface(self): + """config lldp interface""" + if self.lldpenable: + self.config_global_lldp_enable() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.config_interface_lldp_disable_config() + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.config_interface_tlv_disable_config() + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.config_interface_tlv_enable_config() + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + self.config_interface_interval_config() + + def get_end_state(self): + """get end_state information""" + self.get_lldp_enable_pre_config() + if self.lldpenable: + self.end_state['globalLLDPENABLE'] = self.get_lldp_enable_pre_config() + if self.function_lldp_interface_flag == 'disableINTERFACE': + self.end_state['disableINTERFACE'] = self.get_interface_lldp_disable_config() + if self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + self.end_state['tlvdisableINTERFACE'] = self.get_interface_tlv_disable_config() + if self.function_lldp_interface_flag == 'tlvenableINTERFACE': + self.end_state['tlvenableINTERFACE'] = self.get_interface_tlv_enable_config() + if self.function_lldp_interface_flag == 'intervalINTERFACE': + self.end_state['intervalINTERFACE'] = self.get_interface_interval_config() + + def get_update_cmd(self): + """Get updated commands""" + + cmds = [] + if self.state == "present": + if self.lldpenable == "enabled": + cmds.append("lldp enable") + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.lldpadminstatus == 'disabled': + cmds.append("lldp disable") + else: + cmds.append("undo lldp disable") + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.manaddrtxenable: + if self.manaddrtxenable == "false": + cmds.append("lldp tlv-disable basic-tlv management-address") + if self.manaddrtxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv management-address") + if self.portdesctxenable: + if self.portdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv port-description") + if self.portdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv port-description") + if self.syscaptxenable: + if self.syscaptxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-capability") + if self.syscaptxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-capability") + if self.sysdesctxenable: + if self.sysdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-description") + if self.sysdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-description") + if self.sysnametxenable: + if self.sysnametxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-name") + if self.sysnametxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-name") + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.linkaggretxenable: + if self.linkaggretxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv link-aggregation") + if self.linkaggretxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv link-aggregation") + if self.macphytxenable: + if self.macphytxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv mac-physic") + if self.macphytxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv mac-physic") + if self.maxframetxenable: + if self.maxframetxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv max-frame-size") + if self.maxframetxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv max-frame-size") + if self.eee: + if self.eee == "false": + cmds.append("lldp tlv-disable dot3-tlv eee") + if self.eee == "true": + cmds.append("undo lldp tlv-disable dot3-tlv eee") + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.protoidtxenable: + if self.protoidtxenable == "false": + cmds.append("undo lldp tlv-enable dot1-tlv protocol-identity") + if self.protoidtxenable == "true": + cmds.append("lldp tlv-enable dot1-tlv protocol-identity") + if self.type_tlv_enable == 'dcbx': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.dcbx: + if self.dcbx == "false": + cmds.append("undo lldp tlv-enable dcbx") + if self.dcbx == "true": + cmds.append("lldp tlv-enable dcbx") + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.ifname: + cmds.append("%s %s" % ("interface", self.ifname)) + if self.txinterval: + cmds.append("lldp transmit fast-mode interval %s" % self.txinterval) + elif self.lldpenable == "disabled": + cmds.append("undo lldp enable") + else: + if self.enable_flag == 1: + if self.function_lldp_interface_flag == 'disableINTERFACE': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.lldpadminstatus == 'disabled': + cmds.append("lldp disable") + else: + cmds.append("undo lldp disable") + elif self.function_lldp_interface_flag == 'tlvdisableINTERFACE': + if self.type_tlv_disable == 'basic_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.manaddrtxenable: + if self.manaddrtxenable == "false": + cmds.append("lldp tlv-disable basic-tlv management-address") + if self.manaddrtxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv management-address") + if self.portdesctxenable: + if self.portdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv port-description") + if self.portdesctxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv port-description") + if self.syscaptxenable: + if self.syscaptxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-capability") + if self.syscaptxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-capability") + if self.sysdesctxenable: + if self.sysdesctxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-description") + if self.sysdesctxenable == "true": + cli_str = "%s %s\n" % (cli_str, "undo lldp tlv-disable basic-tlv system-description") + if self.sysnametxenable: + if self.sysnametxenable == "false": + cmds.append("lldp tlv-disable basic-tlv system-name") + if self.sysnametxenable == "true": + cmds.append("undo lldp tlv-disable basic-tlv system-name") + if self.type_tlv_disable == 'dot3_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.linkaggretxenable: + if self.linkaggretxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv link-aggregation") + if self.linkaggretxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv link-aggregation") + if self.macphytxenable: + if self.macphytxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv mac-physic") + if self.macphytxenable == "true": + cli_str = "%s %s\n" % (cli_str, "undo lldp tlv-disable dot3-tlv mac-physic") + if self.maxframetxenable: + if self.maxframetxenable == "false": + cmds.append("lldp tlv-disable dot3-tlv max-frame-size") + if self.maxframetxenable == "true": + cmds.append("undo lldp tlv-disable dot3-tlv max-frame-size") + if self.eee: + if self.eee == "false": + cmds.append("lldp tlv-disable dot3-tlv eee") + if self.eee == "true": + cmds.append("undo lldp tlv-disable dot3-tlv eee") + elif self.function_lldp_interface_flag == 'tlvenableINTERFACE': + if self.type_tlv_enable == 'dot1_tlv': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.protoidtxenable: + if self.protoidtxenable == "false": + cmds.append("undo lldp tlv-enable dot1-tlv protocol-identity") + if self.protoidtxenable == "true": + cmds.append("lldp tlv-enable dot1-tlv protocol-identity") + if self.type_tlv_enable == 'dcbx': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.dcbx: + if self.dcbx == "false": + cmds.append("undo lldp tlv-enable dcbx") + if self.dcbx == "true": + cmds.append("lldp tlv-enable dcbx") + elif self.function_lldp_interface_flag == 'intervalINTERFACE': + if self.ifname: + cmds.append("interface %s" % self.ifname) + if self.txinterval: + cmds.append("lldp transmit fast-mode interval %s" % self.txinterval) + self.updates_cmd = cmds + + def work(self): + """Execute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_lldp_interface() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function""" + + argument_spec = dict( + lldpenable=dict(choices=['enabled', 'disabled']), + function_lldp_interface_flag=dict(choices=['disableINTERFACE', 'tlvdisableINTERFACE', 'tlvenableINTERFACE', 'intervalINTERFACE'], type='str'), + type_tlv_disable=dict(choices=['basic_tlv', 'dot3_tlv'], type='str'), + type_tlv_enable=dict(choices=['dot1_tlv', 'dcbx'], type='str'), + ifname=dict(type='str'), + lldpadminstatus=dict(choices=['txOnly', 'rxOnly', 'txAndRx', 'disabled'], type='str'), + manaddrtxenable=dict(type='bool'), + portdesctxenable=dict(type='bool'), + syscaptxenable=dict(type='bool'), + sysdesctxenable=dict(type='bool'), + sysnametxenable=dict(type='bool'), + portvlantxenable=dict(type='bool'), + protovlantxenable=dict(type='bool'), + txprotocolvlanid=dict(type='int'), + vlannametxenable=dict(type='bool'), + txvlannameid=dict(type='int'), + txinterval=dict(type='int'), + protoidtxenable=dict(type='bool'), + macphytxenable=dict(type='bool'), + linkaggretxenable=dict(type='bool'), + maxframetxenable=dict(type='bool'), + eee=dict(type='bool'), + dcbx=dict(type='bool'), + state=dict(type='str', choices=['absent', 'present'], default='present'), + ) + + lldp_interface_obj = Lldp_interface(argument_spec) + lldp_interface_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mdn_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mdn_interface.py new file mode 100644 index 00000000..45f588d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mdn_interface.py @@ -0,0 +1,399 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_mdn_interface +version_added: '0.2.0' +short_description: Manages MDN configuration on HUAWEI CloudEngine switches. +description: + - Manages MDN configuration on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +options: + lldpenable: + description: + - Set global LLDP enable state. + type: str + choices: ['enabled', 'disabled'] + mdnstatus: + description: + - Set interface MDN enable state. + type: str + choices: ['rxOnly', 'disabled'] + ifname: + description: + - Interface name. + type: str + state: + description: + - Manage the state of the resource. + default: present + type: str + choices: ['present','absent'] +notes: + - This module requires the netconf system service be enabled on + the remote device being managed. + - This module works with connection C(netconf). +''' + +EXAMPLES = ''' + - name: "Configure global LLDP enable state" + community.network.ce_mdn_interface: + lldpenable: enabled + + - name: "Configure interface MDN enable state" + community.network.ce_mdn_interface: + ifname: 10GE1/0/1 + mdnstatus: rxOnly +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "rxOnly", + "state":"present" + } +existing: + description: k/v pairs of existing global LLDP configration + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "disabled" + } +end_state: + description: k/v pairs of global LLDP configration after module execution + returned: always + type: dict + sample: { + "lldpenable": "enabled", + "ifname": "10GE1/0/1", + "mdnstatus": "rxOnly" + } +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "interface 10ge 1/0/1", + "lldp mdn enable", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import set_nc_config, get_nc_config, execute_nc_action + +CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG = """ + + + + + + + +""" + +CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG = """ + + + + %s + + + +""" + +CE_NC_GET_INTERFACE_MDNENABLE_CONFIG = """ + + + + + + + + + + +""" + +CE_NC_MERGE_INTERFACE_MDNENABLE_CONFIG = """ + + + + + %s + %s + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('PORT-GROUP'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + return iftype.lower() + + +class Interface_mdn(object): + """Manage global lldp enable configration""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # LLDP global configration info + self.lldpenable = self.module.params['lldpenable'] or None + self.ifname = self.module.params['ifname'] + self.mdnstatus = self.module.params['mdnstatus'] or None + self.state = self.module.params['state'] + self.lldp_conf = dict() + self.conf_exsit = False + self.enable_flag = 0 + self.check_params() + + # state + self.changed = False + self.proposed_changed = dict() + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_params(self): + """Check all input params""" + + if self.ifname: + intf_type = get_interface_type(self.ifname) + if not intf_type: + self.module.fail_json( + msg='Error: ifname name of %s ' + 'is error.' % self.ifname) + if (len(self.ifname) < 1) or (len(self.ifname) > 63): + self.module.fail_json( + msg='Error: Ifname length is beetween 1 and 63.') + + def init_module(self): + """Init module object""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_interface_mdn(self): + """Configure lldp enabled and interface mdn enabled parameters""" + + if self.state == 'present': + if self.enable_flag == 0 and self.lldpenable == 'enabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.lldpenable == 'disabled': + xml_str = CE_NC_MERGE_GLOBA_LLDPENABLE_CONFIG % self.lldpenable + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "LLDP_ENABLE_CONFIG") + self.changed = True + elif self.enable_flag == 1 and self.conf_exsit: + xml_str = CE_NC_MERGE_INTERFACE_MDNENABLE_CONFIG % (self.ifname, self.mdnstatus) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "INTERFACE_MDN_ENABLE_CONFIG") + self.changed = True + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_interface_mdn_exist_config(self): + """Get lldp existed configure""" + + lldp_config = list() + lldp_dict = dict() + conf_enable_str = CE_NC_GET_GLOBAL_LLDPENABLE_CONFIG + conf_enable_obj = get_nc_config(self.module, conf_enable_str) + xml_enable_str = conf_enable_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get lldp enable config info + root_enable = ElementTree.fromstring(xml_enable_str) + ntpsite_enable = root_enable.findall("lldp/lldpSys") + for nexthop_enable in ntpsite_enable: + for ele_enable in nexthop_enable: + if ele_enable.tag in ["lldpEnable"]: + lldp_dict[ele_enable.tag] = ele_enable.text + + if self.state == "present": + if lldp_dict['lldpEnable'] == 'enabled': + self.enable_flag = 1 + lldp_config.append(dict(lldpenable=lldp_dict['lldpEnable'])) + + if self.enable_flag == 1: + conf_str = CE_NC_GET_INTERFACE_MDNENABLE_CONFIG + conf_obj = get_nc_config(self.module, conf_str) + if "" in conf_obj: + return lldp_config + xml_str = conf_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("lldp/mdnInterfaces/mdnInterface") + for nexthop in ntpsite: + for ele in nexthop: + if ele.tag in ["ifName", "mdnStatus"]: + lldp_dict[ele.tag] = ele.text + if self.state == "present": + cur_interface_mdn_cfg = dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus']) + exp_interface_mdn_cfg = dict(ifname=self.ifname, mdnstatus=self.mdnstatus) + if self.ifname == lldp_dict['ifName']: + if cur_interface_mdn_cfg != exp_interface_mdn_cfg: + self.conf_exsit = True + lldp_config.append(dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus'])) + return lldp_config + lldp_config.append(dict(ifname=lldp_dict['ifName'], mdnstatus=lldp_dict['mdnStatus'])) + return lldp_config + + def get_existing(self): + """Get existing info""" + + self.existing = self.get_interface_mdn_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.lldpenable: + self.proposed = dict(lldpenable=self.lldpenable) + if self.enable_flag == 1: + if self.ifname: + self.proposed = dict(ifname=self.ifname, mdnstatus=self.mdnstatus) + + def get_end_state(self): + """Get end state info""" + + self.end_state = self.get_interface_mdn_exist_config() + + def get_update_cmd(self): + """Get updated commands""" + + update_list = list() + if self.state == "present": + if self.lldpenable == "enabled": + cli_str = "lldp enable" + update_list.append(cli_str) + if self.ifname: + cli_str = "%s %s" % ("interface", self.ifname) + update_list.append(cli_str) + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + cli_str = "lldp mdn enable" + update_list.append(cli_str) + else: + cli_str = "undo lldp mdn enable" + update_list.append(cli_str) + + elif self.lldpenable == "disabled": + cli_str = "undo lldp enable" + update_list.append(cli_str) + else: + if self.enable_flag == 1: + if self.ifname: + cli_str = "%s %s" % ("interface", self.ifname) + update_list.append(cli_str) + if self.mdnstatus: + if self.mdnstatus == "rxOnly": + cli_str = "lldp mdn enable" + update_list.append(cli_str) + else: + cli_str = "undo lldp mdn enable" + update_list.append(cli_str) + + self.updates_cmd.append(update_list) + + def work(self): + """Excute task""" + self.check_params() + self.get_existing() + self.get_proposed() + self.config_interface_mdn() + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + lldpenable=dict(type='str', choices=['enabled', 'disabled']), + mdnstatus=dict(type='str', choices=['rxOnly', 'disabled']), + ifname=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + lldp_obj = Interface_mdn(argument_spec) + lldp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_config.py new file mode 100644 index 00000000..b3141188 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_config.py @@ -0,0 +1,912 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mlag_config +short_description: Manages MLAG configuration on HUAWEI CloudEngine switches. +description: + - Manages MLAG configuration on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + dfs_group_id: + description: + - ID of a DFS group. The value is 1. + default: present + nickname: + description: + - The nickname bound to a DFS group. The value is an integer that ranges from 1 to 65471. + pseudo_nickname: + description: + - A pseudo nickname of a DFS group. The value is an integer that ranges from 1 to 65471. + pseudo_priority: + description: + - The priority of a pseudo nickname. The value is an integer that ranges from 128 to 255. + The default value is 192. A larger value indicates a higher priority. + ip_address: + description: + - IP address bound to the DFS group. The value is in dotted decimal notation. + vpn_instance_name: + description: + - Name of the VPN instance bound to the DFS group. The value is a string of 1 to 31 case-sensitive + characters without spaces. If the character string is quoted by double quotation marks, the character + string can contain spaces. The value _public_ is reserved and cannot be used as the VPN instance name. + priority_id: + description: + - Priority of a DFS group. The value is an integer that ranges from 1 to 254. The default value is 100. + eth_trunk_id: + description: + - Name of the peer-link interface. The value is in the range from 0 to 511. + peer_link_id: + description: + - Number of the peer-link interface. The value is 1. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Mlag config module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create DFS Group id + community.network.ce_mlag_config: + dfs_group_id: 1 + provider: "{{ cli }}" + - name: Set dfs-group priority + community.network.ce_mlag_config: + dfs_group_id: 1 + priority_id: 3 + state: present + provider: "{{ cli }}" + - name: Set pseudo nickname + community.network.ce_mlag_config: + dfs_group_id: 1 + pseudo_nickname: 3 + pseudo_priority: 130 + state: present + provider: "{{ cli }}" + - name: Set ip + community.network.ce_mlag_config: + dfs_group_id: 1 + ip_address: 11.1.1.2 + vpn_instance_name: 6 + provider: "{{ cli }}" + - name: Set peer link + community.network.ce_mlag_config: + eth_trunk_id: 3 + peer_link_id: 2 + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { "eth_trunk_id": "3", + "peer_link_id": "1", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: { "eth_trunk_id": "Eth-Trunk3", + "peer_link_id": "1"} +updates: + description: command sent to the device + returned: always + type: list + sample: {"peer-link 1"} +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_DFS_GROUP_INFO = """ + + + + + + + + + + + + + + + + + +""" +CE_NC_GET_PEER_LINK_INFO = """ + + + + + + + + + + + +""" + +CE_NC_CREATE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_CREATE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_MERGE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_MERGE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER = """ + + + + + %s +""" + +CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL = """ + + + + +""" + +CE_NC_DELETE_DFS_GROUP_INFO_HEADER = """ + + + + + %s +""" + +CE_NC_DELETE_DFS_GROUP_INFO_TAIL = """ + + + + +""" + +CE_NC_CREATE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" + +CE_NC_MERGE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" +CE_NC_DELETE_PEER_LINK_INFO = """ + + + + + 1 + %s + %s + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class MlagConfig(object): + """ + Manages Manages MLAG config information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.dfs_group_id = self.module.params['dfs_group_id'] + self.nickname = self.module.params['nickname'] + self.pseudo_nickname = self.module.params['pseudo_nickname'] + self.pseudo_priority = self.module.params['pseudo_priority'] + self.ip_address = self.module.params['ip_address'] + self.vpn_instance_name = self.module.params['vpn_instance_name'] + self.priority_id = self.module.params['priority_id'] + self.eth_trunk_id = self.module.params['eth_trunk_id'] + self.peer_link_id = self.module.params['peer_link_id'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + self.commands = list() + # DFS group info + self.dfs_group_info = None + # peer link info + self.peer_link_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed.""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_dfs_group_info(self): + """ get dfs group attributes info.""" + + dfs_group_info = dict() + conf_str = CE_NC_GET_DFS_GROUP_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return dfs_group_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + dfs_info = root.findall( + "dfs/groupInstances/groupInstance") + if dfs_info: + for tmp in dfs_info: + for site in tmp: + if site.tag in ["groupId", "priority", "ipAddress", "srcVpnName"]: + dfs_group_info[site.tag] = site.text + + dfs_nick_info = root.findall( + "dfs/groupInstances/groupInstance/trillType") + + if dfs_nick_info: + for tmp in dfs_nick_info: + for site in tmp: + if site.tag in ["localNickname", "pseudoNickname", "pseudoPriority"]: + dfs_group_info[site.tag] = site.text + return dfs_group_info + + def get_peer_link_info(self): + """ get peer link info.""" + + peer_link_info = dict() + conf_str = CE_NC_GET_PEER_LINK_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return peer_link_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + link_info = root.findall( + "mlag/peerlinks/peerlink") + if link_info: + for tmp in link_info: + for site in tmp: + if site.tag in ["linkId", "portName"]: + peer_link_info[site.tag] = site.text + return peer_link_info + + def is_dfs_group_info_change(self): + """whether dfs group info""" + if not self.dfs_group_info: + return False + + if self.priority_id and self.dfs_group_info["priority"] != self.priority_id: + return True + if self.ip_address and self.dfs_group_info["ipAddress"] != self.ip_address: + return True + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] != self.vpn_instance_name: + return True + if self.nickname and self.dfs_group_info["localNickname"] != self.nickname: + return True + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] != self.pseudo_nickname: + return True + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] != self.pseudo_priority: + return True + return False + + def check_dfs_group_info_change(self): + """check dfs group info""" + if not self.dfs_group_info: + return True + + if self.priority_id and self.dfs_group_info["priority"] == self.priority_id: + return True + if self.ip_address and self.dfs_group_info["ipAddress"] == self.ip_address: + return True + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] == self.vpn_instance_name: + return True + if self.nickname and self.dfs_group_info["localNickname"] == self.nickname: + return True + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] == self.pseudo_nickname: + return True + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] == self.pseudo_priority: + return True + return False + + def modify_dfs_group(self): + """modify dfs group info""" + + if self.is_dfs_group_info_change(): + + conf_str = CE_NC_MERGE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + if self.priority_id and self.dfs_group_info["priority"] != self.priority_id: + conf_str += "%s" % self.priority_id + if self.ip_address and self.dfs_group_info["ipAddress"] != self.ip_address: + conf_str += "%s" % self.ip_address + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] != self.vpn_instance_name: + if not self.ip_address: + self.module.fail_json( + msg='Error: ip_address can not be null if vpn_instance_name is exist.') + conf_str += "%s" % self.vpn_instance_name + + if self.nickname or self.pseudo_nickname or self.pseudo_priority: + conf_str += "" + if self.nickname and self.dfs_group_info["localNickname"] != self.nickname: + conf_str += "%s" % self.nickname + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] != self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] != self.pseudo_priority: + if not self.pseudo_nickname: + self.module.fail_json( + msg='Error: pseudo_nickname can not be null if pseudo_priority is exist.') + conf_str += "%s" % self.pseudo_priority + conf_str += "" + + conf_str += CE_NC_MERGE_DFS_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge DFS group info failed.') + + self.updates_cmd.append("dfs-group 1") + if self.priority_id: + self.updates_cmd.append("priority %s" % self.priority_id) + if self.ip_address: + if self.vpn_instance_name: + self.updates_cmd.append( + "source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + self.updates_cmd.append("source ip %s" % self.ip_address) + if self.nickname: + self.updates_cmd.append("source nickname %s" % self.nickname) + if self.pseudo_nickname: + if self.pseudo_priority: + self.updates_cmd.append( + "pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + else: + self.updates_cmd.append( + "pseudo-nickname %s" % self.pseudo_nickname) + + self.changed = True + + def create_dfs_group(self): + """create dfs group info""" + + conf_str = CE_NC_CREATE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + if self.priority_id and self.priority_id != 100: + conf_str += "%s" % self.priority_id + if self.ip_address: + conf_str += "%s" % self.ip_address + if self.vpn_instance_name: + if not self.ip_address: + self.module.fail_json( + msg='Error: ip_address can not be null if vpn_instance_name is exist.') + conf_str += "%s" % self.vpn_instance_name + + if self.nickname or self.pseudo_nickname or self.pseudo_priority: + conf_str += "" + if self.nickname: + conf_str += "%s" % self.nickname + if self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + if self.pseudo_priority: + if not self.pseudo_nickname: + self.module.fail_json( + msg='Error: pseudo_nickname can not be null if pseudo_priority is exist.') + conf_str += "%s" % self.pseudo_priority + conf_str += "" + + conf_str += CE_NC_CREATE_DFS_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge DFS group info failed.') + + self.updates_cmd.append("dfs-group 1") + if self.priority_id: + self.updates_cmd.append("priority %s" % self.priority_id) + if self.ip_address: + if self.vpn_instance_name: + self.updates_cmd.append( + "source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + self.updates_cmd.append("source ip %s" % self.ip_address) + if self.nickname: + self.updates_cmd.append("source nickname %s" % self.nickname) + if self.pseudo_nickname: + if self.pseudo_priority: + self.updates_cmd.append( + "pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + else: + self.updates_cmd.append( + "pseudo-nickname %s" % self.pseudo_nickname) + + self.changed = True + + def delete_dfs_group(self): + """delete dfg group""" + + conf_str = CE_NC_DELETE_DFS_GROUP_INFO_HEADER % self.dfs_group_id + conf_str += CE_NC_DELETE_DFS_GROUP_INFO_TAIL + + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group id failed.') + self.updates_cmd.append("undo dfs-group 1") + self.changed = True + + def delete_dfs_group_attribute(self): + """delete dfg group attribute info""" + + conf_str = CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER % self.dfs_group_id + change = False + if self.priority_id and self.dfs_group_info["priority"] == self.priority_id: + conf_str += "%s" % self.priority_id + change = True + self.updates_cmd.append("undo priority %s" % self.priority_id) + if self.ip_address and self.dfs_group_info["ipAddress"] == self.ip_address: + if self.vpn_instance_name and self.dfs_group_info["srcVpnName"] == self.vpn_instance_name: + conf_str += "%s" % self.ip_address + conf_str += "%s" % self.vpn_instance_name + self.updates_cmd.append( + "undo source ip %s vpn-instance %s" % (self.ip_address, self.vpn_instance_name)) + else: + conf_str += "%s" % self.ip_address + self.updates_cmd.append("undo source ip %s" % self.ip_address) + change = True + + conf_str += CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL + + if change: + self.updates_cmd.append("undo dfs-group 1") + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group attribute failed.') + self.changed = True + + def delete_dfs_group_nick(self): + + conf_str = CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_HEADER % self.dfs_group_id + conf_str = conf_str.replace('', '') + change = False + + if self.nickname or self.pseudo_nickname: + conf_str += "" + if self.nickname and self.dfs_group_info["localNickname"] == self.nickname: + conf_str += "%s" % self.nickname + change = True + self.updates_cmd.append("undo source nickname %s" % self.nickname) + if self.pseudo_nickname and self.dfs_group_info["pseudoNickname"] == self.pseudo_nickname: + conf_str += "%s" % self.pseudo_nickname + if self.pseudo_priority and self.dfs_group_info["pseudoPriority"] == self.pseudo_priority: + self.updates_cmd.append( + "undo pseudo-nickname %s priority %s" % (self.pseudo_nickname, self.pseudo_priority)) + if not self.pseudo_priority: + self.updates_cmd.append( + "undo pseudo-nickname %s" % self.pseudo_nickname) + change = True + conf_str += "" + + conf_str += CE_NC_DELETE_DFS_GROUP_ATTRIBUTE_TAIL + + if change: + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete DFS group attribute failed.') + self.changed = True + + def modify_peer_link(self): + """modify peer link info""" + + eth_trunk_id = "Eth-Trunk" + eth_trunk_id += self.eth_trunk_id + if self.eth_trunk_id and eth_trunk_id != self.peer_link_info.get("portName"): + conf_str = CE_NC_MERGE_PEER_LINK_INFO % ( + self.peer_link_id, eth_trunk_id) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Merge peer link failed.') + self.updates_cmd.append("peer-link %s" % self.peer_link_id) + self.changed = True + + def delete_peer_link(self): + """delete peer link info""" + + eth_trunk_id = "Eth-Trunk" + eth_trunk_id += self.eth_trunk_id + if self.eth_trunk_id and eth_trunk_id == self.peer_link_info.get("portName"): + conf_str = CE_NC_DELETE_PEER_LINK_INFO % ( + self.peer_link_id, eth_trunk_id) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: Delete peer link failed.') + self.updates_cmd.append("undo peer-link %s" % self.peer_link_id) + self.changed = True + + def check_params(self): + """Check all input params""" + + # dfs_group_id check + if self.dfs_group_id: + if self.dfs_group_id != "1": + self.module.fail_json( + msg='Error: The value of dfs_group_id must be 1.') + + # nickname check + if self.nickname: + if not self.nickname.isdigit(): + self.module.fail_json( + msg='Error: The value of nickname is an integer.') + if int(self.nickname) < 1 or int(self.nickname) > 65471: + self.module.fail_json( + msg='Error: The nickname is not in the range from 1 to 65471.') + + # pseudo_nickname check + if self.pseudo_nickname: + if not self.pseudo_nickname.isdigit(): + self.module.fail_json( + msg='Error: The value of pseudo_nickname is an integer.') + if int(self.pseudo_nickname) < 1 or int(self.pseudo_nickname) > 65471: + self.module.fail_json( + msg='Error: The pseudo_nickname is not in the range from 1 to 65471.') + + # pseudo_priority check + if self.pseudo_priority: + if not self.pseudo_priority.isdigit(): + self.module.fail_json( + msg='Error: The value of pseudo_priority is an integer.') + if int(self.pseudo_priority) < 128 or int(self.pseudo_priority) > 255: + self.module.fail_json( + msg='Error: The pseudo_priority is not in the range from 128 to 255.') + + # ip_address check + if self.ip_address: + if not is_valid_address(self.ip_address): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.ip_address) + + # vpn_instance_name check + if self.vpn_instance_name: + if len(self.vpn_instance_name) > 31 \ + or len(self.vpn_instance_name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: The length of vpn_instance_name is not in the range from 1 to 31.') + + # priority_id check + if self.priority_id: + if not self.priority_id.isdigit(): + self.module.fail_json( + msg='Error: The value of priority_id is an integer.') + if int(self.priority_id) < 1 or int(self.priority_id) > 254: + self.module.fail_json( + msg='Error: The priority_id is not in the range from 1 to 254.') + + # peer_link_id check + if self.peer_link_id: + if self.peer_link_id != "1": + self.module.fail_json( + msg='Error: The value of peer_link_id must be 1.') + + # eth_trunk_id check + if self.eth_trunk_id: + if not self.eth_trunk_id.isdigit(): + self.module.fail_json( + msg='Error: The value of eth_trunk_id is an integer.') + if int(self.eth_trunk_id) < 0 or int(self.eth_trunk_id) > 511: + self.module.fail_json( + msg='Error: The value of eth_trunk_id is not in the range from 0 to 511.') + + def get_proposed(self): + """get proposed info""" + + if self.dfs_group_id: + self.proposed["dfs_group_id"] = self.dfs_group_id + if self.nickname: + self.proposed["nickname"] = self.nickname + if self.pseudo_nickname: + self.proposed["pseudo_nickname"] = self.pseudo_nickname + if self.pseudo_priority: + self.proposed["pseudo_priority"] = self.pseudo_priority + if self.ip_address: + self.proposed["ip_address"] = self.ip_address + if self.vpn_instance_name: + self.proposed["vpn_instance_name"] = self.vpn_instance_name + if self.priority_id: + self.proposed["priority_id"] = self.priority_id + if self.eth_trunk_id: + self.proposed["eth_trunk_id"] = self.eth_trunk_id + if self.peer_link_id: + self.proposed["peer_link_id"] = self.peer_link_id + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + if self.dfs_group_id: + self.dfs_group_info = self.get_dfs_group_info() + if self.peer_link_id and self.eth_trunk_id: + self.peer_link_info = self.get_peer_link_info() + if self.dfs_group_info: + if self.dfs_group_id: + self.existing["dfs_group_id"] = self.dfs_group_info["groupId"] + if self.nickname: + self.existing["nickname"] = self.dfs_group_info[ + "localNickname"] + if self.pseudo_nickname: + self.existing["pseudo_nickname"] = self.dfs_group_info[ + "pseudoNickname"] + if self.pseudo_priority: + self.existing["pseudo_priority"] = self.dfs_group_info[ + "pseudoPriority"] + if self.ip_address: + self.existing["ip_address"] = self.dfs_group_info["ipAddress"] + if self.vpn_instance_name: + self.existing["vpn_instance_name"] = self.dfs_group_info[ + "srcVpnName"] + if self.priority_id: + self.existing["priority_id"] = self.dfs_group_info["priority"] + if self.peer_link_info: + if self.eth_trunk_id: + self.existing["eth_trunk_id"] = self.peer_link_info["portName"] + if self.peer_link_id: + self.existing["peer_link_id"] = self.peer_link_info["linkId"] + + def get_end_state(self): + """get end state info""" + if self.dfs_group_id: + self.dfs_group_info = self.get_dfs_group_info() + if self.peer_link_id and self.eth_trunk_id: + self.peer_link_info = self.get_peer_link_info() + + if self.dfs_group_info: + if self.dfs_group_id: + self.end_state["dfs_group_id"] = self.dfs_group_info["groupId"] + if self.nickname: + self.end_state["nickname"] = self.dfs_group_info[ + "localNickname"] + if self.pseudo_nickname: + self.end_state["pseudo_nickname"] = self.dfs_group_info[ + "pseudoNickname"] + if self.pseudo_priority: + self.end_state["pseudo_priority"] = self.dfs_group_info[ + "pseudoPriority"] + if self.ip_address: + self.end_state["ip_address"] = self.dfs_group_info["ipAddress"] + if self.vpn_instance_name: + self.end_state["vpn_instance_name"] = self.dfs_group_info[ + "srcVpnName"] + if self.priority_id: + self.end_state["priority_id"] = self.dfs_group_info["priority"] + if self.peer_link_info: + if self.eth_trunk_id: + self.end_state[ + "eth_trunk_id"] = self.peer_link_info["portName"] + if self.peer_link_id: + self.end_state["peer_link_id"] = self.peer_link_info["linkId"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + if self.dfs_group_id: + if self.state == "present": + if self.dfs_group_info: + if self.nickname or self.pseudo_nickname or self.pseudo_priority or self.priority_id \ + or self.ip_address or self.vpn_instance_name: + if self.nickname: + if self.dfs_group_info["ipAddress"] not in ["0.0.0.0", None]: + self.module.fail_json(msg='Error: nickname and ip_address can not be exist at the ' + 'same time.') + if self.ip_address: + if self.dfs_group_info["localNickname"] not in ["0", None]: + self.module.fail_json(msg='Error: nickname and ip_address can not be exist at the ' + 'same time.') + self.modify_dfs_group() + else: + self.create_dfs_group() + else: + if not self.dfs_group_info: + self.module.fail_json( + msg='Error: DFS Group does not exist.') + if not self.nickname and not self.pseudo_nickname and not self.pseudo_priority and not self.priority_id\ + and not self.ip_address and not self.vpn_instance_name: + self.delete_dfs_group() + else: + self.updates_cmd.append("dfs-group 1") + self.delete_dfs_group_attribute() + self.delete_dfs_group_nick() + if "undo dfs-group 1" in self.updates_cmd: + self.updates_cmd = ["undo dfs-group 1"] + + if self.eth_trunk_id and not self.peer_link_id: + self.module.fail_json( + msg='Error: eth_trunk_id and peer_link_id must be config at the same time.') + if self.peer_link_id and not self.eth_trunk_id: + self.module.fail_json( + msg='Error: eth_trunk_id and peer_link_id must be config at the same time.') + + if self.eth_trunk_id and self.peer_link_id: + if self.state == "present": + self.modify_peer_link() + else: + if self.peer_link_info: + self.delete_peer_link() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + dfs_group_id=dict(type='str'), + nickname=dict(type='str'), + pseudo_nickname=dict(type='str'), + pseudo_priority=dict(type='str'), + ip_address=dict(type='str'), + vpn_instance_name=dict(type='str'), + priority_id=dict(type='str'), + eth_trunk_id=dict(type='str'), + peer_link_id=dict(type='str'), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = MlagConfig(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_interface.py new file mode 100644 index 00000000..0c2f07cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mlag_interface.py @@ -0,0 +1,1038 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mlag_interface +short_description: Manages MLAG interfaces on HUAWEI CloudEngine switches. +description: + - Manages MLAG interface attributes on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + eth_trunk_id: + description: + - Name of the local M-LAG interface. The value is ranging from 0 to 511. + dfs_group_id: + description: + - ID of a DFS group.The value is 1. + default: present + mlag_id: + description: + - ID of the M-LAG. The value is an integer that ranges from 1 to 2048. + mlag_system_id: + description: + - M-LAG global LACP system MAC address. The value is a string of 0 to 255 characters. The default value + is the MAC address of the Ethernet port of MPU. + mlag_priority_id: + description: + - M-LAG global LACP system priority. The value is an integer ranging from 0 to 65535. + The default value is 32768. + interface: + description: + - Name of the interface that enters the Error-Down state when the peer-link fails. + The value is a string of 1 to 63 characters. + mlag_error_down: + description: + - Configure the interface on the slave device to enter the Error-Down state. + choices: ['enable','disable'] + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] + +''' + +EXAMPLES = ''' +- name: Mlag interface module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Set interface mlag error down + community.network.ce_mlag_interface: + interface: 10GE2/0/1 + mlag_error_down: enable + provider: "{{ cli }}" + - name: Create mlag + community.network.ce_mlag_interface: + eth_trunk_id: 1 + dfs_group_id: 1 + mlag_id: 4 + provider: "{{ cli }}" + - name: Set mlag global attribute + community.network.ce_mlag_interface: + mlag_system_id: 0020-1409-0407 + mlag_priority_id: 5 + provider: "{{ cli }}" + - name: Set mlag interface attribute + community.network.ce_mlag_interface: + eth_trunk_id: 1 + mlag_system_id: 0020-1409-0400 + mlag_priority_id: 3 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { "interface": "eth-trunk1", + "mlag_error_down": "disable", + "state": "present" + } +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { "mlagErrorDownInfos": [ + { + "dfsgroupId": "1", + "portName": "Eth-Trunk1" + } + ] + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {} +updates: + description: command sent to the device + returned: always + type: list + sample: { "interface eth-trunk1", + "undo m-lag unpaired-port suspend"} +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_MLAG_INFO = """ + + + + + %s + + + + +""" + +CE_NC_CREATE_MLAG_INFO = """ + + + + + %s + %s + %s + + + + +""" + +CE_NC_DELETE_MLAG_INFO = """ + + + + + %s + %s + + + + +""" + +CE_NC_GET_LACP_MLAG_INFO = """ + + + + + %s + + + + + + + + +""" + +CE_NC_SET_LACP_MLAG_INFO_HEAD = """ + + + + + %s + +""" + +CE_NC_SET_LACP_MLAG_INFO_TAIL = """ + + + + + +""" + +CE_NC_GET_GLOBAL_LACP_MLAG_INFO = """ + + + + + + + + + + +""" + +CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD = """ + + + + +""" + +CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL = """ + + + + +""" + +CE_NC_GET_MLAG_ERROR_DOWN_INFO = """ + + + + + + + + + + + + +""" + +CE_NC_CREATE_MLAG_ERROR_DOWN_INFO = """ + + + + + 1 + %s + + + + +""" + +CE_NC_DELETE_MLAG_ERROR_DOWN_INFO = """ + + + + + 1 + %s + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class MlagInterface(object): + """ + Manages Manages MLAG interface information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.eth_trunk_id = self.module.params['eth_trunk_id'] + self.dfs_group_id = self.module.params['dfs_group_id'] + self.mlag_id = self.module.params['mlag_id'] + self.mlag_system_id = self.module.params['mlag_system_id'] + self.mlag_priority_id = self.module.params['mlag_priority_id'] + self.interface = self.module.params['interface'] + self.mlag_error_down = self.module.params['mlag_error_down'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # mlag info + self.commands = list() + self.mlag_info = None + self.mlag_global_info = None + self.mlag_error_down_info = None + self.mlag_trunk_attribute_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_mlag_info(self): + """ get mlag info.""" + + mlag_info = dict() + conf_str = CE_NC_GET_MLAG_INFO % ("Eth-Trunk%s" % self.eth_trunk_id) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + mlag_info["mlagInfos"] = list() + root = ElementTree.fromstring(xml_str) + dfs_mlag_infos = root.findall( + "./mlag/mlagInstances/mlagInstance") + + if dfs_mlag_infos: + for dfs_mlag_info in dfs_mlag_infos: + mlag_dict = dict() + for ele in dfs_mlag_info: + if ele.tag in ["dfsgroupId", "mlagId", "localMlagPort"]: + mlag_dict[ele.tag] = ele.text + mlag_info["mlagInfos"].append(mlag_dict) + return mlag_info + + def get_mlag_global_info(self): + """ get mlag global info.""" + + mlag_global_info = dict() + conf_str = CE_NC_GET_GLOBAL_LACP_MLAG_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "./ifmtrunk/lacpSysInfo/lacpMlagGlobal") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["lacpMlagSysId", "lacpMlagPriority"]: + mlag_global_info[site.tag] = site.text + return mlag_global_info + + def get_mlag_trunk_attribute_info(self): + """ get mlag global info.""" + + mlag_trunk_attribute_info = dict() + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + conf_str = CE_NC_GET_LACP_MLAG_INFO % eth_trunk + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_trunk_attribute_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "./ifmtrunk/TrunkIfs/TrunkIf/lacpMlagIf") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["lacpMlagSysId", "lacpMlagPriority"]: + mlag_trunk_attribute_info[site.tag] = site.text + return mlag_trunk_attribute_info + + def get_mlag_error_down_info(self): + """ get error down info.""" + + mlag_error_down_info = dict() + conf_str = CE_NC_GET_MLAG_ERROR_DOWN_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return mlag_error_down_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + mlag_error_down_info["mlagErrorDownInfos"] = list() + root = ElementTree.fromstring(xml_str) + mlag_error_infos = root.findall( + "./mlag/errordowns/errordown") + + if mlag_error_infos: + for mlag_error_info in mlag_error_infos: + mlag_error_dict = dict() + for ele in mlag_error_info: + if ele.tag in ["dfsgroupId", "portName"]: + mlag_error_dict[ele.tag] = ele.text + mlag_error_down_info[ + "mlagErrorDownInfos"].append(mlag_error_dict) + return mlag_error_down_info + + def check_macaddr(self): + """check mac-address whether valid""" + + valid_char = '0123456789abcdef-' + mac = self.mlag_system_id + + if len(mac) > 16: + return False + + mac_list = re.findall(r'([0-9a-fA-F]+)', mac) + if len(mac_list) != 3: + return False + + if mac.count('-') != 2: + return False + + for _, value in enumerate(mac, start=0): + if value.lower() not in valid_char: + return False + if all((int(mac_list[0], base=16) == 0, int(mac_list[1], base=16) == 0, int(mac_list[2], base=16) == 0)): + return False + a = "000" + mac_list[0] + b = "000" + mac_list[1] + c = "000" + mac_list[2] + self.mlag_system_id = "-".join([a[-4:], b[-4:], c[-4:]]) + return True + + def check_params(self): + """Check all input params""" + + # eth_trunk_id check + if self.eth_trunk_id: + if not self.eth_trunk_id.isdigit(): + self.module.fail_json( + msg='Error: The value of eth_trunk_id is an integer.') + if int(self.eth_trunk_id) < 0 or int(self.eth_trunk_id) > 511: + self.module.fail_json( + msg='Error: The value of eth_trunk_id is not in the range from 0 to 511.') + + # dfs_group_id check + if self.dfs_group_id: + if self.dfs_group_id != "1": + self.module.fail_json( + msg='Error: The value of dfs_group_id must be 1.') + + # mlag_id check + if self.mlag_id: + if not self.mlag_id.isdigit(): + self.module.fail_json( + msg='Error: The value of mlag_id is an integer.') + if int(self.mlag_id) < 1 or int(self.mlag_id) > 2048: + self.module.fail_json( + msg='Error: The value of mlag_id is not in the range from 1 to 2048.') + + # mlag_system_id check + if self.mlag_system_id: + if not self.check_macaddr(): + self.module.fail_json( + msg="Error: mlag_system_id has invalid value %s." % self.mlag_system_id) + + # mlag_priority_id check + if self.mlag_priority_id: + if not self.mlag_priority_id.isdigit(): + self.module.fail_json( + msg='Error: The value of mlag_priority_id is an integer.') + if int(self.mlag_priority_id) < 0 or int(self.mlag_priority_id) > 254: + self.module.fail_json( + msg='Error: The value of mlag_priority_id is not in the range from 0 to 254.') + + # interface check + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + def is_mlag_info_change(self): + """whether mlag info change""" + + if not self.mlag_info: + return True + + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + for info in self.mlag_info["mlagInfos"]: + if info["mlagId"] == self.mlag_id and info["localMlagPort"] == eth_trunk: + return False + return True + + def is_mlag_info_exist(self): + """whether mlag info exist""" + + if not self.mlag_info: + return False + + eth_trunk = "Eth-Trunk" + eth_trunk += self.eth_trunk_id + + for info in self.mlag_info["mlagInfos"]: + if info["localMlagPort"] == eth_trunk: + return True + return False + + def is_mlag_error_down_info_change(self): + """whether mlag error down info change""" + + if not self.mlag_error_down_info: + return True + + for info in self.mlag_error_down_info["mlagErrorDownInfos"]: + if info["portName"].upper() == self.interface.upper(): + return False + return True + + def is_mlag_error_down_info_exist(self): + """whether mlag error down info exist""" + + if not self.mlag_error_down_info: + return False + + for info in self.mlag_error_down_info["mlagErrorDownInfos"]: + if info["portName"].upper() == self.interface.upper(): + return True + return False + + def is_mlag_interface_info_change(self): + """whether mlag interface attribute info change""" + + if not self.mlag_trunk_attribute_info: + return True + + if self.mlag_system_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] != self.mlag_system_id: + return True + if self.mlag_priority_id: + if self.mlag_trunk_attribute_info["lacpMlagPriority"] != self.mlag_priority_id: + return True + return False + + def is_mlag_interface_info_exist(self): + """whether mlag interface attribute info exist""" + + if not self.mlag_trunk_attribute_info: + return False + + if self.mlag_system_id: + if self.mlag_priority_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id: + return True + + if self.mlag_priority_id: + if self.mlag_system_id: + if self.mlag_trunk_attribute_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_trunk_attribute_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + + return False + + def is_mlag_global_info_change(self): + """whether mlag global attribute info change""" + + if not self.mlag_global_info: + return True + + if self.mlag_system_id: + if self.mlag_global_info["lacpMlagSysId"] != self.mlag_system_id: + return True + if self.mlag_priority_id: + if self.mlag_global_info["lacpMlagPriority"] != self.mlag_priority_id: + return True + return False + + def is_mlag_global_info_exist(self): + """whether mlag global attribute info exist""" + + if not self.mlag_global_info: + return False + + if self.mlag_system_id: + if self.mlag_priority_id: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id: + return True + + if self.mlag_priority_id: + if self.mlag_system_id: + if self.mlag_global_info["lacpMlagSysId"] == self.mlag_system_id \ + and self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + else: + if self.mlag_global_info["lacpMlagPriority"] == self.mlag_priority_id: + return True + + return False + + def create_mlag(self): + """create mlag info""" + + if self.is_mlag_info_change(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_CREATE_MLAG_INFO % ( + self.dfs_group_id, self.mlag_id, mlag_port) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create mlag info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + self.updates_cmd.append("dfs-group %s m-lag %s" % + (self.dfs_group_id, self.mlag_id)) + self.changed = True + + def delete_mlag(self): + """delete mlag info""" + + if self.is_mlag_info_exist(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_DELETE_MLAG_INFO % ( + self.dfs_group_id, mlag_port) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete mlag info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + self.updates_cmd.append( + "undo dfs-group %s m-lag %s" % (self.dfs_group_id, self.mlag_id)) + self.changed = True + + def create_mlag_error_down(self): + """create mlag error down info""" + + if self.is_mlag_error_down_info_change(): + conf_str = CE_NC_CREATE_MLAG_ERROR_DOWN_INFO % self.interface + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create mlag error down info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("m-lag unpaired-port suspend") + self.changed = True + + def delete_mlag_error_down(self): + """delete mlag error down info""" + + if self.is_mlag_error_down_info_exist(): + + conf_str = CE_NC_DELETE_MLAG_ERROR_DOWN_INFO % self.interface + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete mlag error down info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append("undo m-lag unpaired-port suspend") + self.changed = True + + def set_mlag_interface(self): + """set mlag interface attribute info""" + + if self.is_mlag_interface_info_change(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_SET_LACP_MLAG_INFO_HEAD % mlag_port + if self.mlag_priority_id: + conf_str += "%s" % self.mlag_priority_id + if self.mlag_system_id: + conf_str += "%s" % self.mlag_system_id + conf_str += CE_NC_SET_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface attribute info failed.') + + self.updates_cmd.append("interface %s" % mlag_port) + if self.mlag_priority_id: + self.updates_cmd.append( + "lacp m-lag priority %s" % self.mlag_priority_id) + + if self.mlag_system_id: + self.updates_cmd.append( + "lacp m-lag system-id %s" % self.mlag_system_id) + self.changed = True + + def delete_mlag_interface(self): + """delete mlag interface attribute info""" + + if self.is_mlag_interface_info_exist(): + mlag_port = "Eth-Trunk" + mlag_port += self.eth_trunk_id + conf_str = CE_NC_SET_LACP_MLAG_INFO_HEAD % mlag_port + cmd = "interface %s" % mlag_port + self.cli_add_command(cmd) + + if self.mlag_priority_id: + cmd = "lacp m-lag priority %s" % self.mlag_priority_id + conf_str += "" + self.cli_add_command(cmd, True) + + if self.mlag_system_id: + cmd = "lacp m-lag system-id %s" % self.mlag_system_id + conf_str += "" + self.cli_add_command(cmd, True) + + if self.commands: + conf_str += CE_NC_SET_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface atrribute info failed.') + + self.changed = True + + def set_mlag_global(self): + """set mlag global attribute info""" + + if self.is_mlag_global_info_change(): + conf_str = CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD + if self.mlag_priority_id: + conf_str += "%s" % self.mlag_priority_id + if self.mlag_system_id: + conf_str += "%s" % self.mlag_system_id + conf_str += CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface attribute info failed.') + + if self.mlag_priority_id: + self.updates_cmd.append( + "lacp m-lag priority %s" % self.mlag_priority_id) + + if self.mlag_system_id: + self.updates_cmd.append( + "lacp m-lag system-id %s" % self.mlag_system_id) + self.changed = True + + def delete_mlag_global(self): + """delete mlag global attribute info""" + + xml_str = '' + if self.is_mlag_global_info_exist(): + if self.mlag_priority_id: + cmd = "lacp m-lag priority %s" % self.mlag_priority_id + xml_str += '' + self.cli_add_command(cmd, True) + + if self.mlag_system_id: + cmd = "lacp m-lag system-id %s" % self.mlag_system_id + xml_str += '' + self.cli_add_command(cmd, True) + + if xml_str != '': + conf_str = CE_NC_SET_GLOBAL_LACP_MLAG_INFO_HEAD + xml_str + CE_NC_SET_GLOBAL_LACP_MLAG_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set mlag interface atrribute info failed.') + self.changed = True + + def get_proposed(self): + """get proposed info""" + + if self.eth_trunk_id: + self.proposed["eth_trunk_id"] = self.eth_trunk_id + if self.dfs_group_id: + self.proposed["dfs_group_id"] = self.dfs_group_id + if self.mlag_id: + self.proposed["mlag_id"] = self.mlag_id + if self.mlag_system_id: + self.proposed["mlag_system_id"] = self.mlag_system_id + if self.mlag_priority_id: + self.proposed["mlag_priority_id"] = self.mlag_priority_id + if self.interface: + self.proposed["interface"] = self.interface + if self.mlag_error_down: + self.proposed["mlag_error_down"] = self.mlag_error_down + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + self.mlag_info = self.get_mlag_info() + self.mlag_global_info = self.get_mlag_global_info() + self.mlag_error_down_info = self.get_mlag_error_down_info() + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + if not self.mlag_system_id and not self.mlag_priority_id: + if self.mlag_info: + self.existing["mlagInfos"] = self.mlag_info["mlagInfos"] + + if self.mlag_system_id or self.mlag_priority_id: + if self.eth_trunk_id: + if self.mlag_trunk_attribute_info: + if self.mlag_system_id: + self.existing["lacpMlagSysId"] = self.mlag_trunk_attribute_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.existing["lacpMlagPriority"] = self.mlag_trunk_attribute_info[ + "lacpMlagPriority"] + else: + if self.mlag_global_info: + if self.mlag_system_id: + self.existing["lacpMlagSysId"] = self.mlag_global_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.existing["lacpMlagPriority"] = self.mlag_global_info[ + "lacpMlagPriority"] + + if self.interface or self.mlag_error_down: + if self.mlag_error_down_info: + self.existing["mlagErrorDownInfos"] = self.mlag_error_down_info[ + "mlagErrorDownInfos"] + + def get_end_state(self): + """get end state info""" + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + self.mlag_info = self.get_mlag_info() + if not self.mlag_system_id and not self.mlag_priority_id: + if self.mlag_info: + self.end_state["mlagInfos"] = self.mlag_info["mlagInfos"] + + if self.mlag_system_id or self.mlag_priority_id: + if self.eth_trunk_id: + self.mlag_trunk_attribute_info = self.get_mlag_trunk_attribute_info() + if self.mlag_trunk_attribute_info: + if self.mlag_system_id: + self.end_state["lacpMlagSysId"] = self.mlag_trunk_attribute_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.end_state["lacpMlagPriority"] = self.mlag_trunk_attribute_info[ + "lacpMlagPriority"] + else: + self.mlag_global_info = self.get_mlag_global_info() + if self.mlag_global_info: + if self.mlag_system_id: + self.end_state["lacpMlagSysId"] = self.mlag_global_info[ + "lacpMlagSysId"] + if self.mlag_priority_id: + self.end_state["lacpMlagPriority"] = self.mlag_global_info[ + "lacpMlagPriority"] + + if self.interface or self.mlag_error_down: + self.mlag_error_down_info = self.get_mlag_error_down_info() + if self.mlag_error_down_info: + self.end_state["mlagErrorDownInfos"] = self.mlag_error_down_info[ + "mlagErrorDownInfos"] + + def work(self): + """worker""" + + self.check_params() + self.get_proposed() + self.get_existing() + + if self.eth_trunk_id or self.dfs_group_id or self.mlag_id: + self.mlag_info = self.get_mlag_info() + if self.eth_trunk_id and self.dfs_group_id and self.mlag_id: + if self.state == "present": + self.create_mlag() + else: + self.delete_mlag() + else: + if not self.mlag_system_id and not self.mlag_priority_id: + self.module.fail_json( + msg='Error: eth_trunk_id, dfs_group_id, mlag_id must be config at the same time.') + + if self.mlag_system_id or self.mlag_priority_id: + + if self.eth_trunk_id: + self.mlag_trunk_attribute_info = self.get_mlag_trunk_attribute_info() + if self.mlag_system_id or self.mlag_priority_id: + if self.state == "present": + self.set_mlag_interface() + else: + self.delete_mlag_interface() + else: + self.mlag_global_info = self.get_mlag_global_info() + if self.mlag_system_id or self.mlag_priority_id: + if self.state == "present": + self.set_mlag_global() + else: + self.delete_mlag_global() + + if self.interface or self.mlag_error_down: + self.mlag_error_down_info = self.get_mlag_error_down_info() + if self.interface and self.mlag_error_down: + if self.mlag_error_down == "enable": + self.create_mlag_error_down() + else: + self.delete_mlag_error_down() + else: + self.module.fail_json( + msg='Error: interface, mlag_error_down must be config at the same time.') + + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + eth_trunk_id=dict(type='str'), + dfs_group_id=dict(type='str'), + mlag_id=dict(type='str'), + mlag_system_id=dict(type='str'), + mlag_priority_id=dict(type='str'), + interface=dict(type='str'), + mlag_error_down=dict(type='str', choices=['enable', 'disable']), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = MlagInterface(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mtu.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mtu.py new file mode 100644 index 00000000..db902480 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_mtu.py @@ -0,0 +1,581 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_mtu +short_description: Manages MTU settings on HUAWEI CloudEngine switches. +description: + - Manages MTU settings on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - Either C(sysmtu) param is required or C(interface) AND C(mtu) params are req'd. + - C(state=absent) unconfigures a given MTU if that value is currently present. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of interface, i.e. 40GE1/0/22. + mtu: + description: + - MTU for a specific interface. + The value is an integer ranging from 46 to 9600, in bytes. + jumbo_max: + description: + - Maximum frame size. The default value is 9216. + The value is an integer and expressed in bytes. The value range is 1536 to 12224 for the CE12800 + and 1536 to 12288 for ToR switches. + jumbo_min: + description: + - Non-jumbo frame size threshold. The default value is 1518. + The value is an integer that ranges from 1518 to jumbo_max, in bytes. + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Mtu test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config jumboframe on 40GE1/0/22" + community.network.ce_mtu: + interface: 40GE1/0/22 + jumbo_max: 9000 + jumbo_min: 8000 + provider: "{{ cli }}" + + - name: "Config mtu on 40GE1/0/22 (routed interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 1600 + provider: "{{ cli }}" + + - name: "Config mtu on 40GE1/0/23 (switched interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 9216 + provider: "{{ cli }}" + + - name: "Config mtu and jumboframe on 40GE1/0/22 (routed interface)" + community.network.ce_mtu: + interface: 40GE1/0/22 + mtu: 1601 + jumbo_max: 9001 + jumbo_min: 8001 + provider: "{{ cli }}" + + - name: "Unconfigure mtu and jumboframe on a given interface" + community.network.ce_mtu: + state: absent + interface: 40GE1/0/22 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} +existing: + description: k/v pairs of existing mtu/sysmtu on the interface/system + returned: always + type: dict + sample: {"mtu": "1600", "jumbo_max": "9216", "jumbo_min": "1518"} +end_state: + description: k/v pairs of mtu/sysmtu values after module execution + returned: always + type: dict + sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface 40GE1/0/23", "mtu 1700", "jumboframe enable 9000 8000"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +import copy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible.module_utils.connection import exec_command + + +def is_interface_support_setjumboframe(interface): + """is interface support set jumboframe""" + + if interface is None: + return False + support_flag = False + if interface.upper().startswith('GE'): + support_flag = True + elif interface.upper().startswith('10GE'): + support_flag = True + elif interface.upper().startswith('25GE'): + support_flag = True + elif interface.upper().startswith('4X10GE'): + support_flag = True + elif interface.upper().startswith('40GE'): + support_flag = True + elif interface.upper().startswith('100GE'): + support_flag = True + else: + support_flag = False + return support_flag + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Mtu(object): + """set mtu""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface info + self.interface = self.module.params['interface'] + self.mtu = self.module.params['mtu'] + self.state = self.module.params['state'] + self.jbf_max = self.module.params['jumbo_max'] or None + self.jbf_min = self.module.params['jumbo_min'] or None + self.jbf_config = list() + self.jbf_cli = "" + self.commands = list() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intf_info = dict() # one interface info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """ init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + intf_info = dict() + + flags = list() + exp = r"| ignore-case section include ^#\s+interface %s\s+" % ifname.replace(" ", "") + flags.append(exp) + output = self.get_config(flags) + output_list = output.split('\n') + if output_list is None: + return intf_info + + mtu = None + for config in output_list: + config = config.strip() + if config.startswith('mtu'): + mtu = re.findall(r'.*mtu\s*([0-9]*)', output)[0] + + intf_info = dict(ifName=ifname, + ifMtu=mtu) + + return intf_info + + def prase_jumboframe_para(self, config_str): + """prase_jumboframe_para""" + + interface_cli = "interface %s" % (self.interface.replace(" ", "").lower()) + if config_str.find(interface_cli) == -1: + self.module.fail_json(msg='Error: Interface does not exist.') + + try: + npos1 = config_str.index('jumboframe enable') + except ValueError: + # return default vale + return [9216, 1518] + try: + npos2 = config_str.index('\n', npos1) + config_str_tmp = config_str[npos1:npos2] + except ValueError: + config_str_tmp = config_str[npos1:] + + return re.findall(r'([0-9]+)', config_str_tmp) + + def cli_load_config(self): + """load config by cli""" + + if not self.module.check_mode: + if len(self.commands) > 1: + load_config(self.module, self.commands) + self.changed = True + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + + def get_jumboframe_config(self): + """ get_jumboframe_config""" + + flags = list() + exp = r"| ignore-case section include ^#\s+interface %s\s+" % self.interface.replace(" ", "") + flags.append(exp) + output = self.get_config(flags) + output = output.replace('*', '').lower() + + return self.prase_jumboframe_para(output) + + def set_jumboframe(self): + """ set_jumboframe""" + + if self.state == "present": + if not self.jbf_max and not self.jbf_min: + return + + jbf_value = self.get_jumboframe_config() + self.jbf_config = copy.deepcopy(jbf_value) + if len(jbf_value) == 1: + jbf_value.append("1518") + self.jbf_config.append("1518") + if not self.jbf_max: + return + + if (len(jbf_value) > 2) or (len(jbf_value) == 0): + self.module.fail_json( + msg='Error: Get jubmoframe config value num error.') + if self.jbf_min is None: + if jbf_value[0] == self.jbf_max: + return + else: + if (jbf_value[0] == self.jbf_max) \ + and (jbf_value[1] == self.jbf_min): + return + if jbf_value[0] != self.jbf_max: + jbf_value[0] = self.jbf_max + if (jbf_value[1] != self.jbf_min) and (self.jbf_min is not None): + jbf_value[1] = self.jbf_min + else: + jbf_value.pop(1) + else: + jbf_value = self.get_jumboframe_config() + self.jbf_config = copy.deepcopy(jbf_value) + if (jbf_value == [9216, 1518]): + return + jbf_value = [9216, 1518] + + if len(jbf_value) == 2: + self.jbf_cli = "jumboframe enable %s %s" % ( + jbf_value[0], jbf_value[1]) + else: + self.jbf_cli = "jumboframe enable %s" % (jbf_value[0]) + self.cli_add_command(self.jbf_cli) + + if self.state == "present": + if self.jbf_min: + self.updates_cmd.append( + "jumboframe enable %s %s" % (self.jbf_max, self.jbf_min)) + else: + self.updates_cmd.append("jumboframe enable %s" % (self.jbf_max)) + else: + self.updates_cmd.append("undo jumboframe enable") + + return + + def merge_interface(self, ifname, mtu): + """ Merge interface mtu.""" + + xmlstr = '' + change = False + + command = "interface %s" % ifname + self.cli_add_command(command) + + if self.state == "present": + if mtu and self.intf_info["ifMtu"] != mtu: + command = "mtu %s" % mtu + self.cli_add_command(command) + self.updates_cmd.append("mtu %s" % mtu) + change = True + else: + if self.intf_info["ifMtu"] != '1500' and self.intf_info["ifMtu"]: + command = "mtu 1500" + self.cli_add_command(command) + self.updates_cmd.append("undo mtu") + change = True + + return + + def check_params(self): + """Check all input params""" + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface %s is error.') + + # mtu check mtu + if self.mtu: + if not self.mtu.isdigit(): + self.module.fail_json(msg='Error: Mtu is invalid.') + # check mtu range + if int(self.mtu) < 46 or int(self.mtu) > 9600: + self.module.fail_json( + msg='Error: Mtu is not in the range from 46 to 9600.') + # get interface info + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: interface does not exist.') + + # check interface can set jumbo frame + if self.state == 'present': + if self.jbf_max: + if not is_interface_support_setjumboframe(self.interface): + self.module.fail_json( + msg='Error: Interface %s does not support jumboframe set.' % self.interface) + if not self.jbf_max.isdigit(): + self.module.fail_json( + msg='Error: Max jumboframe is not digit.') + if (int(self.jbf_max) > 12288) or (int(self.jbf_max) < 1536): + self.module.fail_json( + msg='Error: Max jumboframe is between 1536 to 12288.') + + if self.jbf_min: + if not self.jbf_min.isdigit(): + self.module.fail_json( + msg='Error: Min jumboframe is not digit.') + if not self.jbf_max: + self.module.fail_json( + msg='Error: please specify max jumboframe value.') + if (int(self.jbf_min) > int(self.jbf_max)) or (int(self.jbf_min) < 1518): + self.module.fail_json( + msg='Error: Min jumboframe is between ' + '1518 to jumboframe max value.') + + if self.jbf_min is not None: + if self.jbf_max is None: + self.module.fail_json( + msg='Error: please input MAX jumboframe ' + 'value.') + + def get_proposed(self): + """ get_proposed""" + + self.proposed['state'] = self.state + if self.interface: + self.proposed["interface"] = self.interface + + if self.state == 'present': + if self.mtu: + self.proposed["mtu"] = self.mtu + if self.jbf_max: + if self.jbf_min: + self.proposed["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_max, self.jbf_min) + else: + self.proposed[ + "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) + + def get_existing(self): + """ get_existing""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + self.existing["mtu"] = self.intf_info["ifMtu"] + + if self.intf_info: + if not self.existing["interface"]: + self.existing["interface"] = self.interface + + if len(self.jbf_config) != 2: + return + + self.existing["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_config[0], self.jbf_config[1]) + + def get_end_state(self): + """ get_end_state""" + + if self.intf_info: + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + self.end_state["mtu"] = end_info["ifMtu"] + if self.intf_info: + if not self.end_state["interface"]: + self.end_state["interface"] = self.interface + + if self.state == 'absent': + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + 9216, 1518) + elif not self.jbf_max and not self.jbf_min: + if len(self.jbf_config) != 2: + return + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_config[0], self.jbf_config[1]) + elif self.jbf_min: + self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( + self.jbf_max, self.jbf_min) + else: + self.end_state[ + "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + self.check_params() + + self.get_proposed() + + self.merge_interface(self.interface, self.mtu) + self.set_jumboframe() + self.cli_load_config() + + self.get_existing() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + mtu=dict(type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + jumbo_max=dict(type='str'), + jumbo_min=dict(type='str'), + ) + argument_spec.update(ce_argument_spec) + interface = Mtu(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_global.py new file mode 100644 index 00000000..e6ab24fd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_global.py @@ -0,0 +1,286 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_multicast_global +version_added: '0.2.0' +author: xuxiaowei0512 (@xuxiaowei0512) +short_description: Manages multicast global configuration on HUAWEI CloudEngine switches. +description: + - Manages multicast global on HUAWEI CloudEngine switches. +notes: + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - This module works with connection C(netconf). +options: + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + vrf: + description: + - VPN instance of destination ip address. + type: str + state: + description: + - Specify desired state of the resource. + type: str + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +--- + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + state: absent + provider: "{{ cli }}" + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + state: present + provider: "{{ cli }}" + - name: Multicast routing-enable + community.network.ce_multicast_global: + aftype: v4 + vrf: vrf1 + provider: "{{ cli }}" + +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["multicast routing-enable"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_MULTICAST_GLOBAL = """ + + + + + %s + %s + + + + +""" +CE_NC_MERGE_MULTICAST_GLOBAL = """ + + + + %s + %s + + + +""" +CE_NC_DELETE_MULTICAST_GLOBAL = """ + + + + %s + %s + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +class MulticastGlobal(object): + """multicast global module""" + + def __init__(self, argument_spec): + """multicast global info""" + self.spec = argument_spec + self.module = None + self._initmodule_() + + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.version = "ipv4unicast" + else: + self.version = "ipv6unicast" + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.multicast_global_info = dict() + + def _initmodule_(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_change_state(self): + """set change state""" + state = self.state + change = False + self.get_multicast_global() + # new or edit + if state == 'present': + if not self.multicast_global_info.get('multicast_global'): + # i.e. self.multicast_global_info['multicast_global'] has not value + change = True + else: + # delete + if self.multicast_global_info.get('multicast_global'): + # i.e. self.multicast_global_info['multicast_global'] has value + change = True + self.changed = change + + def get_multicast_global(self): + """get one data""" + self.multicast_global_info["multicast_global"] = list() + getxmlstr = CE_NC_GET_MULTICAST_GLOBAL % ( + self.version, self.vrf) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + mcast_enable = root.findall( + "mcastbase/mcastAfsEnables/mcastAfsEnable") + if mcast_enable: + # i.e. mcast_enable = [{vrfName:11,addressFamily:'xx'},{vrfName:22,addressFamily:'xx'}...] + for mcast_enable_key in mcast_enable: + # i.e. mcast_enable_key = {vrfName:11,addressFamily:'xx'} + mcast_info = dict() + for ele in mcast_enable_key: + if ele.tag in ["vrfName", "addressFamily"]: + mcast_info[ele.tag] = ele.text + self.multicast_global_info['multicast_global'].append(mcast_info) + + def get_existing(self): + """get existing information""" + self.set_change_state() + self.existing["multicast_global"] = self.multicast_global_info["multicast_global"] + + def get_proposed(self): + """get proposed information""" + self.proposed['addressFamily'] = self.version + self.proposed['state'] = self.state + self.proposed['vrfName'] = self.vrf + + def set_multicast_global(self): + """set multicast global""" + if not self.changed: + return + version = self.version + state = self.state + if state == "present": + configxmlstr = CE_NC_MERGE_MULTICAST_GLOBAL % (self.vrf, version) + else: + configxmlstr = CE_NC_DELETE_MULTICAST_GLOBAL % (self.vrf, version) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_MULTICAST_GLOBAL") + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('multicast routing-enable') + else: + self.updates_cmd.append('undo multicast routing-enable') + + def get_end_state(self): + """get end state information""" + self.get_multicast_global() + self.end_state["multicast_global"] = self.multicast_global_info["multicast_global"] + + def work(self): + """worker""" + self.get_existing() + self.get_proposed() + self.set_multicast_global() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['existing'] = self.existing + self.results['proposed'] = self.proposed + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + aftype=dict(choices=['v4', 'v6'], required=True), + vrf=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], default='present', required=False), + ) + interface = MulticastGlobal(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_igmp_enable.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_igmp_enable.py new file mode 100644 index 00000000..1432f19a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_multicast_igmp_enable.py @@ -0,0 +1,543 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_multicast_igmp_enable +version_added: '0.2.0' +author: xuxiaowei0512 (@CloudEngine-Ansible) +short_description: Manages multicast igmp enable configuration on HUAWEI CloudEngine switches. +description: + - Manages multicast igmp on HUAWEI CloudEngine switches. +notes: + - If no vrf is supplied, vrf is set to default. + If I(state=absent), the route will be removed, regardless of the + non-required parameters. + - This module requires the netconf system service be enabled on + the remote device being managed. + - This module works with connection C(netconf). +options: + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + features: + description: + - Distinguish between Globally Enabled IGMP or + - Enabled IGMP under vlanID. + required: true + type: str + choices: ['global','vlan'] + vlan_id: + description: + - Virtual LAN identity. + type: int + igmp: + description: + - Enable Layer 2 multicast Snooping in a VLAN. + type: bool + default: false + version: + description: + - Specifies the IGMP version that can be processed. + default: 2 + type: int + proxy: + description: + - Layer 2 multicast snooping proxy is enabled. + type: bool + default: false + state: + description: + - Specify desired state of the resource. + choices: ['present','absent'] + default: present + type: str +''' + +EXAMPLES = ''' + + - name: Configure global igmp enable + community.network.ce_multicast_igmp_enable: + aftype: v4 + features: 'global' + state: present + + - name: Configure global igmp disable + community.network.ce_multicast_igmp_enable: + features: 'global' + aftype: v4 + state: absent + + - name: Configure vlan igmp enable + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + igmp: true + + - name: New proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + proxy: true + igmp: true + version: 1 + + - name: Modify proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + version: 2 + + - name: Delete proxy,igmp,version + community.network.ce_multicast_igmp_enable: + features: 'vlan' + aftype: v4 + vlan_id: 1 + state: absent +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"addrFamily": "ipv4unicast", "features": "vlan", "proxyEnable": "false", + "snoopingEnable": "false", "state": "absent", "version": 2, "vlanId": 1} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["undo igmp snooping enable", + "undo igmp snooping version", + "undo igmp snooping proxy"] +changed: + description: check if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_IGMP_GLOBAL = """ + + + + + %s + + + + +""" +CE_NC_MERGE_IGMP_SYSVIEW = """ + + + + %s + + + +""" +CE_NC_DELETE_IGMP_SYSVIEW = """ + + + + %s + + + +""" +CE_NC_GET_IGMP_VLAN_INFO = """ + + + + + + %s + %s + + + + + + + + +""" +CE_NC_MERGE_IGMP_VLANVIEW = """ + + + + + %s + %s%s%s%s + + + + +""" +CE_NC_MERGE_IGMP_VLANVIEW_SNOENABLE = """ +%s +""" +CE_NC_MERGE_IGMP_VLANVIEW_VERSION = """ +%s +""" +CE_NC_MERGE_IGMP_VLANVIEW_PROXYENABLE = """ +%s +""" +CE_NC_DELETE_IGMP_VLANVIEW = """ + + + + + %s + %s + + + + +""" + + +def get_xml(xml, value): + """operate xml""" + tempxml = xml % value + return tempxml + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +class IgmpSnoop(object): + """igmp snooping module""" + + def __init__(self, argument_spec): + """igmp snooping info""" + self.spec = argument_spec + self.module = None + self._initmodule_() + + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.addr_family = "ipv4unicast" + else: + self.addr_family = "ipv6unicast" + self.features = self.module.params['features'] + self.vlan_id = self.module.params['vlan_id'] + self.igmp = str(self.module.params['igmp']).lower() + self.version = self.module.params['version'] + if self.version is None: + self.version = 2 + self.proxy = str(self.module.params['proxy']).lower() + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.igmp_info_data = dict() + + def _initmodule_(self): + """init module""" + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def _checkparams_(self): + """check all input params""" + # check vlan id + if self.features == 'vlan': + if not self.vlan_id: + self.module.fail_json(msg='Error: missing required arguments: vlan_id.') + + if self.vlan_id: + if self.vlan_id <= 0 or self.vlan_id > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + # check version + if self.version: + if self.version <= 0 or self.version > 3: + self.module.fail_json( + msg='Error: Version id is not in the range from 1 to 3.') + + def set_change_state(self): + """set change state""" + state = self.state + change = False + # vlan view igmp + if self.features == 'vlan': + self.get_igmp_vlan() + change = self.compare_data() + else: + # sys view igmp(global) + self.get_igmp_global() + # new or edit + if state == 'present': + if not self.igmp_info_data["igmp_info"]: + # igmp_info_data has not igmp_info value. + change = True + else: + # delete + if self.igmp_info_data["igmp_info"]: + # igmp_info_data has not igmp_info value. + change = True + self.changed = change + + def compare_data(self): + """compare new data and old data""" + state = self.state + change = False + # new or edit + if state == 'present': + # edit + if self.igmp_info_data["igmp_info"]: + for data in self.igmp_info_data["igmp_info"]: + if self.addr_family == data["addrFamily"] and str(self.vlan_id) == data["vlanId"]: + if self.igmp: + if self.igmp != data["snoopingEnable"]: + change = True + if self.version: + if str(self.version) != data["version"]: + change = True + if self.proxy: + if self.proxy != data["proxyEnable"]: + change = True + # new + else: + change = True + else: + # delete + if self.igmp_info_data["igmp_info"]: + change = True + return change + + def get_igmp_vlan(self): + """get igmp vlan info data""" + self.igmp_info_data["igmp_info"] = list() + getxmlstr = CE_NC_GET_IGMP_VLAN_INFO % (self.addr_family, self.vlan_id) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + igmp_enable = root.findall( + "l2mc/vlan/l2McVlanCfgs/l2McVlanCfg") + if igmp_enable: + # igmp_enable = [{addressFamily:'xx'}] + for igmp_enable_key in igmp_enable: + # igmp_enable_key = {addressFamily:'xx'} + igmp_global_info = dict() + for ele in igmp_enable_key: + if ele.tag in ["addrFamily", "vlanId", "snoopingEnable", "version", "proxyEnable"]: + igmp_global_info[ele.tag] = ele.text + self.igmp_info_data["igmp_info"].append(igmp_global_info) + + def get_igmp_global(self): + """get igmp global data""" + self.igmp_info_data["igmp_info"] = list() + getxmlstr = CE_NC_GET_IGMP_GLOBAL % ( + self.addr_family) + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + igmp_enable = root.findall( + 'l2mc/l2McSnpgEnables/l2McSnpgEnable') + if igmp_enable: + # igmp_enable = [{addressFamily:'xx'}] + for igmp_enable_key in igmp_enable: + # igmp_enable_key = {addressFamily:'xx'} + igmp_global_info = dict() + for ele in igmp_enable_key: + if ele.tag in ["addrFamily"]: + igmp_global_info[ele.tag] = ele.text + self.igmp_info_data["igmp_info"].append(igmp_global_info) + + def set_vlanview_igmp(self): + """set igmp of vlanview""" + if not self.changed: + return + addr_family = self.addr_family + state = self.state + igmp_xml = """\n""" + version_xml = """\n""" + proxy_xml = """\n""" + if state == "present": + if self.igmp: + igmp_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_SNOENABLE, self.igmp.lower()) + if str(self.version): + version_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_VERSION, self.version) + if self.proxy: + proxy_xml = get_xml(CE_NC_MERGE_IGMP_VLANVIEW_PROXYENABLE, self.proxy.lower()) + configxmlstr = CE_NC_MERGE_IGMP_VLANVIEW % ( + addr_family, self.vlan_id, igmp_xml, version_xml, proxy_xml) + else: + configxmlstr = CE_NC_DELETE_IGMP_VLANVIEW % (addr_family, self.vlan_id) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_VLANVIEW_IGMP") + + def set_sysview_igmp(self): + """set igmp of sysview""" + if not self.changed: + return + version = self.addr_family + state = self.state + if state == "present": + configxmlstr = CE_NC_MERGE_IGMP_SYSVIEW % (version) + else: + configxmlstr = CE_NC_DELETE_IGMP_SYSVIEW % (version) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "SET_SYSVIEW_IGMP") + + def set_sysview_cmd(self): + """set sysview update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('igmp snooping enable') + else: + self.updates_cmd.append('undo igmp snooping enable') + + def set_vlanview_cmd(self): + """set vlanview update command""" + if not self.changed: + return + if self.state == "present": + if self.igmp: + if self.igmp.lower() == 'true': + self.updates_cmd.append('igmp snooping enable') + else: + self.updates_cmd.append('undo igmp snooping enable') + if str(self.version): + self.updates_cmd.append('igmp snooping version %s' % (self.version)) + else: + self.updates_cmd.append('undo igmp snooping version') + if self.proxy: + if self.proxy.lower() == 'true': + self.updates_cmd.append('igmp snooping proxy') + else: + self.updates_cmd.append('undo igmp snooping proxy') + + else: + self.updates_cmd.append('undo igmp snooping enable') + self.updates_cmd.append('undo igmp snooping version') + self.updates_cmd.append('undo igmp snooping proxy') + + def get_existing(self): + """get existing information""" + self.set_change_state() + self.existing["igmp_info"] = self.igmp_info_data["igmp_info"] + + def get_proposed(self): + """get proposed information""" + self.proposed['addrFamily'] = self.addr_family + self.proposed['features'] = self.features + if self.features == 'vlan': + self.proposed['snoopingEnable'] = self.igmp + self.proposed['version'] = self.version + self.proposed['vlanId'] = self.vlan_id + self.proposed['proxyEnable'] = self.proxy + self.proposed['state'] = self.state + + def set_igmp_netconf(self): + """config netconf""" + if self.features == 'vlan': + self.set_vlanview_igmp() + else: + self.set_sysview_igmp() + + def set_update_cmd(self): + """set update command""" + if self.features == 'vlan': + self.set_vlanview_cmd() + else: + self.set_sysview_cmd() + + def get_end_state(self): + """get end state information""" + if self.features == 'vlan': + self.get_igmp_vlan() + else: + # sys view igmp(global) + self.get_igmp_global() + self.end_state["igmp_info"] = self.igmp_info_data["igmp_info"] + + def work(self): + """worker""" + self._checkparams_() + self.get_existing() + self.get_proposed() + self.set_igmp_netconf() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['existing'] = self.existing + self.results['proposed'] = self.proposed + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """main""" + argument_spec = dict( + aftype=dict(choices=['v4', 'v6'], required=True), + features=dict(required=True, choices=['global', 'vlan'], type='str'), + vlan_id=dict(type='int'), + igmp=dict(type='bool', default=False), + version=dict(type='int', default=2), + proxy=dict(type='bool', default=False), + state=dict(choices=['absent', 'present'], default='present'), + ) + interface = IgmpSnoop(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netconf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netconf.py new file mode 100644 index 00000000..4efbca3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netconf.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netconf +short_description: Run an arbitrary netconf command on HUAWEI CloudEngine switches. +description: + - Sends an arbitrary netconf command on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + rpc: + description: + - The type of rpc. + required: true + choices: ['get', 'edit-config', 'execute-action', 'execute-cli'] + cfg_xml: + description: + - The config xml string. + required: true +''' + +EXAMPLES = ''' + +- name: CloudEngine netconf test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Netconf get operation" + community.network.ce_netconf: + rpc: get + cfg_xml: ' + + + + 10 + + + + + + + + + ' + provider: "{{ cli }}" + + - name: "Netconf edit-config operation" + community.network.ce_netconf: + rpc: edit-config + cfg_xml: ' + + + + default_wdz + local + invalid + + + + ' + provider: "{{ cli }}" + + - name: "Netconf execute-action operation" + community.network.ce_netconf: + rpc: execute-action + cfg_xml: ' + + + ipv4unicast + + + ' + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"result": ["ok"]} +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import execute_nc_action, ce_argument_spec, execute_nc_cli + + +def main(): + """ main """ + + argument_spec = dict( + rpc=dict(choices=['get', 'edit-config', + 'execute-action', 'execute-cli'], required=True), + cfg_xml=dict(required=True) + ) + + argument_spec.update(ce_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + rpc = module.params['rpc'] + cfg_xml = module.params['cfg_xml'] + changed = False + end_state = dict() + + if rpc == "get": + + response = get_nc_config(module, cfg_xml) + + if "" in response: + end_state["result"] = "" + else: + tmp1 = response.split(r"") + tmp2 = tmp1[1].split(r"") + result = tmp2[0].split("\n") + + end_state["result"] = result + + elif rpc == "edit-config": + + response = set_nc_config(module, cfg_xml) + + if "" not in response: + module.fail_json(msg='rpc edit-config failed.') + + changed = True + end_state["result"] = "ok" + + elif rpc == "execute-action": + + response = execute_nc_action(module, cfg_xml) + + if "" not in response: + module.fail_json(msg='rpc execute-action failed.') + + changed = True + end_state["result"] = "ok" + + elif rpc == "execute-cli": + + response = execute_nc_cli(module, cfg_xml) + + if "" in response: + end_state["result"] = "" + else: + tmp1 = response.split(r"") + tmp2 = tmp1[1].split(r"") + result = tmp2[0].split("\n") + + end_state["result"] = result + + else: + module.fail_json(msg='please input correct rpc.') + + results = dict() + results['changed'] = changed + results['end_state'] = end_state + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_aging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_aging.py new file mode 100644 index 00000000..8f4c046a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_aging.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_aging +short_description: Manages timeout mode of NetStream on HUAWEI CloudEngine switches. +description: + - Manages timeout mode of NetStream on HUAWEI CloudEngine switches. +author: YangYang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + timeout_interval: + description: + - Netstream timeout interval. + If is active type the interval is 1-60. + If is inactive ,the interval is 5-600. + default: 30 + type: + description: + - Specifies the packet type of netstream timeout active interval. + choices: ['ip', 'vxlan'] + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present + timeout_type: + description: + - Netstream timeout type. + choices: ['active', 'inactive', 'tcp-session', 'manual'] + manual_slot: + description: + - Specifies the slot number of netstream manual timeout. +''' + +EXAMPLES = ''' +- name: Netstream aging module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure netstream ip timeout active interval , the interval is 40 minutes. + community.network.ce_netstream_aging: + timeout_interval: 40 + type: ip + timeout_type: active + state: present + provider: "{{ cli }}" + + - name: Configure netstream vxlan timeout active interval , the interval is 40 minutes. + community.network.ce_netstream_aging: + timeout_interval: 40 + type: vxlan + timeout_type: active + active_state: present + provider: "{{ cli }}" + + - name: Delete netstream ip timeout active interval , set the ip timeout interval to 30 minutes. + community.network.ce_netstream_aging: + type: ip + timeout_type: active + state: absent + provider: "{{ cli }}" + + - name: Delete netstream vxlan timeout active interval , set the vxlan timeout interval to 30 minutes. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: active + state: absent + provider: "{{ cli }}" + + - name: Enable netstream ip tcp session timeout. + community.network.ce_netstream_aging: + type: ip + timeout_type: tcp-session + state: present + provider: "{{ cli }}" + + - name: Enable netstream vxlan tcp session timeout. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: tcp-session + state: present + provider: "{{ cli }}" + + - name: Disable netstream ip tcp session timeout. + community.network.ce_netstream_aging: + type: ip + timeout_type: tcp-session + state: absent + provider: "{{ cli }}" + + - name: Disable netstream vxlan tcp session timeout. + community.network.ce_netstream_aging: + type: vxlan + timeout_type: tcp-session + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"timeout_interval": "40", + "type": "ip", + "state": "absent", + "timeout_type": active} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"active_timeout": [ + { + "ip": "40", + "vxlan": 30 + } + ], + "inactive_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "tcp_timeout": [ + { + "ip": "disable", + "vxlan": "disable" + } + ]} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"active_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "inactive_timeout": [ + { + "ip": 30, + "vxlan": 30 + } + ], + "tcp_timeout": [ + { + "ip": "disable", + "vxlan": "disable" + } + ]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["undo netstream timeout ip active 40"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +class NetStreamAging(object): + """ + Manages netstream aging. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.timeout_interval = self.module.params['timeout_interval'] + self.type = self.module.params['type'] + self.state = self.module.params['state'] + self.timeout_type = self.module.params['timeout_type'] + self.manual_slot = self.module.params['manual_slot'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # local parameters + self.existing["active_timeout"] = list() + self.existing["inactive_timeout"] = list() + self.existing["tcp_timeout"] = list() + self.end_state["active_timeout"] = list() + self.end_state["inactive_timeout"] = list() + self.end_state["tcp_timeout"] = list() + self.active_changed = False + self.inactive_changed = False + self.tcp_changed = False + + def init_module(self): + """init module""" + + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) + + def get_exist_timer_out_para(self): + """Get exist netstream timeout parameters""" + + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + active_tmp["ip"] = "30" + active_tmp["vxlan"] = "30" + inactive_tmp["ip"] = "30" + inactive_tmp["vxlan"] = "30" + tcp_tmp["ip"] = "absent" + tcp_tmp["vxlan"] = "absent" + + cmd = "display current-configuration | include ^netstream timeout" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 4 and config_mem_list[2] == "ip": + if config_mem_list[3] == "active": + active_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "inactive": + inactive_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "tcp-session": + tcp_tmp["ip"] = "present" + if len(config_mem_list) > 4 and config_mem_list[2] == "vxlan": + if config_mem_list[4] == "active": + active_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "inactive": + inactive_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "tcp-session": + tcp_tmp["vxlan"] = "present" + self.existing["active_timeout"].append(active_tmp) + self.existing["inactive_timeout"].append(inactive_tmp) + self.existing["tcp_timeout"].append(tcp_tmp) + + def get_end_timer_out_para(self): + """Get end netstream timeout parameters""" + + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + active_tmp["ip"] = "30" + active_tmp["vxlan"] = "30" + inactive_tmp["ip"] = "30" + inactive_tmp["vxlan"] = "30" + tcp_tmp["ip"] = "absent" + tcp_tmp["vxlan"] = "absent" + cmd = "display current-configuration | include ^netstream timeout" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 4 and config_mem_list[2] == "ip": + if config_mem_list[3] == "active": + active_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "inactive": + inactive_tmp["ip"] = config_mem_list[4] + if config_mem_list[3] == "tcp-session": + tcp_tmp["ip"] = "present" + if len(config_mem_list) > 4 and config_mem_list[2] == "vxlan": + if config_mem_list[4] == "active": + active_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "inactive": + inactive_tmp["vxlan"] = config_mem_list[5] + if config_mem_list[4] == "tcp-session": + tcp_tmp["vxlan"] = "present" + self.end_state["active_timeout"].append(active_tmp) + self.end_state["inactive_timeout"].append(inactive_tmp) + self.end_state["tcp_timeout"].append(tcp_tmp) + + def check_params(self): + """Check all input params""" + + # interval check + if not str(self.timeout_interval).isdigit(): + self.module.fail_json( + msg='Error: Timeout interval should be numerical.') + if self.timeout_type == "active": + if int(self.timeout_interval) < 1 or int(self.timeout_interval) > 60: + self.module.fail_json( + msg="Error: Active interval should between 1 - 60 minutes.") + if self.timeout_type == "inactive": + if int(self.timeout_interval) < 5 or int(self.timeout_interval) > 600: + self.module.fail_json( + msg="Error: Inactive interval should between 5 - 600 seconds.") + if self.timeout_type == "manual": + if not self.manual_slot: + self.module.fail_json( + msg="Error: If use manual timeout mode,slot number is needed.") + if re.match(r'^\d+(\/\d*)?$', self.manual_slot) is None: + self.module.fail_json( + msg='Error: Slot number should be numerical.') + + def get_proposed(self): + """get proposed info""" + + if self.timeout_interval: + self.proposed["timeout_interval"] = self.timeout_interval + if self.timeout_type: + self.proposed["timeout_type"] = self.timeout_type + if self.type: + self.proposed["type"] = self.type + if self.state: + self.proposed["state"] = self.state + if self.manual_slot: + self.proposed["manual_slot"] = self.manual_slot + + def get_existing(self): + """get existing info""" + active_tmp = dict() + inactive_tmp = dict() + tcp_tmp = dict() + + self.get_exist_timer_out_para() + + if self.timeout_type == "active": + for active_tmp in self.existing["active_timeout"]: + if self.state == "present": + if str(active_tmp[self.type]) != self.timeout_interval: + self.active_changed = True + else: + if self.timeout_interval != "30": + if str(active_tmp[self.type]) != "30": + if str(active_tmp[self.type]) != self.timeout_interval: + self.module.fail_json( + msg='Error: The specified active interval do not exist.') + if str(active_tmp[self.type]) != "30": + self.timeout_interval = active_tmp[self.type] + self.active_changed = True + if self.timeout_type == "inactive": + for inactive_tmp in self.existing["inactive_timeout"]: + if self.state == "present": + if str(inactive_tmp[self.type]) != self.timeout_interval: + self.inactive_changed = True + else: + if self.timeout_interval != "30": + if str(inactive_tmp[self.type]) != "30": + if str(inactive_tmp[self.type]) != self.timeout_interval: + self.module.fail_json( + msg='Error: The specified inactive interval do not exist.') + if str(inactive_tmp[self.type]) != "30": + self.timeout_interval = inactive_tmp[self.type] + self.inactive_changed = True + if self.timeout_type == "tcp-session": + for tcp_tmp in self.existing["tcp_timeout"]: + if str(tcp_tmp[self.type]) != self.state: + self.tcp_changed = True + + def operate_time_out(self): + """configure timeout parameters""" + + cmd = "" + if self.timeout_type == "manual": + if self.type == "ip": + self.cli_add_command("quit") + cmd = "reset netstream cache ip slot %s" % self.manual_slot + self.cli_add_command(cmd) + elif self.type == "vxlan": + self.cli_add_command("quit") + cmd = "reset netstream cache vxlan inner-ip slot %s" % self.manual_slot + self.cli_add_command(cmd) + + if not self.active_changed and not self.inactive_changed and not self.tcp_changed: + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + return + + if self.active_changed or self.inactive_changed: + if self.type == "ip": + cmd = "netstream timeout ip %s %s" % (self.timeout_type, self.timeout_interval) + elif self.type == "vxlan": + cmd = "netstream timeout vxlan inner-ip %s %s" % (self.timeout_type, self.timeout_interval) + if self.state == "absent": + self.cli_add_command(cmd, undo=True) + else: + self.cli_add_command(cmd) + if self.timeout_type == "tcp-session" and self.tcp_changed: + if self.type == "ip": + if self.state == "present": + cmd = "netstream timeout ip tcp-session" + else: + cmd = "undo netstream timeout ip tcp-session" + + elif self.type == "vxlan": + if self.state == "present": + cmd = "netstream timeout vxlan inner-ip tcp-session" + else: + cmd = "undo netstream timeout vxlan inner-ip tcp-session" + self.cli_add_command(cmd) + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def get_end_state(self): + """get end state info""" + + self.get_end_timer_out_para() + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_time_out() + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + timeout_interval=dict(required=False, type='str', default='30'), + type=dict(required=False, choices=['ip', 'vxlan']), + state=dict(required=False, choices=['present', 'absent'], default='present'), + timeout_type=dict(required=False, choices=['active', 'inactive', 'tcp-session', 'manual']), + manual_slot=dict(required=False, type='str'), + ) + argument_spec.update(ce_argument_spec) + module = NetStreamAging(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_export.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_export.py new file mode 100644 index 00000000..ac6e0287 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_export.py @@ -0,0 +1,557 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_export +short_description: Manages netstream export on HUAWEI CloudEngine switches. +description: + - Configure NetStream flow statistics exporting and versions for exported packets on HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + type: + description: + - Specifies NetStream feature. + required: true + choices: ['ip', 'vxlan'] + source_ip: + description: + - Specifies source address which can be IPv6 or IPv4 of the exported NetStream packet. + host_ip: + description: + - Specifies destination address which can be IPv6 or IPv4 of the exported NetStream packet. + host_port: + description: + - Specifies the destination UDP port number of the exported packets. + The value is an integer that ranges from 1 to 65535. + host_vpn: + description: + - Specifies the VPN instance of the exported packets carrying flow statistics. + Ensure the VPN instance has been created on the device. + version: + description: + - Sets the version of exported packets. + choices: ['5', '9'] + as_option: + description: + - Specifies the AS number recorded in the statistics as the original or the peer AS number. + choices: ['origin', 'peer'] + bgp_nexthop: + description: + - Configures the statistics to carry BGP next hop information. Currently, only V9 supports the exported + packets carrying BGP next hop information. + choices: ['enable','disable'] + default: 'disable' + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Netstream export module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configures the source address for the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + source_ip: 192.8.2.2 + provider: "{{ cli }}" + + - name: Configures the source IP address for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + source_ip: 192.8.2.3 + provider: "{{ cli }}" + + - name: Configures the destination IP address and destination UDP port number for the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + host_ip: 192.8.2.4 + host_port: 25 + host_vpn: test + provider: "{{ cli }}" + + - name: Configures the destination IP address and destination UDP port number for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + host_ip: 192.8.2.5 + host_port: 26 + host_vpn: test + provider: "{{ cli }}" + + - name: Configures the version number of the exported packets carrying IPv4 flow statistics. + community.network.ce_netstream_export: + type: ip + version: 9 + as_option: origin + bgp_nexthop: enable + provider: "{{ cli }}" + + - name: Configures the version for the exported packets carrying VXLAN flexible flow statistics. + community.network.ce_netstream_export: + type: vxlan + version: 9 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "as_option": "origin", + "bgp_nexthop": "enable", + "host_ip": "192.8.5.6", + "host_port": "26", + "host_vpn": "test", + "source_ip": "192.8.2.5", + "state": "present", + "type": "ip", + "version": "9" + } +existing: + description: k/v pairs of existing attributes on the device + returned: always + type: dict + sample: { + "as_option": null, + "bgp_nexthop": "disable", + "host_ip": null, + "host_port": null, + "host_vpn": null, + "source_ip": null, + "type": "ip", + "version": null + } +end_state: + description: k/v pairs of end attributes on the device + returned: always + type: dict + sample: { + "as_option": "origin", + "bgp_nexthop": "enable", + "host_ip": "192.8.5.6", + "host_port": "26", + "host_vpn": "test", + "source_ip": "192.8.2.5", + "type": "ip", + "version": "9" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "netstream export ip source 192.8.2.5", + "netstream export ip host 192.8.5.6 26 vpn-instance test", + "netstream export ip version 9 origin-as bgp-nexthop" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def is_ipv4_addr(ip_addr): + """check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + + return bool(re.match(ipv4_regex, ip_addr)) + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + test_cfg_tmp = test_cfg + ' *$' + '|' + test_cfg + ' *\n' + obj = re.compile(test_cfg_tmp) + result = re.findall(obj, cmp_cfg) + if not result: + return False + return True + + +class NetstreamExport(object): + """Manage NetStream export""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # NetStream export configuration parameters + self.type = self.module.params['type'] + self.source_ip = self.module.params['source_ip'] + self.host_ip = self.module.params['host_ip'] + self.host_port = self.module.params['host_port'] + self.host_vpn = self.module.params['host_vpn'] + self.version = self.module.params['version'] + self.as_option = self.module.params['as_option'] + self.bgp_netxhop = self.module.params['bgp_nexthop'] + self.state = self.module.params['state'] + + self.commands = list() + self.config = None + self.exist_conf = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_netstream_config(self): + """get current netstream configuration""" + + cmd = "display current-configuration | include ^netstream export" + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + config = str(out).strip() + return config + + def get_existing(self): + """get existing config""" + + self.existing = dict(type=self.type, + source_ip=self.exist_conf['source_ip'], + host_ip=self.exist_conf['host_ip'], + host_port=self.exist_conf['host_port'], + host_vpn=self.exist_conf['host_vpn'], + version=self.exist_conf['version'], + as_option=self.exist_conf['as_option'], + bgp_nexthop=self.exist_conf['bgp_netxhop']) + + def get_proposed(self): + """get proposed config""" + + self.proposed = dict(type=self.type, + source_ip=self.source_ip, + host_ip=self.host_ip, + host_port=self.host_port, + host_vpn=self.host_vpn, + version=self.version, + as_option=self.as_option, + bgp_nexthop=self.bgp_netxhop, + state=self.state) + + def get_end_state(self): + """get end config""" + self.get_config_data() + self.end_state = dict(type=self.type, + source_ip=self.exist_conf['source_ip'], + host_ip=self.exist_conf['host_ip'], + host_port=self.exist_conf['host_port'], + host_vpn=self.exist_conf['host_vpn'], + version=self.exist_conf['version'], + as_option=self.exist_conf['as_option'], + bgp_nexthop=self.exist_conf['bgp_netxhop']) + + def show_result(self): + """show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + if cmd not in self.updates_cmd: + self.updates_cmd.append(cmd) # show updates result + + def config_nets_export_src_addr(self): + """Configures the source address for the exported packets""" + + if is_ipv4_addr(self.source_ip): + if self.type == 'ip': + cmd = "netstream export ip source %s" % self.source_ip + else: + cmd = "netstream export vxlan inner-ip source %s" % self.source_ip + else: + if self.type == 'ip': + cmd = "netstream export ip source ipv6 %s" % self.source_ip + else: + cmd = "netstream export vxlan inner-ip source ipv6 %s" % self.source_ip + + if is_config_exist(self.config, cmd): + self.exist_conf['source_ip'] = self.source_ip + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_host_addr(self): + """Configures the destination IP address and destination UDP port number""" + + if is_ipv4_addr(self.host_ip): + if self.type == 'ip': + cmd = 'netstream export ip host %s %s' % (self.host_ip, self.host_port) + else: + cmd = 'netstream export vxlan inner-ip host %s %s' % (self.host_ip, self.host_port) + else: + if self.type == 'ip': + cmd = 'netstream export ip host ipv6 %s %s' % (self.host_ip, self.host_port) + else: + cmd = 'netstream export vxlan inner-ip host ipv6 %s %s' % (self.host_ip, self.host_port) + + if self.host_vpn: + cmd += " vpn-instance %s" % self.host_vpn + + if is_config_exist(self.config, cmd): + self.exist_conf['host_ip'] = self.host_ip + self.exist_conf['host_port'] = self.host_port + if self.host_vpn: + self.exist_conf['host_vpn'] = self.host_vpn + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_vxlan_ver(self): + """Configures the version for the exported packets carrying VXLAN flexible flow statistics""" + + cmd = 'netstream export vxlan inner-ip version 9' + + if is_config_exist(self.config, cmd): + self.exist_conf['version'] = self.version + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_nets_export_ip_ver(self): + """Configures the version number of the exported packets carrying IPv4 flow statistics""" + + cmd = 'netstream export ip version %s' % self.version + if self.version == '5': + if self.as_option == 'origin': + cmd += ' origin-as' + elif self.as_option == 'peer': + cmd += ' peer-as' + else: + if self.as_option == 'origin': + cmd += ' origin-as' + elif self.as_option == 'peer': + cmd += ' peer-as' + + if self.bgp_netxhop == 'enable': + cmd += ' bgp-nexthop' + + if cmd == 'netstream export ip version 5': + cmd_tmp = "netstream export ip version" + if cmd_tmp in self.config: + if self.state == 'present': + self.cli_add_command(cmd, False) + else: + self.exist_conf['version'] = self.version + return + + if is_config_exist(self.config, cmd): + self.exist_conf['version'] = self.version + self.exist_conf['as_option'] = self.as_option + self.exist_conf['bgp_netxhop'] = self.bgp_netxhop + + if self.state == 'present': + return + else: + undo = True + else: + if self.state == 'absent': + return + else: + undo = False + + self.cli_add_command(cmd, undo) + + def config_netstream_export(self): + """configure netstream export""" + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + if not self.type: + self.module.fail_json(msg='Error: The value of type cannot be empty.') + + if self.host_port: + if not self.host_port.isdigit(): + self.module.fail_json(msg='Error: Host port is invalid.') + if int(self.host_port) < 1 or int(self.host_port) > 65535: + self.module.fail_json(msg='Error: Host port is not in the range from 1 to 65535.') + + if self.host_vpn: + if self.host_vpn == '_public_': + self.module.fail_json( + msg='Error: The host vpn name _public_ is reserved.') + if len(self.host_vpn) < 1 or len(self.host_vpn) > 31: + self.module.fail_json(msg='Error: The host vpn name length is not in the range from 1 to 31.') + + if self.type == 'vxlan' and self.version == '5': + self.module.fail_json(msg="Error: When type is vxlan, version must be 9.") + + if self.type == 'ip' and self.version == '5' and self.bgp_netxhop == 'enable': + self.module.fail_json(msg="Error: When type=ip and version=5, bgp_netxhop is not supported.") + + if (self.host_ip and not self.host_port) or (self.host_port and not self.host_ip): + self.module.fail_json(msg="Error: host_ip and host_port must both exist or not exist.") + + def get_config_data(self): + """get configuration commands and current configuration""" + + self.exist_conf['type'] = self.type + self.exist_conf['source_ip'] = None + self.exist_conf['host_ip'] = None + self.exist_conf['host_port'] = None + self.exist_conf['host_vpn'] = None + self.exist_conf['version'] = None + self.exist_conf['as_option'] = None + self.exist_conf['bgp_netxhop'] = 'disable' + + self.config = self.get_netstream_config() + + if self.type and self.source_ip: + self.config_nets_export_src_addr() + + if self.type and self.host_ip and self.host_port: + self.config_nets_export_host_addr() + + if self.type == 'vxlan' and self.version == '9': + self.config_nets_export_vxlan_ver() + + if self.type == 'ip' and self.version: + self.config_nets_export_ip_ver() + + def work(self): + """execute task""" + + self.check_params() + self.get_proposed() + self.get_config_data() + self.get_existing() + + self.config_netstream_export() + + self.get_end_state() + self.show_result() + + +def main(): + """main function entry""" + + argument_spec = dict( + type=dict(required=True, type='str', choices=['ip', 'vxlan']), + source_ip=dict(required=False, type='str'), + host_ip=dict(required=False, type='str'), + host_port=dict(required=False, type='str'), + host_vpn=dict(required=False, type='str'), + version=dict(required=False, type='str', choices=['5', '9']), + as_option=dict(required=False, type='str', choices=['origin', 'peer']), + bgp_nexthop=dict(required=False, type='str', choices=['enable', 'disable'], default='disable'), + state=dict(choices=['absent', 'present'], default='present', required=False) + ) + argument_spec.update(ce_argument_spec) + netstream_export = NetstreamExport(argument_spec) + netstream_export.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_global.py new file mode 100644 index 00000000..d2d45d90 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_global.py @@ -0,0 +1,942 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_global +short_description: Manages global parameters of NetStream on HUAWEI CloudEngine switches. +description: + - Manages global parameters of NetStream on HUAWEI CloudEngine switches. +author: YangYang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + type: + description: + - Specifies the type of netstream global. + choices: ['ip', 'vxlan'] + default: 'ip' + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present + interface: + description: + - Netstream global interface. + required: true + sampler_interval: + description: + - Specifies the netstream sampler interval, length is 1 - 65535. + sampler_direction: + description: + - Specifies the netstream sampler direction. + choices: ['inbound', 'outbound'] + statistics_direction: + description: + - Specifies the netstream statistic direction. + choices: ['inbound', 'outbound'] + statistics_record: + description: + - Specifies the flexible netstream statistic record, length is 1 - 32. + index_switch: + description: + - Specifies the netstream index-switch. + choices: ['16', '32'] + default: '16' +''' + +EXAMPLES = ''' +- name: Netstream global module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure a netstream sampler at interface 10ge1/0/2, direction is outbound,interval is 30. + community.network.ce_netstream_global: + interface: 10ge1/0/2 + type: ip + sampler_interval: 30 + sampler_direction: outbound + state: present + provider: "{{ cli }}" + - name: Configure a netstream flexible statistic at interface 10ge1/0/2, record is test1, type is ip. + community.network.ce_netstream_global: + type: ip + interface: 10ge1/0/2 + statistics_record: test1 + provider: "{{ cli }}" + - name: Set the vxlan index-switch to 32. + community.network.ce_netstream_global: + type: vxlan + interface: all + index_switch: 32 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"index_switch": "16", + "interface": "10ge1/0/2", + "state": "present", + "statistics_record": "test", + "type": "vxlan"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"flexible_statistic": [ + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "ip" + }, + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "vxlan" + } + ], + "index-switch": [ + { + "index-switch": "16", + "type": "ip" + }, + { + "index-switch": "16", + "type": "vxlan" + } + ], + "ip_record": [ + "test", + "test1" + ], + "sampler": [ + { + "interface": "all", + "sampler_direction": "null", + "sampler_interval": "null" + } + ], + "statistic": [ + { + "interface": "10ge1/0/2", + "statistics_direction": [], + "type": "null" + } + ], + "vxlan_record": [ + "test" + ]} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"flexible_statistic": [ + { + "interface": "10ge1/0/2", + "statistics_record": [], + "type": "ip" + }, + { + "interface": "10ge1/0/2", + "statistics_record": [ + "test" + ], + "type": "vxlan" + } + ], + "index-switch": [ + { + "index-switch": "16", + "type": "ip" + }, + { + "index-switch": "16", + "type": "vxlan" + } + ], + "sampler": [ + { + "interface": "all", + "sampler_direction": "null", + "sampler_interval": "null" + } + ], + "statistic": [ + { + "interface": "10ge1/0/2", + "statistics_direction": [], + "type": "null" + } + ]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface 10ge1/0/2", + "netstream record test vxlan inner-ip"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_connection, rm_config_prefix +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('ALL'): + iftype = 'all' + else: + return None + + return iftype.lower() + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache + """ + time_stamp_regex = re.compile(r'\s*\d{4}-\d{1,2}-\d{1,2}\s+\d{2}\:\d{2}\:\d{2}\.\d+\s*') + flags = [] if flags is None else flags + if isinstance(flags, str): + flags = [flags] + elif not isinstance(flags, list): + flags = [] + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + conn = get_connection(module) + rc, out, err = conn.exec_command(cmd) + if rc != 0: + module.fail_json(msg=err) + cfg = str(out).strip() + # remove default configuration prefix '~' + for flag in flags: + if "include-default" in flag: + cfg = rm_config_prefix(cfg) + break + lines = cfg.split('\n') + lines = [l for l in lines if time_stamp_regex.match(l) is None] + if cfg.startswith('display'): + if len(lines) > 1: + lines.pop(0) + else: + return '' + return '\n'.join(lines) + + +class NetStreamGlobal(object): + """ + Manages netstream global parameters. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.type = self.module.params['type'] + self.interface = self.module.params['interface'] + self.sampler_interval = self.module.params['sampler_interval'] + self.sampler_direction = self.module.params['sampler_direction'] + self.statistics_direction = self.module.params['statistics_direction'] + self.statistics_record = self.module.params['statistics_record'] + self.index_switch = self.module.params['index_switch'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + # local parameters + self.existing["sampler"] = list() + self.existing["statistic"] = list() + self.existing["flexible_statistic"] = list() + self.existing["index-switch"] = list() + self.existing["ip_record"] = list() + self.existing["vxlan_record"] = list() + self.end_state["sampler"] = list() + self.end_state["statistic"] = list() + self.end_state["flexible_statistic"] = list() + self.end_state["index-switch"] = list() + self.sampler_changed = False + self.statistic_changed = False + self.flexible_changed = False + self.index_switch_changed = False + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) + + def get_exist_sampler_interval(self): + """get exist netstream sampler interval""" + + sampler_tmp = dict() + sampler_tmp1 = dict() + flags = list() + exp = " | ignore-case include ^netstream sampler random-packets" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp["sampler_interval"] = "null" + sampler_tmp["sampler_direction"] = "null" + sampler_tmp["interface"] = "null" + else: + config_list = config.split(' ') + config_num = len(config_list) + sampler_tmp["sampler_direction"] = config_list[config_num - 1] + sampler_tmp["sampler_interval"] = config_list[config_num - 2] + sampler_tmp["interface"] = "all" + self.existing["sampler"].append(sampler_tmp) + if self.interface != "all": + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream sampler random-packets" % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp1["sampler_interval"] = "null" + sampler_tmp1["sampler_direction"] = "null" + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + sampler_tmp1 = dict() + config_mem_list = config_mem.split(' ') + config_num = len(config_mem_list) + if config_num > 1: + sampler_tmp1["sampler_direction"] = config_mem_list[ + config_num - 1] + sampler_tmp1["sampler_interval"] = config_mem_list[ + config_num - 2] + sampler_tmp1["interface"] = self.interface + self.existing["sampler"].append(sampler_tmp1) + + def get_exist_statistic_record(self): + """get exist netstream statistic record parameter""" + + if self.statistics_record and self.statistics_direction: + self.module.fail_json( + msg='Error: The statistic direction and record can not exist at the same time.') + statistic_tmp = dict() + statistic_tmp1 = dict() + statistic_tmp["statistics_record"] = list() + statistic_tmp["interface"] = self.interface + statistic_tmp1["statistics_record"] = list() + statistic_tmp1["interface"] = self.interface + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream record"\ + % (self.interface) + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp["type"] = "ip" + self.existing["flexible_statistic"].append(statistic_tmp) + statistic_tmp1["type"] = "vxlan" + self.existing["flexible_statistic"].append(statistic_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + statistic_tmp["statistics_record"] = list() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "ip": + statistic_tmp["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp["type"] = "ip" + self.existing["flexible_statistic"].append(statistic_tmp) + for config_mem in config_list: + statistic_tmp1["statistics_record"] = list() + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "vxlan": + statistic_tmp1["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp1["type"] = "vxlan" + self.existing["flexible_statistic"].append(statistic_tmp1) + + def get_exist_interface_statistic(self): + """get exist netstream interface statistic parameter""" + + statistic_tmp1 = dict() + statistic_tmp1["statistics_direction"] = list() + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream inbound|outbound"\ + % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp1["type"] = "null" + else: + statistic_tmp1["type"] = "ip" + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 1: + statistic_tmp1["statistics_direction"].append( + str(config_mem_list[1])) + statistic_tmp1["interface"] = self.interface + self.existing["statistic"].append(statistic_tmp1) + + def get_exist_index_switch(self): + """get exist netstream index-switch""" + + index_switch_tmp = dict() + index_switch_tmp1 = dict() + index_switch_tmp["index-switch"] = "16" + index_switch_tmp["type"] = "ip" + index_switch_tmp1["index-switch"] = "16" + index_switch_tmp1["type"] = "vxlan" + flags = list() + exp = " | ignore-case include index-switch" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + self.existing["index-switch"].append(index_switch_tmp) + self.existing["index-switch"].append(index_switch_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "ip": + index_switch_tmp["index-switch"] = "32" + index_switch_tmp["type"] = "ip" + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "vxlan": + index_switch_tmp1["index-switch"] = "32" + index_switch_tmp1["type"] = "vxlan" + self.existing["index-switch"].append(index_switch_tmp) + self.existing["index-switch"].append(index_switch_tmp1) + + def get_exist_record(self): + """get exist netstream record""" + + flags = list() + exp = " | ignore-case include netstream record" + flags.append(exp) + config = get_config(self.module, flags) + if config: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and config_mem_list[3] == "ip": + self.existing["ip_record"].append(config_mem_list[2]) + if len(config_mem_list) > 3 and config_mem_list[3] == "vxlan": + self.existing["vxlan_record"].append(config_mem_list[2]) + + def get_end_sampler_interval(self): + """get end netstream sampler interval""" + + sampler_tmp = dict() + sampler_tmp1 = dict() + flags = list() + exp = " | ignore-case include ^netstream sampler random-packets" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp["sampler_interval"] = "null" + sampler_tmp["sampler_direction"] = "null" + else: + config_list = config.split(' ') + config_num = len(config_list) + if config_num > 1: + sampler_tmp["sampler_direction"] = config_list[config_num - 1] + sampler_tmp["sampler_interval"] = config_list[config_num - 2] + sampler_tmp["interface"] = "all" + self.end_state["sampler"].append(sampler_tmp) + if self.interface != "all": + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream sampler random-packets" % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + sampler_tmp1["sampler_interval"] = "null" + sampler_tmp1["sampler_direction"] = "null" + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + sampler_tmp1 = dict() + config_mem_list = config_mem.split(' ') + config_num = len(config_mem_list) + if config_num > 1: + sampler_tmp1["sampler_direction"] = config_mem_list[ + config_num - 1] + sampler_tmp1["sampler_interval"] = config_mem_list[ + config_num - 2] + sampler_tmp1["interface"] = self.interface + self.end_state["sampler"].append(sampler_tmp1) + + def get_end_statistic_record(self): + """get end netstream statistic record parameter""" + + if self.statistics_record and self.statistics_direction: + self.module.fail_json( + msg='Error: The statistic direction and record can not exist at the same time.') + statistic_tmp = dict() + statistic_tmp1 = dict() + statistic_tmp["statistics_record"] = list() + statistic_tmp["interface"] = self.interface + statistic_tmp1["statistics_record"] = list() + statistic_tmp1["interface"] = self.interface + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream record"\ + % (self.interface) + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp["type"] = "ip" + self.end_state["flexible_statistic"].append(statistic_tmp) + statistic_tmp1["type"] = "vxlan" + self.end_state["flexible_statistic"].append(statistic_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + statistic_tmp["statistics_record"] = list() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "ip": + statistic_tmp["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp["type"] = "ip" + self.end_state["flexible_statistic"].append(statistic_tmp) + for config_mem in config_list: + statistic_tmp1["statistics_record"] = list() + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 3 and str(config_mem_list[3]) == "vxlan": + statistic_tmp1["statistics_record"].append( + str(config_mem_list[2])) + statistic_tmp1["type"] = "vxlan" + self.end_state["flexible_statistic"].append(statistic_tmp1) + + def get_end_interface_statistic(self): + """get end netstream interface statistic parameters""" + + statistic_tmp1 = dict() + statistic_tmp1["statistics_direction"] = list() + flags = list() + exp = r" | ignore-case section include ^#\s+interface %s" \ + r" | include netstream inbound|outbound"\ + % self.interface + flags.append(exp) + config = get_config(self.module, flags) + if not config: + statistic_tmp1["type"] = "null" + else: + statistic_tmp1["type"] = "ip" + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem = config_mem.lstrip() + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 1: + statistic_tmp1["statistics_direction"].append( + str(config_mem_list[1])) + statistic_tmp1["interface"] = self.interface + self.end_state["statistic"].append(statistic_tmp1) + + def get_end_index_switch(self): + """get end netstream index switch""" + + index_switch_tmp = dict() + index_switch_tmp1 = dict() + index_switch_tmp["index-switch"] = "16" + index_switch_tmp["type"] = "ip" + index_switch_tmp1["index-switch"] = "16" + index_switch_tmp1["type"] = "vxlan" + flags = list() + exp = " | ignore-case include index-switch" + flags.append(exp) + config = get_config(self.module, flags) + if not config: + self.end_state["index-switch"].append(index_switch_tmp) + self.end_state["index-switch"].append(index_switch_tmp1) + else: + config = config.lstrip() + config_list = config.split('\n') + for config_mem in config_list: + config_mem_list = config_mem.split(' ') + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "ip": + index_switch_tmp["index-switch"] = "32" + index_switch_tmp["type"] = "ip" + if len(config_mem_list) > 2 and str(config_mem_list[2]) == "vxlan": + index_switch_tmp1["index-switch"] = "32" + index_switch_tmp1["type"] = "vxlan" + self.end_state["index-switch"].append(index_switch_tmp) + self.end_state["index-switch"].append(index_switch_tmp1) + + def check_params(self): + """check all input params""" + + # netstream parameters check + if not get_interface_type(self.interface): + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + if self.sampler_interval: + if not str(self.sampler_interval).isdigit(): + self.module.fail_json( + msg='Error: Active interval should be numerical.') + if int(self.sampler_interval) < 1 or int(self.sampler_interval) > 65535: + self.module.fail_json( + msg="Error: Sampler interval should between 1 - 65535.") + if self.statistics_record: + if len(self.statistics_record) < 1 or len(self.statistics_record) > 32: + self.module.fail_json( + msg="Error: Statistic record length should between 1 - 32.") + if self.interface == "all": + if self.statistics_record or self.statistics_direction: + self.module.fail_json( + msg="Error: Statistic function should be used at interface.") + if self.statistics_direction: + if self.type == "vxlan": + self.module.fail_json( + msg="Error: Vxlan do not support inbound or outbound statistic.") + if (self.sampler_interval and not self.sampler_direction) \ + or (self.sampler_direction and not self.sampler_interval): + self.module.fail_json( + msg="Error: Sampler interval and direction must be set at the same time.") + + if self.statistics_record and not self.type: + self.module.fail_json( + msg="Error: Statistic type and record must be set at the same time.") + + self.get_exist_record() + if self.statistics_record: + if self.type == "ip": + if self.statistics_record not in self.existing["ip_record"]: + self.module.fail_json( + msg="Error: The statistic record is not exist.") + if self.type == "vxlan": + if self.statistics_record not in self.existing["vxlan_record"]: + self.module.fail_json( + msg="Error: The statistic record is not exist.") + + def get_proposed(self): + """get proposed info""" + + if self.type: + self.proposed["type"] = self.type + if self.interface: + self.proposed["interface"] = self.interface + if self.sampler_interval: + self.proposed["sampler_interval"] = self.sampler_interval + if self.sampler_direction: + self.proposed["sampler_direction"] = self.sampler_direction + if self.statistics_direction: + self.proposed["statistics_direction"] = self.statistics_direction + if self.statistics_record: + self.proposed["statistics_record"] = self.statistics_record + if self.index_switch: + self.proposed["index_switch"] = self.index_switch + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + sampler_tmp = dict() + statistic_tmp = dict() + statistic_tmp1 = dict() + index_tmp = dict() + temp = False + + self.get_exist_sampler_interval() + self.get_exist_interface_statistic() + self.get_exist_statistic_record() + self.get_exist_index_switch() + + if self.state == "present": + for sampler_tmp in self.existing["sampler"]: + if self.interface == str(sampler_tmp["interface"]): + temp = True + if (self.sampler_interval and str(sampler_tmp["sampler_interval"]) != self.sampler_interval) \ + or (self.sampler_direction and + str(sampler_tmp["sampler_direction"]) != self.sampler_direction): + self.sampler_changed = True + if not temp: + if self.sampler_direction or self.sampler_interval: + self.sampler_changed = True + for statistic_tmp in self.existing["statistic"]: + if str(statistic_tmp["interface"]) == self.interface and self.interface != "all": + if self.type == "vxlan": + if statistic_tmp["statistics_direction"] \ + and 'outbound' in statistic_tmp["statistics_direction"]: + self.module.fail_json( + msg='Error: The NetStream record vxlan ' + 'cannot be configured because the port has been configured NetStream outbound ip.') + if statistic_tmp["statistics_direction"] and self.statistics_direction: + if self.statistics_direction not in statistic_tmp["statistics_direction"]: + self.statistic_changed = True + else: + if self.statistics_direction: + self.statistic_changed = True + for statistic_tmp1 in self.existing["flexible_statistic"]: + if self.interface != "all" \ + and self.type == str(statistic_tmp1["type"]) \ + and self.interface == str(statistic_tmp1["interface"]): + if statistic_tmp1["statistics_record"] and self.statistics_record: + if self.statistics_record not in statistic_tmp1["statistics_record"]: + self.flexible_changed = True + else: + if self.statistics_record: + self.flexible_changed = True + for index_tmp in self.existing["index-switch"]: + if self.type == str(index_tmp["type"]): + if self.index_switch != str(index_tmp["index-switch"]): + self.index_switch_changed = True + else: + for sampler_tmp in self.existing["sampler"]: + if self.interface == str(sampler_tmp["interface"]): + if (self.sampler_interval and str(sampler_tmp["sampler_interval"]) == self.sampler_interval) \ + and (self.sampler_direction and str(sampler_tmp["sampler_direction"]) == self.sampler_direction): + self.sampler_changed = True + for statistic_tmp in self.existing["statistic"]: + if str(statistic_tmp["interface"]) == self.interface and self.interface != "all": + if len(statistic_tmp["statistics_direction"]) and self.statistics_direction: + if self.statistics_direction in statistic_tmp["statistics_direction"]: + self.statistic_changed = True + for statistic_tmp1 in self.existing["flexible_statistic"]: + if self.interface != "all" \ + and self.type == str(statistic_tmp1["type"]) \ + and self.interface == str(statistic_tmp1["interface"]): + if len(statistic_tmp1["statistics_record"]) and self.statistics_record: + if self.statistics_record in statistic_tmp1["statistics_record"]: + self.flexible_changed = True + for index_tmp in self.existing["index-switch"]: + if self.type == str(index_tmp["type"]): + if self.index_switch == str(index_tmp["index-switch"]): + if self.index_switch != "16": + self.index_switch_changed = True + + def operate_ns_gloabl(self): + """configure netstream global parameters""" + + cmd = "" + if not self.sampler_changed and not self.statistic_changed \ + and not self.flexible_changed and not self.index_switch_changed: + self.changed = False + return + + if self.sampler_changed is True: + if self.type == "vxlan": + self.module.fail_json( + msg="Error: Netstream do not support vxlan sampler.") + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + cmd = "netstream sampler random-packets %s %s" % ( + self.sampler_interval, self.sampler_direction) + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.statistic_changed is True: + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + cmd = "netstream %s ip" % self.statistics_direction + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.flexible_changed is True: + if self.interface != "all": + cmd = "interface %s" % self.interface + self.cli_add_command(cmd) + if self.state == "present": + for statistic_tmp in self.existing["flexible_statistic"]: + tmp_list = statistic_tmp["statistics_record"] + if self.type == statistic_tmp["type"]: + if self.type == "ip": + if len(tmp_list) > 0: + cmd = "netstream record %s ip" % tmp_list[0] + self.cli_add_command(cmd, undo=True) + cmd = "netstream record %s ip" % self.statistics_record + self.cli_add_command(cmd) + if self.type == "vxlan": + if len(tmp_list) > 0: + cmd = "netstream record %s vxlan inner-ip" % tmp_list[ + 0] + self.cli_add_command(cmd, undo=True) + cmd = "netstream record %s vxlan inner-ip" % self.statistics_record + self.cli_add_command(cmd) + else: + if self.type == "ip": + cmd = "netstream record %s ip" % self.statistics_record + self.cli_add_command(cmd, undo=True) + if self.type == "vxlan": + cmd = "netstream record %s vxlan inner-ip" % self.statistics_record + self.cli_add_command(cmd, undo=True) + if self.interface != "all": + cmd = "quit" + self.cli_add_command(cmd) + if self.index_switch_changed is True: + if self.interface != "all": + self.module.fail_json( + msg="Error: Index-switch function should be used globally.") + if self.type == "ip": + cmd = "netstream export ip index-switch %s" % self.index_switch + else: + cmd = "netstream export vxlan inner-ip index-switch %s" % self.index_switch + if self.state == "present": + self.cli_add_command(cmd) + else: + self.cli_add_command(cmd, undo=True) + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + def get_end_state(self): + """get end state info""" + + self.get_end_sampler_interval() + self.get_end_interface_statistic() + self.get_end_statistic_record() + self.get_end_index_switch() + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_ns_gloabl() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + type=dict(required=False, choices=['ip', 'vxlan'], default='ip'), + interface=dict(required=True, type='str'), + sampler_interval=dict(required=False, type='str'), + sampler_direction=dict(required=False, choices=['inbound', 'outbound']), + statistics_direction=dict(required=False, choices=['inbound', 'outbound']), + statistics_record=dict(required=False, type='str'), + index_switch=dict(required=False, choices=['16', '32'], default='16'), + state=dict(required=False, choices=['present', 'absent'], default='present'), + ) + argument_spec.update(ce_argument_spec) + module = NetStreamGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_template.py new file mode 100644 index 00000000..38d9fede --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_netstream_template.py @@ -0,0 +1,494 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_netstream_template +short_description: Manages NetStream template configuration on HUAWEI CloudEngine switches. +description: + - Manages NetStream template configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + type: + description: + - Configure the type of netstream record. + required: true + choices: ['ip', 'vxlan'] + record_name: + description: + - Configure the name of netstream record. + The value is a string of 1 to 32 case-insensitive characters. + match: + description: + - Configure flexible flow statistics template keywords. + choices: ['destination-address', 'destination-port', 'tos', 'protocol', 'source-address', 'source-port'] + collect_counter: + description: + - Configure the number of packets and bytes that are included in the flexible flow statistics sent to NSC. + choices: ['bytes', 'packets'] + collect_interface: + description: + - Configure the input or output interface that are included in the flexible flow statistics sent to NSC. + choices: ['input', 'output'] + description: + description: + - Configure the description of netstream record. + The value is a string of 1 to 80 case-insensitive characters. +''' + +EXAMPLES = ''' +- name: Netstream template module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config ipv4 netstream record + community.network.ce_netstream_template: + state: present + type: ip + record_name: test + provider: "{{ cli }}" + - name: Undo ipv4 netstream record + community.network.ce_netstream_template: + state: absent + type: ip + record_name: test + provider: "{{ cli }}" + - name: Config ipv4 netstream record collect_counter + community.network.ce_netstream_template: + state: present + type: ip + record_name: test + collect_counter: bytes + provider: "{{ cli }}" + - name: Undo ipv4 netstream record collect_counter + community.network.ce_netstream_template: + state: absent + type: ip + record_name: test + collect_counter: bytes + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"record_name": "test", + "type": "ip", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"record_name": "test", + "type": "ip"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["netstream record test ip"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_connection, rm_config_prefix +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + if isinstance(flags, str): + flags = [flags] + elif not isinstance(flags, list): + flags = [] + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + conn = get_connection(module) + rc, out, err = conn.exec_command(cmd) + if rc != 0: + module.fail_json(msg=err) + cfg = str(out).strip() + # remove default configuration prefix '~' + for flag in flags: + if "include-default" in flag: + cfg = rm_config_prefix(cfg) + break + if cfg.startswith('display'): + lines = cfg.split('\n') + if len(lines) > 1: + return '\n'.join(lines[1:]) + else: + return '' + return cfg + + +class NetstreamTemplate(object): + """ Manages netstream template configuration """ + + def __init__(self, **kwargs): + """ Netstream template module init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # netstream config + self.netstream_cfg = None + + # module args + self.state = self.module.params['state'] or None + self.type = self.module.params['type'] or None + self.record_name = self.module.params['record_name'] or None + self.match = self.module.params['match'] or None + self.collect_counter = self.module.params['collect_counter'] or None + self.collect_interface = self.module.params['collect_interface'] or None + self.description = self.module.params['description'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def cli_load_config(self, commands): + """ Cli load configuration """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_netstream_config(self): + """ Cli get netstream configuration """ + + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + flags = list() + regular = "| section include %s" % cmd + flags.append(regular) + self.netstream_cfg = get_config(self.module, flags) + + def check_args(self): + """ Check module args """ + + if not self.type or not self.record_name: + self.module.fail_json( + msg='Error: Please input type and record_name.') + + if self.record_name: + if len(self.record_name) < 1 or len(self.record_name) > 32: + self.module.fail_json( + msg='Error: The len of record_name is out of [1 - 32].') + + if self.description: + if len(self.description) < 1 or len(self.description) > 80: + self.module.fail_json( + msg='Error: The len of description is out of [1 - 80].') + + def get_proposed(self): + """ Get module proposed """ + + self.proposed["state"] = self.state + + if self.type: + self.proposed["type"] = self.type + if self.record_name: + self.proposed["record_name"] = self.record_name + if self.match: + self.proposed["match"] = self.match + if self.collect_counter: + self.proposed["collect_counter"] = self.collect_counter + if self.collect_interface: + self.proposed["collect_interface"] = self.collect_interface + if self.description: + self.proposed["description"] = self.description + + def get_existing(self): + """ Get existing configuration """ + + self.cli_get_netstream_config() + + if self.netstream_cfg is not None and "netstream record" in self.netstream_cfg: + self.existing["type"] = self.type + self.existing["record_name"] = self.record_name + + if self.description: + tmp_value = re.findall(r'description (.*)', self.netstream_cfg) + if tmp_value is not None and len(tmp_value) > 0: + self.existing["description"] = tmp_value[0] + + if self.match: + if self.type == "ip": + tmp_value = re.findall(r'match ip (.*)', self.netstream_cfg) + else: + tmp_value = re.findall(r'match inner-ip (.*)', self.netstream_cfg) + + if tmp_value: + self.existing["match"] = tmp_value + + if self.collect_counter: + tmp_value = re.findall(r'collect counter (.*)', self.netstream_cfg) + if tmp_value: + self.existing["collect_counter"] = tmp_value + + if self.collect_interface: + tmp_value = re.findall(r'collect interface (.*)', self.netstream_cfg) + if tmp_value: + self.existing["collect_interface"] = tmp_value + + def get_end_state(self): + """ Get end state """ + + self.cli_get_netstream_config() + + if self.netstream_cfg is not None and "netstream record" in self.netstream_cfg: + self.end_state["type"] = self.type + self.end_state["record_name"] = self.record_name + + if self.description: + tmp_value = re.findall(r'description (.*)', self.netstream_cfg) + if tmp_value is not None and len(tmp_value) > 0: + self.end_state["description"] = tmp_value[0] + + if self.match: + if self.type == "ip": + tmp_value = re.findall(r'match ip (.*)', self.netstream_cfg) + else: + tmp_value = re.findall(r'match inner-ip (.*)', self.netstream_cfg) + + if tmp_value: + self.end_state["match"] = tmp_value + + if self.collect_counter: + tmp_value = re.findall(r'collect counter (.*)', self.netstream_cfg) + if tmp_value: + self.end_state["collect_counter"] = tmp_value + + if self.collect_interface: + tmp_value = re.findall(r'collect interface (.*)', self.netstream_cfg) + if tmp_value: + self.end_state["collect_interface"] = tmp_value + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def present_netstream(self): + """ Present netstream configuration """ + + cmds = list() + need_create_record = False + + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + cmds.append(cmd) + + if self.existing.get('record_name') != self.record_name: + self.updates_cmd.append(cmd) + need_create_record = True + + if self.description: + cmd = "description %s" % self.description.strip() + if need_create_record or not self.netstream_cfg or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.match: + if self.type == "ip": + cmd = "match ip %s" % self.match + cfg = "match ip" + else: + cmd = "match inner-ip %s" % self.match + cfg = "match inner-ip" + + if need_create_record or cfg not in self.netstream_cfg or self.match != self.existing["match"][0]: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_counter: + cmd = "collect counter %s" % self.collect_counter + if need_create_record or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_interface: + cmd = "collect interface %s" % self.collect_interface + if need_create_record or cmd not in self.netstream_cfg: + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def absent_netstream(self): + """ Absent netstream configuration """ + + cmds = list() + absent_netstream_attr = False + + if not self.netstream_cfg: + return + + if self.description or self.match or self.collect_counter or self.collect_interface: + absent_netstream_attr = True + + if absent_netstream_attr: + if self.type == "ip": + cmd = "netstream record %s ip" % self.record_name + else: + cmd = "netstream record %s vxlan inner-ip" % self.record_name + + cmds.append(cmd) + + if self.description: + cfg = "description %s" % self.description + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo description %s" % self.description + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.match: + if self.type == "ip": + cfg = "match ip %s" % self.match + else: + cfg = "match inner-ip %s" % self.match + if self.netstream_cfg and cfg in self.netstream_cfg: + if self.type == "ip": + cmd = "undo match ip %s" % self.match + else: + cmd = "undo match inner-ip %s" % self.match + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_counter: + cfg = "collect counter %s" % self.collect_counter + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo collect counter %s" % self.collect_counter + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.collect_interface: + cfg = "collect interface %s" % self.collect_interface + if self.netstream_cfg and cfg in self.netstream_cfg: + cmd = "undo collect interface %s" % self.collect_interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if len(cmds) > 1: + self.cli_load_config(cmds) + self.changed = True + + else: + if self.type == "ip": + cmd = "undo netstream record %s ip" % self.record_name + else: + cmd = "undo netstream record %s vxlan inner-ip" % self.record_name + + cmds.append(cmd) + self.updates_cmd.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + self.present_netstream() + else: + self.absent_netstream() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + type=dict(choices=['ip', 'vxlan'], required=True), + record_name=dict(type='str'), + match=dict(choices=['destination-address', 'destination-port', + 'tos', 'protocol', 'source-address', 'source-port']), + collect_counter=dict(choices=['bytes', 'packets']), + collect_interface=dict(choices=['input', 'output']), + description=dict(type='str') + ) + argument_spec.update(ce_argument_spec) + module = NetstreamTemplate(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp.py new file mode 100644 index 00000000..3603658c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp.py @@ -0,0 +1,615 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ntp +short_description: Manages core NTP configuration on HUAWEI CloudEngine switches. +description: + - Manages core NTP configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + server: + description: + - Network address of NTP server. + peer: + description: + - Network address of NTP peer. + key_id: + description: + - Authentication key identifier to use with given NTP server or peer. + is_preferred: + description: + - Makes given NTP server or peer the preferred NTP server or peer for the device. + choices: ['enable', 'disable'] + vpn_name: + description: + - Makes the device communicate with the given + NTP server or peer over a specific vpn. + default: '_public_' + source_int: + description: + - Local source interface from which NTP messages are sent. + Must be fully qualified interface name, i.e. C(40GE1/0/22), C(vlanif10). + Interface types, such as C(10GE), C(40GE), C(100GE), C(Eth-Trunk), C(LoopBack), + C(MEth), C(NULL), C(Tunnel), C(Vlanif). + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: NTP test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Set NTP Server with parameters" + community.network.ce_ntp: + server: 192.8.2.6 + vpn_name: js + source_int: vlanif4001 + is_preferred: enable + key_id: 32 + provider: "{{ cli }}" + + - name: "Set NTP Peer with parameters" + community.network.ce_ntp: + peer: 192.8.2.6 + vpn_name: js + source_int: vlanif4001 + is_preferred: enable + key_id: 32 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "48", + "is_preferred": "enable", "vpn_name":"js", + "source_int": "vlanif4002", "state":"present"} +existing: + description: k/v pairs of existing ntp server/peer + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "32", + "is_preferred": "disable", "vpn_name":"js", + "source_int": "vlanif4002"} +end_state: + description: k/v pairs of ntp info after module execution + returned: always + type: dict + sample: {"server": "2.2.2.2", "key_id": "48", + "is_preferred": "enable", "vpn_name":"js", + "source_int": "vlanif4002"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["ntp server 2.2.2.2 authentication-keyid 48 source-interface vlanif4002 vpn-instance js preferred"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config + +CE_NC_GET_NTP_CONFIG = """ + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_NTP_CONFIG = """ + + + + + %s + %s + %s + %s + %s + %s + %s + %s + 0-0 + + + + +""" + +CE_NC_DELETE_NTP_CONFIG = """ + + + + + %s + %s + %s + %s + %s + 0-0 + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Ntp(object): + """Ntp class""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.mutually_exclusive = [('server', 'peer')] + self.init_module() + + # ntp configuration info + self.server = self.module.params['server'] or None + self.peer = self.module.params['peer'] or None + self.key_id = self.module.params['key_id'] + self.is_preferred = self.module.params['is_preferred'] + self.vpn_name = self.module.params['vpn_name'] + self.interface = self.module.params['source_int'] or "" + self.state = self.module.params['state'] + self.ntp_conf = dict() + self.conf_exsit = False + self.ip_ver = 'IPv4' + + if self.server: + self.peer_type = 'Server' + self.address = self.server + elif self.peer: + self.peer_type = 'Peer' + self.address = self.peer + else: + self.peer_type = None + self.address = None + + self.check_params() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + self.init_data() + + def init_data(self): + """Init data""" + + if self.interface is not None: + self.interface = self.interface.lower() + + if not self.key_id: + self.key_id = "" + + if not self.is_preferred: + self.is_preferred = 'disable' + + def init_module(self): + """Init module""" + + required_one_of = [("server", "peer")] + self.module = AnsibleModule( + argument_spec=self.spec, + supports_check_mode=True, + required_one_of=required_one_of, + mutually_exclusive=self.mutually_exclusive + ) + + def check_ipaddr_validate(self): + """Check ipaddress validate""" + + rule1 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.' + rule2 = r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])' + ipv4_regex = '%s%s%s%s%s%s' % ('^', rule1, rule1, rule1, rule2, '$') + ipv6_regex = '^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$' + + flag = False + if bool(re.match(ipv4_regex, self.address)): + flag = True + self.ip_ver = "IPv4" + if not self.ntp_ucast_ipv4_validate(): + flag = False + elif bool(re.match(ipv6_regex, self.address)): + flag = True + self.ip_ver = "IPv6" + else: + flag = True + self.ip_ver = "IPv6" + + if not flag: + if self.peer_type == "Server": + self.module.fail_json(msg='Error: Illegal server ip-address.') + else: + self.module.fail_json(msg='Error: Illegal peer ip-address.') + + def ntp_ucast_ipv4_validate(self): + """Check ntp ucast ipv4 address""" + + addr_list = re.findall(r'(.*)\.(.*)\.(.*)\.(.*)', self.address) + if not addr_list: + self.module.fail_json(msg='Error: Match ip-address fail.') + + value = ((int(addr_list[0][0])) * 0x1000000) + (int(addr_list[0][1]) * 0x10000) + \ + (int(addr_list[0][2]) * 0x100) + (int(addr_list[0][3])) + if (value & (0xff000000) == 0x7f000000) or (value & (0xF0000000) == 0xF0000000) \ + or (value & (0xF0000000) == 0xE0000000) or (value == 0): + return False + return True + + def check_params(self): + """Check all input params""" + + # check interface type + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + if self.vpn_name: + if (len(self.vpn_name) < 1) or (len(self.vpn_name) > 31): + self.module.fail_json( + msg='Error: VPN name length is between 1 and 31.') + + if self.address: + self.check_ipaddr_validate() + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_ntp(self, *args): + """Configure ntp parameters""" + + if self.state == 'present': + if self.ip_ver == 'IPv4': + xml_str = CE_NC_MERGE_NTP_CONFIG % ( + args[0], args[1], '::', args[2], args[3], args[4], args[5], args[6]) + elif self.ip_ver == 'IPv6': + xml_str = CE_NC_MERGE_NTP_CONFIG % ( + args[0], '0.0.0.0', args[1], args[2], args[3], args[4], args[5], args[6]) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "NTP_CORE_CONFIG") + else: + if self.ip_ver == 'IPv4': + xml_str = CE_NC_DELETE_NTP_CONFIG % ( + args[0], args[1], '::', args[2], args[3]) + elif self.ip_ver == 'IPv6': + xml_str = CE_NC_DELETE_NTP_CONFIG % ( + args[0], '0.0.0.0', args[1], args[2], args[3]) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "UNDO_NTP_CORE_CONFIG") + + def config_ntp(self): + """Config ntp""" + + if self.state == "present": + if self.address and not self.conf_exsit: + if self.is_preferred == 'enable': + is_preferred = 'true' + else: + is_preferred = 'false' + self.set_ntp(self.ip_ver, self.address, self.peer_type, + self.vpn_name, self.key_id, is_preferred, self.interface) + self.changed = True + else: + if self.address: + self.set_ntp(self.ip_ver, self.address, + self.peer_type, self.vpn_name, '', '', '') + self.changed = True + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def get_ntp_exist_config(self): + """Get ntp existed configure""" + + ntp_config = list() + conf_str = CE_NC_GET_NTP_CONFIG + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return ntp_config + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get all ntp config info + root = ElementTree.fromstring(xml_str) + ntpsite = root.findall("ntp/ntpUCastCfgs/ntpUCastCfg") + for nexthop in ntpsite: + ntp_dict = dict() + for ele in nexthop: + if ele.tag in ["addrFamily", "vpnName", "ifName", "ipv4Addr", + "ipv6Addr", "type", "isPreferred", "keyId"]: + ntp_dict[ele.tag] = ele.text + + ip_addr = ntp_dict['ipv6Addr'] + if ntp_dict['addrFamily'] == "IPv4": + ip_addr = ntp_dict['ipv4Addr'] + if ntp_dict['ifName'] is None: + ntp_dict['ifName'] = "" + if ntp_dict['isPreferred'] == 'true': + is_preferred = 'enable' + else: + is_preferred = 'disable' + + if self.state == "present": + key_id = ntp_dict['keyId'] or "" + cur_ntp_cfg = dict(vpn_name=ntp_dict['vpnName'], source_int=ntp_dict['ifName'].lower(), address=ip_addr, + peer_type=ntp_dict['type'], prefer=is_preferred, key_id=key_id) + exp_ntp_cfg = dict(vpn_name=self.vpn_name, source_int=self.interface.lower(), address=self.address, + peer_type=self.peer_type, prefer=self.is_preferred, key_id=self.key_id) + if cur_ntp_cfg == exp_ntp_cfg: + self.conf_exsit = True + + vpn_name = ntp_dict['vpnName'] + if ntp_dict['vpnName'] == "_public_": + vpn_name = None + + if_name = ntp_dict['ifName'] + if if_name == "": + if_name = None + if self.peer_type == 'Server': + ntp_config.append(dict(vpn_name=vpn_name, + source_int=if_name, server=ip_addr, + is_preferred=is_preferred, key_id=ntp_dict['keyId'])) + else: + ntp_config.append(dict(vpn_name=vpn_name, + source_int=if_name, peer=ip_addr, + is_preferred=is_preferred, key_id=ntp_dict['keyId'])) + + return ntp_config + + def get_existing(self): + """Get existing info""" + + if self.address: + self.existing = self.get_ntp_exist_config() + + def get_proposed(self): + """Get proposed info""" + + if self.address: + vpn_name = self.vpn_name + if vpn_name == "_public_": + vpn_name = None + + if_name = self.interface + if if_name == "": + if_name = None + + key_id = self.key_id + if key_id == "": + key_id = None + if self.peer_type == 'Server': + self.proposed = dict(state=self.state, vpn_name=vpn_name, + source_int=if_name, server=self.address, + is_preferred=self.is_preferred, key_id=key_id) + else: + self.proposed = dict(state=self.state, vpn_name=vpn_name, + source_int=if_name, peer=self.address, + is_preferred=self.is_preferred, key_id=key_id) + + def get_end_state(self): + """Get end state info""" + + if self.address: + self.end_state = self.get_ntp_exist_config() + + def get_update_cmd(self): + """Get updated commands""" + + if self.conf_exsit: + return + + cli_str = "" + if self.state == "present": + if self.address: + if self.peer_type == 'Server': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "ntp unicast-server", self.address) + else: + cli_str = "%s %s" % ( + "ntp unicast-server ipv6", self.address) + elif self.peer_type == 'Peer': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ("ntp unicast-peer", self.address) + else: + cli_str = "%s %s" % ( + "ntp unicast-peer ipv6", self.address) + + if self.key_id: + cli_str = "%s %s %s" % ( + cli_str, "authentication-keyid", self.key_id) + if self.interface: + cli_str = "%s %s %s" % ( + cli_str, "source-interface", self.interface) + if (self.vpn_name) and (self.vpn_name != '_public_'): + cli_str = "%s %s %s" % ( + cli_str, "vpn-instance", self.vpn_name) + if self.is_preferred == "enable": + cli_str = "%s %s" % (cli_str, "preferred") + else: + if self.address: + if self.peer_type == 'Server': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "undo ntp unicast-server", self.address) + else: + cli_str = "%s %s" % ( + "undo ntp unicast-server ipv6", self.address) + elif self.peer_type == 'Peer': + if self.ip_ver == "IPv4": + cli_str = "%s %s" % ( + "undo ntp unicast-peer", self.address) + else: + cli_str = "%s %s" % ( + "undo ntp unicast-peer ipv6", self.address) + if (self.vpn_name) and (self.vpn_name != '_public_'): + cli_str = "%s %s %s" % ( + cli_str, "vpn-instance", self.vpn_name) + + self.updates_cmd.append(cli_str) + + def work(self): + """Execute task""" + + self.get_existing() + self.get_proposed() + + self.config_ntp() + + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + server=dict(type='str'), + peer=dict(type='str'), + key_id=dict(type='str'), + is_preferred=dict(type='str', choices=['enable', 'disable']), + vpn_name=dict(type='str', default='_public_'), + source_int=dict(type='str'), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + ntp_obj = Ntp(argument_spec) + ntp_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp_auth.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp_auth.py new file mode 100644 index 00000000..e44d65f6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ntp_auth.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- + +module: ce_ntp_auth +short_description: Manages NTP authentication configuration on HUAWEI CloudEngine switches. +description: + - Manages NTP authentication configuration on HUAWEI CloudEngine switches. +author: + - Zhijin Zhou (@QijunPan) +notes: + - 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) and C(authentication=on), authentication will be turned on. + - If C(state=absent) and C(authentication=off), authentication will be turned off. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + key_id: + description: + - Authentication key identifier (numeric). + required: true + auth_pwd: + description: + - Plain text with length of 1 to 255, encrypted text with length of 20 to 392. + auth_mode: + description: + - Specify authentication algorithm. + choices: ['hmac-sha256', 'md5'] + auth_type: + description: + - Whether the given password is in cleartext or + has been encrypted. If in cleartext, the device + will encrypt it before storing it. + default: encrypt + choices: ['text', 'encrypt'] + trusted_key: + description: + - Whether the given key is required to be supplied by a time source + for the device to synchronize to the time source. + default: 'disable' + choices: ['enable', 'disable'] + authentication: + description: + - Configure ntp authentication enable or unconfigure ntp authentication enable. + choices: ['enable', 'disable'] + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: NTP AUTH test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure ntp authentication key-id" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + provider: "{{ cli }}" + + - name: "Configure ntp authentication key-id and trusted authentication keyid" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + trusted_key: enable + provider: "{{ cli }}" + + - name: "Configure ntp authentication key-id and authentication enable" + community.network.ce_ntp_auth: + key_id: 32 + auth_mode: md5 + auth_pwd: 11111111111111111111111 + authentication: enable + provider: "{{ cli }}" + + - name: "Unconfigure ntp authentication key-id and trusted authentication keyid" + community.network.ce_ntp_auth: + key_id: 32 + state: absent + provider: "{{ cli }}" + + - name: "Unconfigure ntp authentication key-id and authentication enable" + community.network.ce_ntp_auth: + key_id: 32 + authentication: enable + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "auth_type": "text", + "authentication": "enable", + "key_id": "32", + "auth_pwd": "1111", + "auth_mode": "md5", + "trusted_key": "enable", + "state": "present" + } +existing: + description: k/v pairs of existing ntp authentication + returned: always + type: dict + sample: { + "authentication": "off", + "authentication-keyid": [ + { + "auth_mode": "md5", + "key_id": "1", + "trusted_key": "disable" + } + ] + } +end_state: + description: k/v pairs of ntp authentication after module execution + returned: always + type: dict + sample: { + "authentication": "off", + "authentication-keyid": [ + { + "auth_mode": "md5", + "key_id": "1", + "trusted_key": "disable" + }, + { + "auth_mode": "md5", + "key_id": "32", + "trusted_key": "enable" + } + ] + } +state: + description: state as sent in from the playbook + returned: always + type: str + sample: "present" +updates: + description: command sent to the device + returned: always + type: list + sample: [ + "ntp authentication-key 32 md5 1111", + "ntp trusted-key 32", + "ntp authentication enable" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import copy +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config +from ansible.module_utils.connection import exec_command + + +class NtpAuth(object): + """Manage ntp authentication""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # ntp_auth configuration info + self.key_id = self.module.params['key_id'] + self.password = self.module.params['auth_pwd'] or None + self.auth_mode = self.module.params['auth_mode'] or None + self.auth_type = self.module.params['auth_type'] + self.trusted_key = self.module.params['trusted_key'] + self.authentication = self.module.params['authentication'] or None + self.state = self.module.params['state'] + self.check_params() + + self.ntp_auth_conf = dict() + self.key_id_exist = False + self.cur_trusted_key = 'disable' + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = list() + self.end_state = list() + + self.get_ntp_auth_exist_config() + + def check_params(self): + """Check all input params""" + + if not self.key_id.isdigit(): + self.module.fail_json( + msg='Error: key_id is not digit.') + + if (int(self.key_id) < 1) or (int(self.key_id) > 4294967295): + self.module.fail_json( + msg='Error: The length of key_id is between 1 and 4294967295.') + if self.state == "present" and not self.password: + self.module.fail_json( + msg='Error: The password cannot be empty.') + if self.state == "present" and self.password: + if (self.auth_type == 'encrypt') and\ + ((len(self.password) < 20) or (len(self.password) > 392)): + self.module.fail_json( + msg='Error: The length of encrypted password is between 20 and 392.') + elif (self.auth_type == 'text') and\ + ((len(self.password) < 1) or (len(self.password) > 255)): + self.module.fail_json( + msg='Error: The length of text password is between 1 and 255.') + + def init_module(self): + """Init module object""" + + required_if = [("state", "present", ("auth_pwd", "auth_mode"))] + self.module = AnsibleModule( + argument_spec=self.spec, + required_if=required_if, + supports_check_mode=True + ) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_ntp_auth_enable(self): + """Get ntp authentication enable state""" + + flags = list() + exp = "| exclude undo | include ntp authentication" + flags.append(exp) + config = self.get_config(flags) + auth_en = re.findall( + r'.*ntp\s*authentication\s*enable.*', config) + if auth_en: + self.ntp_auth_conf['authentication'] = 'enable' + else: + self.ntp_auth_conf['authentication'] = 'disable' + + def get_ntp_all_auth_keyid(self): + """Get all authentication keyid info""" + + ntp_auth_conf = list() + + flags = list() + exp = "| include authentication-keyid %s" % self.key_id + flags.append(exp) + config = self.get_config(flags) + ntp_config_list = config.split('\n') + if not ntp_config_list: + self.ntp_auth_conf["authentication-keyid"] = "None" + return ntp_auth_conf + + self.key_id_exist = True + cur_auth_mode = "" + cur_auth_pwd = "" + for ntp_config in ntp_config_list: + ntp_auth_mode = re.findall(r'.*authentication-mode(\s\S*)\s\S*\s(\S*)', ntp_config) + ntp_auth_trust = re.findall(r'.*trusted.*', ntp_config) + if ntp_auth_trust: + self.cur_trusted_key = 'enable' + if ntp_auth_mode: + cur_auth_mode = ntp_auth_mode[0][0].strip() + cur_auth_pwd = ntp_auth_mode[0][1].strip() + ntp_auth_conf.append(dict(key_id=self.key_id, + auth_mode=cur_auth_mode, + auth_pwd=cur_auth_pwd, + trusted_key=self.cur_trusted_key)) + self.ntp_auth_conf["authentication-keyid"] = ntp_auth_conf + + return ntp_auth_conf + + def get_ntp_auth_exist_config(self): + """Get ntp authentication existed configure""" + + self.get_ntp_auth_enable() + self.get_ntp_all_auth_keyid() + + def config_ntp_auth_keyid(self): + """Config ntp authentication keyid""" + + commands = list() + if self.auth_type == 'encrypt': + config_cli = "ntp authentication-keyid %s authentication-mode %s cipher %s" % ( + self.key_id, self.auth_mode, self.password) + else: + config_cli = "ntp authentication-keyid %s authentication-mode %s %s" % ( + self.key_id, self.auth_mode, self.password) + + commands.append(config_cli) + + if self.trusted_key != self.cur_trusted_key: + if self.trusted_key == 'enable': + config_cli_trust = "ntp trusted authentication-keyid %s" % (self.key_id) + commands.append(config_cli_trust) + else: + config_cli_trust = "undo ntp trusted authentication-keyid %s" % (self.key_id) + commands.append(config_cli_trust) + + self.cli_load_config(commands) + + def config_ntp_auth_enable(self): + """Config ntp authentication enable""" + + commands = list() + if self.ntp_auth_conf['authentication'] != self.authentication: + if self.authentication == 'enable': + config_cli = "ntp authentication enable" + else: + config_cli = "undo ntp authentication enable" + commands.append(config_cli) + + self.cli_load_config(commands) + + def undo_config_ntp_auth_keyid(self): + """Undo ntp authentication key-id""" + + commands = list() + config_cli = "undo ntp authentication-keyid %s" % self.key_id + commands.append(config_cli) + + self.cli_load_config(commands) + + def cli_load_config(self, commands): + """Load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def config_ntp_auth(self): + """Config ntp authentication""" + + if self.state == "present": + self.config_ntp_auth_keyid() + else: + if not self.key_id_exist: + self.module.fail_json( + msg='Error: The Authentication-keyid does not exist.') + self.undo_config_ntp_auth_keyid() + + if self.authentication: + self.config_ntp_auth_enable() + + self.changed = True + + def get_existing(self): + """Get existing info""" + + self.existing = copy.deepcopy(self.ntp_auth_conf) + + def get_proposed(self): + """Get proposed result""" + + auth_type = self.auth_type + trusted_key = self.trusted_key + if self.state == 'absent': + auth_type = None + trusted_key = None + self.proposed = dict(key_id=self.key_id, auth_pwd=self.password, + auth_mode=self.auth_mode, auth_type=auth_type, + trusted_key=trusted_key, authentication=self.authentication, + state=self.state) + + def get_update_cmd(self): + """Get updated commands""" + + cli_str = "" + if self.state == "present": + cli_str = "ntp authentication-keyid %s authentication-mode %s " % ( + self.key_id, self.auth_mode) + if self.auth_type == 'encrypt': + cli_str = "%s cipher %s" % (cli_str, self.password) + else: + cli_str = "%s %s" % (cli_str, self.password) + else: + cli_str = "undo ntp authentication-keyid %s" % self.key_id + + self.updates_cmd.append(cli_str) + + if self.authentication: + cli_str = "" + + if self.ntp_auth_conf['authentication'] != self.authentication: + if self.authentication == 'enable': + cli_str = "ntp authentication enable" + else: + cli_str = "undo ntp authentication enable" + + if cli_str != "": + self.updates_cmd.append(cli_str) + + cli_str = "" + if self.state == "present": + if self.trusted_key != self.cur_trusted_key: + if self.trusted_key == 'enable': + cli_str = "ntp trusted authentication-keyid %s" % self.key_id + else: + cli_str = "undo ntp trusted authentication-keyid %s" % self.key_id + else: + cli_str = "undo ntp trusted authentication-keyid %s" % self.key_id + + if cli_str != "": + self.updates_cmd.append(cli_str) + + def get_end_state(self): + """Get end state info""" + + self.ntp_auth_conf = dict() + self.get_ntp_auth_exist_config() + self.end_state = copy.deepcopy(self.ntp_auth_conf) + if self.end_state == self.existing: + self.changed = False + + def show_result(self): + """Show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def work(self): + """Execute task""" + + self.get_existing() + self.get_proposed() + self.get_update_cmd() + + self.config_ntp_auth() + + self.get_end_state() + self.show_result() + + +def main(): + """Main function entry""" + + argument_spec = dict( + key_id=dict(required=True, type='str'), + auth_pwd=dict(type='str', no_log=True), + auth_mode=dict(choices=['md5', 'hmac-sha256'], type='str'), + auth_type=dict(choices=['text', 'encrypt'], default='encrypt'), + trusted_key=dict(choices=['enable', 'disable'], default='disable'), + authentication=dict(choices=['enable', 'disable']), + state=dict(choices=['absent', 'present'], default='present'), + ) + argument_spec.update(ce_argument_spec) + ntp_auth_obj = NtpAuth(argument_spec) + ntp_auth_obj.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf.py new file mode 100644 index 00000000..6f7a6646 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf.py @@ -0,0 +1,968 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ospf +short_description: Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF instance on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + process_id: + description: + - Specifies a process ID. + The value is an integer ranging from 1 to 4294967295. + required: true + area: + description: + - Specifies the area ID. The area with the area-id being 0 is a backbone area. + Valid values are a string, formatted as an IP address + (i.e. "0.0.0.0") or as an integer between 1 and 4294967295. + addr: + description: + - Specifies the address of the network segment where the interface resides. + The value is in dotted decimal notation. + mask: + description: + - IP network wildcard bits in decimal format between 0 and 32. + auth_mode: + description: + - Specifies the authentication type. + choices: ['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'] + auth_text_simple: + description: + - Specifies a password for simple authentication. + The value is a string of 1 to 8 characters. + auth_key_id: + description: + - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5. + Valid value is an integer is in the range from 1 to 255. + auth_text_md5: + description: + - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication. + The value is a string of 1 to 255 case-sensitive characters, spaces not supported. + nexthop_addr: + description: + - IPv4 address for configure next-hop address's weight. + Valid values are a string, formatted as an IP address. + nexthop_weight: + description: + - Indicates the weight of the next hop. + The smaller the value is, the higher the preference of the route is. + It is an integer that ranges from 1 to 254. + max_load_balance: + description: + - The maximum number of paths for forward packets over multiple paths. + Valid value is an integer in the range from 1 to 64. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Ospf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure ospf + community.network.ce_ospf: + process_id: 1 + area: 100 + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"process_id": "1", "area": "100"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"process_id": "1", "areas": [], "nexthops":[], "max_load_balance": "32"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"process_id": "1", + "areas": [{"areaId": "0.0.0.100", "areaType": "Normal"}], + "nexthops":[], "max_load_balance": "32"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["ospf 1", "area 0.0.0.100"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF = """ + + + + + + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_CREATE_PROCESS = """ + + + + + + %s + + + + + +""" + +CE_NC_DELETE_PROCESS = """ + + + + + + %s + + + + + +""" + +CE_NC_XML_BUILD_MERGE_PROCESS = """ + + + + + + %s + %s + + + + + +""" + +CE_NC_XML_BUILD_PROCESS = """ + + + + + + %s + %s + + + + + +""" + +CE_NC_XML_BUILD_MERGE_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_BUILD_DELETE_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_BUILD_AREA = """ + + + %s + %s + + +""" + +CE_NC_XML_SET_AUTH_MODE = """ + %s +""" +CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """ + %s +""" + +CE_NC_XML_SET_AUTH_MD5 = """ + %s + %s +""" + + +CE_NC_XML_MERGE_NETWORKS = """ + + + %s + %s + + +""" + +CE_NC_XML_DELETE_NETWORKS = """ + + + %s + %s + + +""" + +CE_NC_XML_SET_LB = """ + %s +""" + + +CE_NC_XML_BUILD_MERGE_TOPO = """ + + + base + %s + + + +""" + +CE_NC_XML_BUILD_TOPO = """ + + + base + %s + + + +""" + +CE_NC_XML_MERGE_NEXTHOP = """ + + + %s + %s + + +""" + +CE_NC_XML_DELETE_NEXTHOP = """ + + + %s + + +""" + + +class OSPF(object): + """ + Manages configuration of an ospf instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.process_id = self.module.params['process_id'] + self.area = self.module.params['area'] + self.addr = self.module.params['addr'] + self.mask = self.module.params['mask'] + self.auth_mode = self.module.params['auth_mode'] + self.auth_text_simple = self.module.params['auth_text_simple'] + self.auth_key_id = self.module.params['auth_key_id'] + self.auth_text_md5 = self.module.params['auth_text_md5'] + self.nexthop_addr = self.module.params['nexthop_addr'] + self.nexthop_weight = self.module.params['nexthop_weight'] + self.max_load_balance = self.module.params['max_load_balance'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + + required_together = [ + ("addr", "mask"), + ("auth_key_id", "auth_text_md5"), + ("nexthop_addr", "nexthop_weight") + ] + self.module = AnsibleModule( + argument_spec=self.spec, required_together=required_together, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_wildcard_mask(self): + """convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255""" + + mask_int = ["255"] * 4 + length = int(self.mask) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int(~(0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '0' + mask_int[1] = str(int(~(0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '0' + mask_int[2] = str(int(~(0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '0' + mask_int[3] = str(int(~(0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '0' + + return '.'.join(mask_int) + + def get_area_ip(self): + """convert integer to ip address""" + + if not self.area.isdigit(): + return self.area + + addr_int = ['0'] * 4 + addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF) + addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF) + addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF) + addr_int[3] = str(int(self.area) & 0xFF) + + return '.'.join(addr_int) + + def get_ospf_dict(self, process_id): + """ get one ospf attributes dict.""" + + ospf_info = dict() + conf_str = CE_NC_GET_OSPF % process_id + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return ospf_info + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get process base info + root = ElementTree.fromstring(xml_str) + ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite") + if ospfsite: + for site in ospfsite: + if site.tag in ["processId", "routerId", "vrfName"]: + ospf_info[site.tag] = site.text + + # get Topology info + topo = root.find( + "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology") + if topo: + for eles in topo: + if eles.tag in ["maxLoadBalancing"]: + ospf_info[eles.tag] = eles.text + + # get nexthop info + ospf_info["nexthops"] = list() + nexthops = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology/nexthopMTs/nexthopMT") + if nexthops: + for nexthop in nexthops: + nh_dict = dict() + for ele in nexthop: + if ele.tag in ["ipAddress", "weight"]: + nh_dict[ele.tag] = ele.text + ospf_info["nexthops"].append(nh_dict) + + # get areas info + ospf_info["areas"] = list() + areas = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area") + if areas: + for area in areas: + area_dict = dict() + for ele in area: + if ele.tag in ["areaId", "authTextSimple", "areaType", + "authenticationMode", "keyId", "authTextMd5"]: + area_dict[ele.tag] = ele.text + if ele.tag == "networks": + # get networks info + area_dict["networks"] = list() + for net in ele: + net_dict = dict() + for net_ele in net: + if net_ele.tag in ["ipAddress", "wildcardMask"]: + net_dict[net_ele.tag] = net_ele.text + area_dict["networks"].append(net_dict) + + ospf_info["areas"].append(area_dict) + return ospf_info + + def is_area_exist(self): + """is ospf area exist""" + if not self.ospf_info: + return False + for area in self.ospf_info["areas"]: + if area["areaId"] == self.get_area_ip(): + return True + + return False + + def is_network_exist(self): + """is ospf area network exist""" + if not self.ospf_info: + return False + + for area in self.ospf_info["areas"]: + if area["areaId"] == self.get_area_ip(): + if not area.get("networks"): + return False + for network in area.get("networks"): + if network["ipAddress"] == self.addr and network["wildcardMask"] == self.get_wildcard_mask(): + return True + return False + + def is_nexthop_exist(self): + """is ospf nexthop exist""" + + if not self.ospf_info: + return False + for nexthop in self.ospf_info["nexthops"]: + if nexthop["ipAddress"] == self.nexthop_addr: + return True + + return False + + def is_nexthop_change(self): + """is ospf nexthop change""" + if not self.ospf_info: + return True + + for nexthop in self.ospf_info["nexthops"]: + if nexthop["ipAddress"] == self.nexthop_addr: + if nexthop["weight"] == self.nexthop_weight: + return False + else: + return True + + return True + + def create_process(self): + """Create ospf process""" + + xml_area = "" + self.updates_cmd.append("ospf %s" % self.process_id) + xml_create = CE_NC_CREATE_PROCESS % self.process_id + set_nc_config(self.module, xml_create) + + # nexthop weight + xml_nh = "" + if self.nexthop_addr: + xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( + self.nexthop_addr, self.nexthop_weight) + self.updates_cmd.append("nexthop %s weight %s" % ( + self.nexthop_addr, self.nexthop_weight)) + + # max load balance + xml_lb = "" + if self.max_load_balance: + xml_lb = CE_NC_XML_SET_LB % self.max_load_balance + self.updates_cmd.append( + "maximum load-balancing %s" % self.max_load_balance) + + xml_topo = "" + if xml_lb or xml_nh: + xml_topo = CE_NC_XML_BUILD_TOPO % (xml_nh + xml_lb) + + if self.area: + self.updates_cmd.append("area %s" % self.get_area_ip()) + xml_auth = "" + xml_network = "" + + # networks + if self.addr and self.mask: + xml_network = CE_NC_XML_MERGE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + self.updates_cmd.append("network %s %s" % ( + self.addr, self.get_wildcard_mask())) + + # authentication mode + if self.auth_mode: + xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo authentication-mode") + else: + self.updates_cmd.append( + "authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and self.auth_text_md5: + xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + if xml_network or xml_auth or not self.is_area_exist(): + xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( + self.get_area_ip(), xml_network + xml_auth) + + xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( + self.process_id, xml_topo + xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CREATE_PROCESS") + self.changed = True + + def delete_process(self): + """Delete ospf process""" + + xml_str = CE_NC_DELETE_PROCESS % self.process_id + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_PROCESS") + self.updates_cmd.append("undo ospf %s" % self.process_id) + self.changed = True + + def merge_process(self): + """merge ospf process""" + + xml_area = "" + xml_str = "" + self.updates_cmd.append("ospf %s" % self.process_id) + + # nexthop weight + xml_nh = "" + if self.nexthop_addr and self.is_nexthop_change(): + xml_nh = CE_NC_XML_MERGE_NEXTHOP % ( + self.nexthop_addr, self.nexthop_weight) + self.updates_cmd.append("nexthop %s weight %s" % ( + self.nexthop_addr, self.nexthop_weight)) + + # max load balance + xml_lb = "" + if self.max_load_balance and self.ospf_info.get("maxLoadBalancing") != self.max_load_balance: + xml_lb = CE_NC_XML_SET_LB % self.max_load_balance + self.updates_cmd.append( + "maximum load-balancing %s" % self.max_load_balance) + + xml_topo = "" + if xml_lb or xml_nh: + xml_topo = CE_NC_XML_BUILD_MERGE_TOPO % (xml_nh + xml_lb) + + if self.area: + self.updates_cmd.append("area %s" % self.get_area_ip()) + xml_network = "" + xml_auth = "" + if self.addr and self.mask: + if not self.is_network_exist(): + xml_network += CE_NC_XML_MERGE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + self.updates_cmd.append("network %s %s" % ( + self.addr, self.get_wildcard_mask())) + + # NOTE: for security, authentication config will always be update + if self.auth_mode: + xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode + if self.auth_mode == "none": + self.updates_cmd.append("undo authentication-mode") + else: + self.updates_cmd.append( + "authentication-mode %s" % self.auth_mode) + if self.auth_mode == "simple" and self.auth_text_simple: + xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple)) + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id and self.auth_text_md5: + xml_auth += CE_NC_XML_SET_AUTH_MD5 % ( + self.auth_key_id, self.auth_text_md5) + self.updates_cmd.pop() + self.updates_cmd.append( + "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5)) + if xml_network or xml_auth or not self.is_area_exist(): + xml_area += CE_NC_XML_BUILD_MERGE_AREA % ( + self.get_area_ip(), xml_network + xml_auth) + elif self.is_area_exist(): + self.updates_cmd.pop() # remove command: area + else: + pass + + if xml_area or xml_topo: + xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % ( + self.process_id, xml_topo + xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "MERGE_PROCESS") + self.changed = True + + def remove_area_network(self): + """remvoe ospf area network""" + + if not self.is_network_exist(): + return + + xml_network = CE_NC_XML_DELETE_NETWORKS % ( + self.addr, self.get_wildcard_mask()) + xml_area = CE_NC_XML_BUILD_AREA % (self.get_area_ip(), xml_network) + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_AREA_NETWORK") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("area %s" % self.get_area_ip()) + self.updates_cmd.append("undo network %s %s" % + (self.addr, self.get_wildcard_mask())) + self.changed = True + + def remove_area(self): + """remove ospf area""" + + if not self.is_area_exist(): + return + + xml_area = CE_NC_XML_BUILD_DELETE_AREA % (self.get_area_ip(), "") + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_AREA") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("undo area %s" % self.get_area_ip()) + self.changed = True + + def remove_nexthop(self): + """remove ospf nexthop weight""" + + if not self.is_nexthop_exist(): + return + + xml_nh = CE_NC_XML_DELETE_NEXTHOP % self.nexthop_addr + xml_topo = CE_NC_XML_BUILD_TOPO % xml_nh + xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_topo) + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "DELETE_NEXTHOP_WEIGHT") + self.updates_cmd.append("ospf %s" % self.process_id) + self.updates_cmd.append("undo nexthop %s" % self.nexthop_addr) + self.changed = True + + def is_valid_v4addr(self, addr): + """check is ipv4 addr is valid""" + + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + def convert_ip_to_network(self): + """convert ip to subnet address""" + + ip_list = self.addr.split('.') + mask_list = self.get_wildcard_mask().split('.') + + for i in range(len(ip_list)): + ip_list[i] = str((int(ip_list[i]) & (~int(mask_list[i]))) & 0xff) + + self.addr = '.'.join(ip_list) + + def check_params(self): + """Check all input params""" + + # process_id check + if not self.process_id.isdigit(): + self.module.fail_json(msg="Error: process_id is not digit.") + if int(self.process_id) < 1 or int(self.process_id) > 4294967295: + self.module.fail_json( + msg="Error: process_id must be an integer between 1 and 4294967295.") + + if self.area: + # area check + if self.area.isdigit(): + if int(self.area) < 0 or int(self.area) > 4294967295: + self.module.fail_json( + msg="Error: area id (Integer) must be between 0 and 4294967295.") + + else: + if not self.is_valid_v4addr(self.area): + self.module.fail_json(msg="Error: area id is invalid.") + + # area network check + if self.addr: + if not self.is_valid_v4addr(self.addr): + self.module.fail_json( + msg="Error: network addr is invalid.") + if not self.mask.isdigit(): + self.module.fail_json( + msg="Error: network mask is not digit.") + if int(self.mask) < 0 or int(self.mask) > 32: + self.module.fail_json( + msg="Error: network mask is invalid.") + + # area authentication check + if self.state == "present" and self.auth_mode: + if self.auth_mode == "simple": + if self.auth_text_simple and len(self.auth_text_simple) > 8: + self.module.fail_json( + msg="Error: auth_text_simple is not in the range from 1 to 8.") + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + if self.auth_key_id: + if not self.auth_key_id.isdigit(): + self.module.fail_json( + msg="Error: auth_key_id is not digit.") + if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255: + self.module.fail_json( + msg="Error: auth_key_id is not in the range from 1 to 255.") + if self.auth_text_md5 and len(self.auth_text_md5) > 255: + self.module.fail_json( + msg="Error: auth_text_md5 is not in the range from 1 to 255.") + + # process max load balance check + if self.state == "present" and self.max_load_balance: + if not self.max_load_balance.isdigit(): + self.module.fail_json( + msg="Error: max_load_balance is not digit.") + if int(self.max_load_balance) < 1 or int(self.max_load_balance) > 64: + self.module.fail_json( + msg="Error: max_load_balance is not in the range from 1 to 64.") + + # process nexthop weight check + if self.nexthop_addr: + if not self.is_valid_v4addr(self.nexthop_addr): + self.module.fail_json(msg="Error: nexthop_addr is invalid.") + if not self.nexthop_weight.isdigit(): + self.module.fail_json( + msg="Error: nexthop_weight is not digit.") + if int(self.nexthop_weight) < 1 or int(self.nexthop_weight) > 254: + self.module.fail_json( + msg="Error: nexthop_weight is not in the range from 1 to 254.") + + if self.addr: + self.convert_ip_to_network() + + def get_proposed(self): + """get proposed info""" + + self.proposed["process_id"] = self.process_id + self.proposed["area"] = self.area + if self.area: + self.proposed["addr"] = self.addr + self.proposed["mask"] = self.mask + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.auth_mode == "simple": + self.proposed["auth_text_simple"] = self.auth_text_simple + if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]: + self.proposed["auth_key_id"] = self.auth_key_id + self.proposed["auth_text_md5"] = self.auth_text_md5 + + if self.nexthop_addr: + self.proposed["nexthop_addr"] = self.nexthop_addr + self.proposed["nexthop_weight"] = self.nexthop_weight + self.proposed["max_load_balance"] = self.max_load_balance + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.ospf_info: + return + + self.existing["process_id"] = self.process_id + self.existing["areas"] = self.ospf_info["areas"] + self.existing["nexthops"] = self.ospf_info["nexthops"] + self.existing["max_load_balance"] = self.ospf_info.get( + "maxLoadBalancing") + + def get_end_state(self): + """get end state info""" + + ospf_info = self.get_ospf_dict(self.process_id) + + if not ospf_info: + return + + self.end_state["process_id"] = self.process_id + self.end_state["areas"] = ospf_info["areas"] + self.end_state["nexthops"] = ospf_info["nexthops"] + self.end_state["max_load_balance"] = ospf_info.get("maxLoadBalancing") + + if self.end_state == self.existing: + if not self.auth_text_simple and not self.auth_text_md5: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.ospf_info = self.get_ospf_dict(self.process_id) + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.state == "present": + if not self.ospf_info: + # create ospf process + self.create_process() + else: + # merge ospf + self.merge_process() + else: + if self.ospf_info: + if self.area: + if self.addr: + # remove ospf area network + self.remove_area_network() + else: + # remove ospf area + self.remove_area() + if self.nexthop_addr: + # remove ospf nexthop weight + self.remove_nexthop() + + if not self.area and not self.nexthop_addr: + # remove ospf process + self.delete_process() + else: + self.module.fail_json(msg='Error: ospf process does not exist') + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + process_id=dict(required=True, type='str'), + area=dict(required=False, type='str'), + addr=dict(required=False, type='str'), + mask=dict(required=False, type='str'), + auth_mode=dict(required=False, + choices=['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'), + auth_text_simple=dict(required=False, type='str', no_log=True), + auth_key_id=dict(required=False, type='str'), + auth_text_md5=dict(required=False, type='str', no_log=True), + nexthop_addr=dict(required=False, type='str'), + nexthop_weight=dict(required=False, type='str'), + max_load_balance=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = OSPF(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf_vrf.py new file mode 100644 index 00000000..56c0e5ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_ospf_vrf.py @@ -0,0 +1,1619 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_ospf_vrf +short_description: Manages configuration of an OSPF VPN instance on HUAWEI CloudEngine switches. +description: + - Manages configuration of an OSPF VPN instance on HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + ospf: + description: + - The ID of the ospf process. + Valid values are an integer, 1 - 4294967295, the default value is 1. + required: true + route_id: + description: + - Specifies the ospf private route id,. + Valid values are a string, formatted as an IP address + (i.e. "10.1.1.1") the length is 0 - 20. + vrf: + description: + - Specifies the vpn instance which use ospf,length is 1 - 31. + Valid values are a string. + default: _public_ + description: + description: + - Specifies the description information of ospf process. + bandwidth: + description: + - Specifies the reference bandwidth used to assign ospf cost. + Valid values are an integer, in Mbps, 1 - 2147483648, the default value is 100. + lsaalflag: + description: + - Specifies the mode of timer to calculate interval of arrive LSA. + If set the parameter but not specifies value, the default will be used. + If true use general timer. + If false use intelligent timer. + type: bool + default: 'no' + lsaainterval: + description: + - Specifies the interval of arrive LSA when use the general timer. + Valid value is an integer, in millisecond, from 0 to 10000. + lsaamaxinterval: + description: + - Specifies the max interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 1000. + lsaastartinterval: + description: + - Specifies the start interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 500. + lsaaholdinterval: + description: + - Specifies the hold interval of arrive LSA when use the intelligent timer. + Valid value is an integer, in millisecond, from 0 to 10000, the default value is 500. + lsaointervalflag: + description: + - Specifies whether cancel the interval of LSA originate or not. + If set the parameter but noe specifies value, the default will be used. + true:cancel the interval of LSA originate, the interval is 0. + false:do not cancel the interval of LSA originate. + type: bool + default: 'no' + lsaointerval: + description: + - Specifies the interval of originate LSA . + Valid value is an integer, in second, from 0 to 10, the default value is 5. + lsaomaxinterval: + description: + - Specifies the max interval of originate LSA . + Valid value is an integer, in millisecond, from 1 to 10000, the default value is 5000. + lsaostartinterval: + description: + - Specifies the start interval of originate LSA . + Valid value is an integer, in millisecond, from 0 to 1000, the default value is 500. + lsaoholdinterval: + description: + - Specifies the hold interval of originate LSA . + Valid value is an integer, in millisecond, from 0 to 5000, the default value is 1000. + spfintervaltype: + description: + - Specifies the mode of timer which used to calculate SPF. + If set the parameter but noe specifies value, the default will be used. + If is intelligent-timer, then use intelligent timer. + If is timer, then use second level timer. + If is millisecond, then use millisecond level timer. + choices: ['intelligent-timer','timer','millisecond'] + default: intelligent-timer + spfinterval: + description: + - Specifies the interval to calculate SPF when use second level timer. + Valid value is an integer, in second, from 1 to 10. + spfintervalmi: + description: + - Specifies the interval to calculate SPF when use millisecond level timer. + Valid value is an integer, in millisecond, from 1 to 10000. + spfmaxinterval: + description: + - Specifies the max interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 20000, the default value is 5000. + spfstartinterval: + description: + - Specifies the start interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 1000, the default value is 50. + spfholdinterval: + description: + - Specifies the hold interval to calculate SPF when use intelligent timer. + Valid value is an integer, in millisecond, from 1 to 5000, the default value is 200. + state: + description: + - Specify desired state of the resource. + choices: ['present', 'absent'] + default: present +''' + +EXAMPLES = ''' +- name: Ospf vrf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure ospf route id + community.network.ce_ospf_vrf: + ospf: 2 + route_id: 2.2.2.2 + lsaointervalflag: False + lsaointerval: 2 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "bandwidth": "100", + "description": null, + "lsaaholdinterval": "500", + "lsaainterval": null, + "lsaamaxinterval": "1000", + "lsaastartinterval": "500", + "lsaalflag": "False", + "lsaoholdinterval": "1000", + "lsaointerval": "2", + "lsaointervalflag": "False", + "lsaomaxinterval": "5000", + "lsaostartinterval": "500", + "process_id": "2", + "route_id": "2.2.2.2", + "spfholdinterval": "1000", + "spfinterval": null, + "spfintervalmi": null, + "spfintervaltype": "intelligent-timer", + "spfmaxinterval": "10000", + "spfstartinterval": "500", + "vrf": "_public_" + } +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: { + "bandwidthReference": "100", + "description": null, + "lsaArrivalFlag": "false", + "lsaArrivalHoldInterval": "500", + "lsaArrivalInterval": null, + "lsaArrivalMaxInterval": "1000", + "lsaArrivalStartInterval": "500", + "lsaOriginateHoldInterval": "1000", + "lsaOriginateInterval": "2", + "lsaOriginateIntervalFlag": "false", + "lsaOriginateMaxInterval": "5000", + "lsaOriginateStartInterval": "500", + "processId": "2", + "routerId": "2.2.2.2", + "spfScheduleHoldInterval": "1000", + "spfScheduleInterval": null, + "spfScheduleIntervalMillisecond": null, + "spfScheduleIntervalType": "intelligent-timer", + "spfScheduleMaxInterval": "10000", + "spfScheduleStartInterval": "500", + "vrfName": "_public_" + } +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: { + "bandwidthReference": "100", + "description": null, + "lsaArrivalFlag": "false", + "lsaArrivalHoldInterval": "500", + "lsaArrivalInterval": null, + "lsaArrivalMaxInterval": "1000", + "lsaArrivalStartInterval": "500", + "lsaOriginateHoldInterval": "1000", + "lsaOriginateInterval": "2", + "lsaOriginateIntervalFlag": "false", + "lsaOriginateMaxInterval": "5000", + "lsaOriginateStartInterval": "500", + "processId": "2", + "routerId": "2.2.2.2", + "spfScheduleHoldInterval": "1000", + "spfScheduleInterval": null, + "spfScheduleIntervalMillisecond": null, + "spfScheduleIntervalType": "intelligent-timer", + "spfScheduleMaxInterval": "10000", + "spfScheduleStartInterval": "500", + "vrfName": "_public_" + } +updates: + description: commands sent to the device + returned: always + type: list + sample: ["ospf 2"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: False +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_OSPF_VRF = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_CREATE_OSPF_VRF = """ + + + + + %s +%s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + + + + +""" +CE_NC_CREATE_ROUTE_ID = """ + %s +""" + +CE_NC_DELETE_OSPF = """ + + + + + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +class OspfVrf(object): + """ + Manages configuration of an ospf instance. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.ospf = self.module.params['ospf'] + self.route_id = self.module.params['route_id'] + self.vrf = self.module.params['vrf'] + self.description = self.module.params['description'] + self.bandwidth = self.module.params['bandwidth'] + self.lsaalflag = self.module.params['lsaalflag'] + self.lsaainterval = self.module.params['lsaainterval'] + self.lsaamaxinterval = self.module.params['lsaamaxinterval'] + self.lsaastartinterval = self.module.params['lsaastartinterval'] + self.lsaaholdinterval = self.module.params['lsaaholdinterval'] + self.lsaointervalflag = self.module.params['lsaointervalflag'] + self.lsaointerval = self.module.params['lsaointerval'] + self.lsaomaxinterval = self.module.params['lsaomaxinterval'] + self.lsaostartinterval = self.module.params['lsaostartinterval'] + self.lsaoholdinterval = self.module.params['lsaoholdinterval'] + self.spfintervaltype = self.module.params['spfintervaltype'] + self.spfinterval = self.module.params['spfinterval'] + self.spfintervalmi = self.module.params['spfintervalmi'] + self.spfmaxinterval = self.module.params['spfmaxinterval'] + self.spfstartinterval = self.module.params['spfstartinterval'] + self.spfholdinterval = self.module.params['spfholdinterval'] + self.state = self.module.params['state'] + + # ospf info + self.ospf_info = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.lsa_arrival_changed = False + self.lsa_originate_changed = False + self.spf_changed = False + self.route_id_changed = False + self.bandwidth_changed = False + self.description_changed = False + self.vrf_changed = False + + def init_module(self): + """" init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def is_valid_ospf_process_id(self): + """check whether the input ospf process id is valid""" + + if not self.ospf.isdigit(): + return False + if int(self.ospf) > 4294967295 or int(self.ospf) < 1: + return False + return True + + def is_valid_ospf_route_id(self): + """check is ipv4 addr is valid""" + + if self.route_id.find('.') != -1: + addr_list = self.route_id.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + def is_valid_vrf_name(self): + """check whether the input ospf vrf name is valid""" + + if len(self.vrf) > 31 or len(self.vrf) < 1: + return False + if self.vrf.find('?') != -1: + return False + if self.vrf.find(' ') != -1: + return False + return True + + def is_valid_description(self): + """check whether the input ospf description is valid""" + + if len(self.description) > 80 or len(self.description) < 1: + return False + if self.description.find('?') != -1: + return False + return True + + def is_valid_bandwidth(self): + """check whether the input ospf bandwidth reference is valid""" + + if not self.bandwidth.isdigit(): + return False + if int(self.bandwidth) > 2147483648 or int(self.bandwidth) < 1: + return False + return True + + def is_valid_lsa_arrival_interval(self): + """check whether the input ospf lsa arrival interval is valid""" + + if self.lsaainterval is None: + return False + if not self.lsaainterval.isdigit(): + return False + if int(self.lsaainterval) > 10000 or int(self.lsaainterval) < 0: + return False + return True + + def isvalidlsamaxarrivalinterval(self): + """check whether the input ospf lsa max arrival interval is valid""" + + if not self.lsaamaxinterval.isdigit(): + return False + if int(self.lsaamaxinterval) > 10000 or int(self.lsaamaxinterval) < 1: + return False + return True + + def isvalidlsastartarrivalinterval(self): + """check whether the input ospf lsa start arrival interval is valid""" + + if not self.lsaastartinterval.isdigit(): + return False + if int(self.lsaastartinterval) > 1000 or int(self.lsaastartinterval) < 0: + return False + return True + + def isvalidlsaholdarrivalinterval(self): + """check whether the input ospf lsa hold arrival interval is valid""" + + if not self.lsaaholdinterval.isdigit(): + return False + if int(self.lsaaholdinterval) > 5000 or int(self.lsaaholdinterval) < 0: + return False + return True + + def is_valid_lsa_originate_interval(self): + """check whether the input ospf lsa originate interval is valid""" + + if not self.lsaointerval.isdigit(): + return False + if int(self.lsaointerval) > 10 or int(self.lsaointerval) < 0: + return False + return True + + def isvalidlsaoriginatemaxinterval(self): + """check whether the input ospf lsa originate max interval is valid""" + + if not self.lsaomaxinterval.isdigit(): + return False + if int(self.lsaomaxinterval) > 10000 or int(self.lsaomaxinterval) < 1: + return False + return True + + def isvalidlsaostartinterval(self): + """check whether the input ospf lsa originate start interval is valid""" + + if not self.lsaostartinterval.isdigit(): + return False + if int(self.lsaostartinterval) > 1000 or int(self.lsaostartinterval) < 0: + return False + return True + + def isvalidlsaoholdinterval(self): + """check whether the input ospf lsa originate hold interval is valid""" + + if not self.lsaoholdinterval.isdigit(): + return False + if int(self.lsaoholdinterval) > 5000 or int(self.lsaoholdinterval) < 1: + return False + return True + + def is_valid_spf_interval(self): + """check whether the input ospf spf interval is valid""" + + if not self.spfinterval.isdigit(): + return False + if int(self.spfinterval) > 10 or int(self.spfinterval) < 1: + return False + return True + + def is_valid_spf_milli_interval(self): + """check whether the input ospf spf millisecond level interval is valid""" + + if not self.spfintervalmi.isdigit(): + return False + if int(self.spfintervalmi) > 10000 or int(self.spfintervalmi) < 1: + return False + return True + + def is_valid_spf_max_interval(self): + """check whether the input ospf spf intelligent timer max interval is valid""" + + if not self.spfmaxinterval.isdigit(): + return False + if int(self.spfmaxinterval) > 20000 or int(self.spfmaxinterval) < 1: + return False + return True + + def is_valid_spf_start_interval(self): + """check whether the input ospf spf intelligent timer start interval is valid""" + + if not self.spfstartinterval.isdigit(): + return False + if int(self.spfstartinterval) > 1000 or int(self.spfstartinterval) < 1: + return False + return True + + def is_valid_spf_hold_interval(self): + """check whether the input ospf spf intelligent timer hold interval is valid""" + + if not self.spfholdinterval.isdigit(): + return False + if int(self.spfholdinterval) > 5000 or int(self.spfholdinterval) < 1: + return False + return True + + def is_route_id_exist(self): + """is route id exist""" + + if not self.ospf_info: + return False + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] != self.ospf: + continue + if ospf_site["routerId"] == self.route_id: + return True + else: + continue + return False + + def get_exist_ospf_id(self): + """get exist ospf process id""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["processId"] + else: + continue + return None + + def get_exist_route(self): + """get exist route id""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["routerId"] + else: + continue + return None + + def get_exist_vrf(self): + """get exist vrf""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["vrfName"] + else: + continue + return None + + def get_exist_bandwidth(self): + """get exist bandwidth""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["bandwidthReference"] + else: + continue + return None + + def get_exist_lsa_a_interval(self): + """get exist lsa arrival interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalInterval"] + else: + continue + return None + + def get_exist_lsa_a_interval_flag(self): + """get exist lsa arrival interval flag""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalFlag"] + else: + continue + return None + + def get_exist_lsa_a_max_interval(self): + """get exist lsa arrival max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalMaxInterval"] + else: + continue + return None + + def get_exist_lsa_a_start_interval(self): + """get exist lsa arrival start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalStartInterval"] + else: + continue + return None + + def get_exist_lsa_a_hold_interval(self): + """get exist lsa arrival hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaArrivalHoldInterval"] + else: + continue + return None + + def getexistlsaointerval(self): + """get exist lsa originate interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateInterval"] + else: + continue + return None + + def getexistlsaointerval_flag(self): + """get exist lsa originate interval flag""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateIntervalFlag"] + else: + continue + return None + + def getexistlsaomaxinterval(self): + """get exist lsa originate max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateMaxInterval"] + else: + continue + return None + + def getexistlsaostartinterval(self): + """get exist lsa originate start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateStartInterval"] + else: + continue + return None + + def getexistlsaoholdinterval(self): + """get exist lsa originate hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["lsaOriginateHoldInterval"] + else: + continue + return None + + def get_exist_spf_interval(self): + """get exist spf second level timer interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleInterval"] + else: + continue + return None + + def get_exist_spf_milli_interval(self): + """get exist spf millisecond level timer interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleIntervalMillisecond"] + else: + continue + return None + + def get_exist_spf_max_interval(self): + """get exist spf max interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleMaxInterval"] + else: + continue + return None + + def get_exist_spf_start_interval(self): + """get exist spf start interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleStartInterval"] + else: + continue + return None + + def get_exist_spf_hold_interval(self): + """get exist spf hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleHoldInterval"] + else: + continue + return None + + def get_exist_spf_interval_type(self): + """get exist spf hold interval""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["spfScheduleIntervalType"] + else: + continue + return None + + def is_ospf_exist(self): + """is ospf exist""" + + if not self.ospf_info: + return False + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return True + else: + continue + return False + + def get_exist_description(self): + """is description exist""" + + if not self.ospf_info: + return None + + for ospf_site in self.ospf_info["ospfsite"]: + if ospf_site["processId"] == self.ospf: + return ospf_site["description"] + else: + continue + return None + + def check_params(self): + """Check all input params""" + + if self.ospf == '': + self.module.fail_json( + msg='Error: The ospf process id should not be null.') + if self.ospf: + if not self.is_valid_ospf_process_id(): + self.module.fail_json( + msg='Error: The ospf process id should between 1 - 4294967295.') + if self.route_id == '': + self.module.fail_json( + msg='Error: The ospf route id length should not be null.') + if self.route_id: + if not self.is_valid_ospf_route_id(): + self.module.fail_json( + msg='Error: The ospf route id length should between 0 - 20,i.e.10.1.1.1.') + if self.vrf == '': + self.module.fail_json( + msg='Error: The ospf vpn instance length should not be null.') + if self.vrf: + if not self.is_valid_vrf_name(): + self.module.fail_json( + msg='Error: The ospf vpn instance length should between 0 - 31,but can not contain " " or "?".') + if self.description == '': + self.module.fail_json( + msg='Error: The ospf description should not be null.') + if self.description: + if not self.is_valid_description(): + self.module.fail_json( + msg='Error: The ospf description length should between 1 - 80,but can not contain "?".') + if self.bandwidth == '': + self.module.fail_json( + msg='Error: The ospf bandwidth reference should not be null.') + if self.bandwidth: + if not self.is_valid_bandwidth(): + self.module.fail_json( + msg='Error: The ospf bandwidth reference should between 1 - 2147483648.') + if self.lsaalflag is True: + if not self.is_valid_lsa_arrival_interval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival interval should between 0 - 10000.') + if self.lsaamaxinterval or self.lsaastartinterval or self.lsaaholdinterval: + self.module.fail_json( + msg='Error: Non-Intelligent Timer and Intelligent Timer Interval of ' + 'lsa-arrival-interval can not configured at the same time.') + if self.lsaalflag is False: + if self.lsaainterval: + self.module.fail_json( + msg='Error: The parameter of lsa arrival interval command is invalid, ' + 'because LSA arrival interval can not be config when the LSA arrival flag is not set.') + if self.lsaamaxinterval == '' or self.lsaastartinterval == '' or self.lsaaholdinterval == '': + self.module.fail_json( + msg='Error: The ospf lsa arrival intervals should not be null.') + if self.lsaamaxinterval: + if not self.isvalidlsamaxarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival max interval should between 1 - 10000.') + if self.lsaastartinterval: + if not self.isvalidlsastartarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival start interval should between 1 - 1000.') + if self.lsaaholdinterval: + if not self.isvalidlsaholdarrivalinterval(): + self.module.fail_json( + msg='Error: The ospf lsa arrival hold interval should between 1 - 5000.') + if self.lsaointervalflag is True: + if self.lsaointerval or self.lsaomaxinterval \ + or self.lsaostartinterval or self.lsaoholdinterval: + self.module.fail_json( + msg='Error: Interval for other-type and Instantly Flag ' + 'of lsa-originate-interval can not configured at the same time.') + if self.lsaointerval == '': + self.module.fail_json( + msg='Error: The ospf lsa originate interval should should not be null.') + if self.lsaointerval: + if not self.is_valid_lsa_originate_interval(): + self.module.fail_json( + msg='Error: The ospf lsa originate interval should between 0 - 10 s.') + if self.lsaomaxinterval == '' or self.lsaostartinterval == '' or self.lsaoholdinterval == '': + self.module.fail_json( + msg='Error: The ospf lsa originate intelligent intervals should should not be null.') + if self.lsaomaxinterval: + if not self.isvalidlsaoriginatemaxinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate max interval should between 1 - 10000 ms.') + if self.lsaostartinterval: + if not self.isvalidlsaostartinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate start interval should between 0 - 1000 ms.') + if self.lsaoholdinterval: + if not self.isvalidlsaoholdinterval(): + self.module.fail_json( + msg='Error: The ospf lsa originate hold interval should between 1 - 5000 ms.') + if self.spfintervaltype == '': + self.module.fail_json( + msg='Error: The ospf spf interval type should should not be null.') + if self.spfintervaltype == 'intelligent-timer': + if self.spfinterval is not None or self.spfintervalmi is not None: + self.module.fail_json( + msg='Error: Interval second and interval millisecond ' + 'of spf-schedule-interval can not configured if use intelligent timer.') + if self.spfmaxinterval == '' or self.spfstartinterval == '' or self.spfholdinterval == '': + self.module.fail_json( + msg='Error: The ospf spf intelligent timer intervals should should not be null.') + if self.spfmaxinterval and not self.is_valid_spf_max_interval(): + self.module.fail_json( + msg='Error: The ospf spf max interval of intelligent timer should between 1 - 20000 ms.') + if self.spfstartinterval and not self.is_valid_spf_start_interval(): + self.module.fail_json( + msg='Error: The ospf spf start interval of intelligent timer should between 1 - 1000 ms.') + if self.spfholdinterval and not self.is_valid_spf_hold_interval(): + self.module.fail_json( + msg='Error: The ospf spf hold interval of intelligent timer should between 1 - 5000 ms.') + if self.spfintervaltype == 'timer': + if self.spfintervalmi is not None: + self.module.fail_json( + msg='Error: Interval second and interval millisecond ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfmaxinterval or self.spfstartinterval or self.spfholdinterval: + self.module.fail_json( + msg='Error: Interval second and interval intelligent ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfinterval == '' or self.spfinterval is None: + self.module.fail_json( + msg='Error: The ospf spf timer intervals should should not be null.') + if not self.is_valid_spf_interval(): + self.module.fail_json( + msg='Error: Interval second should between 1 - 10 s.') + if self.spfintervaltype == 'millisecond': + if self.spfinterval is not None: + self.module.fail_json( + msg='Error: Interval millisecond and interval second ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfmaxinterval or self.spfstartinterval or self.spfholdinterval: + self.module.fail_json( + msg='Error: Interval millisecond and interval intelligent ' + 'of spf-schedule-interval can not configured at the same time.') + if self.spfintervalmi == '' or self.spfintervalmi is None: + self.module.fail_json( + msg='Error: The ospf spf millisecond intervals should should not be null.') + if not self.is_valid_spf_milli_interval(): + self.module.fail_json( + msg='Error: Interval millisecond should between 1 - 10000 ms.') + + def get_ospf_info(self): + """ get the detail information of ospf """ + + self.ospf_info["ospfsite"] = list() + + getxmlstr = CE_NC_GET_OSPF_VRF + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # get the vpn address family and RD text + ospf_sites = root.findall( + "ospfv2/ospfv2comm/ospfSites/ospfSite") + if ospf_sites: + for ospf_site in ospf_sites: + ospf_ele_info = dict() + for ospf_site_ele in ospf_site: + if ospf_site_ele.tag in ["processId", "routerId", "vrfName", "bandwidthReference", + "description", "lsaArrivalInterval", "lsaArrivalMaxInterval", + "lsaArrivalStartInterval", "lsaArrivalHoldInterval", "lsaArrivalFlag", + "lsaOriginateInterval", "lsaOriginateMaxInterval", + "lsaOriginateStartInterval", "lsaOriginateHoldInterval", + "lsaOriginateIntervalFlag", "spfScheduleInterval", + "spfScheduleIntervalMillisecond", "spfScheduleMaxInterval", + "spfScheduleStartInterval", "spfScheduleHoldInterval", + "spfScheduleIntervalType"]: + ospf_ele_info[ + ospf_site_ele.tag] = ospf_site_ele.text + if ospf_ele_info["processId"] == self.ospf: + self.ospf_info["ospfsite"].append(ospf_ele_info) + + def get_proposed(self): + """get proposed info""" + + self.proposed["process_id"] = self.ospf + self.proposed["route_id"] = self.route_id + self.proposed["vrf"] = self.vrf + self.proposed["description"] = self.description + self.proposed["bandwidth"] = self.bandwidth + self.proposed["lsaalflag"] = self.lsaalflag + self.proposed["lsaainterval"] = self.lsaainterval + self.proposed["lsaamaxinterval"] = self.lsaamaxinterval + self.proposed["lsaastartinterval"] = self.lsaastartinterval + self.proposed["lsaaholdinterval"] = self.lsaaholdinterval + self.proposed["lsaointervalflag"] = self.lsaointervalflag + self.proposed["lsaointerval"] = self.lsaointerval + self.proposed["lsaomaxinterval"] = self.lsaomaxinterval + self.proposed["lsaostartinterval"] = self.lsaostartinterval + self.proposed["lsaoholdinterval"] = self.lsaoholdinterval + self.proposed["spfintervaltype"] = self.spfintervaltype + self.proposed["spfinterval"] = self.spfinterval + self.proposed["spfintervalmi"] = self.spfintervalmi + self.proposed["spfmaxinterval"] = self.spfmaxinterval + self.proposed["spfstartinterval"] = self.spfstartinterval + self.proposed["spfholdinterval"] = self.spfholdinterval + + def operate_ospf_info(self): + """operate ospf info""" + + config_route_id_xml = '' + vrf = self.get_exist_vrf() + if vrf is None: + vrf = '_public_' + description = self.get_exist_description() + if description is None: + description = '' + bandwidth_reference = self.get_exist_bandwidth() + if bandwidth_reference is None: + bandwidth_reference = '100' + lsa_in_interval = self.get_exist_lsa_a_interval() + if lsa_in_interval is None: + lsa_in_interval = '' + lsa_arrival_max_interval = self.get_exist_lsa_a_max_interval() + if lsa_arrival_max_interval is None: + lsa_arrival_max_interval = '1000' + lsa_arrival_start_interval = self.get_exist_lsa_a_start_interval() + if lsa_arrival_start_interval is None: + lsa_arrival_start_interval = '500' + lsa_arrival_hold_interval = self.get_exist_lsa_a_hold_interval() + if lsa_arrival_hold_interval is None: + lsa_arrival_hold_interval = '500' + lsa_originate_interval = self.getexistlsaointerval() + if lsa_originate_interval is None: + lsa_originate_interval = '5' + lsa_originate_max_interval = self.getexistlsaomaxinterval() + if lsa_originate_max_interval is None: + lsa_originate_max_interval = '5000' + lsa_originate_start_interval = self.getexistlsaostartinterval() + if lsa_originate_start_interval is None: + lsa_originate_start_interval = '500' + lsa_originate_hold_interval = self.getexistlsaoholdinterval() + if lsa_originate_hold_interval is None: + lsa_originate_hold_interval = '1000' + spf_interval = self.get_exist_spf_interval() + if spf_interval is None: + spf_interval = '' + spf_interval_milli = self.get_exist_spf_milli_interval() + if spf_interval_milli is None: + spf_interval_milli = '' + spf_max_interval = self.get_exist_spf_max_interval() + if spf_max_interval is None: + spf_max_interval = '5000' + spf_start_interval = self.get_exist_spf_start_interval() + if spf_start_interval is None: + spf_start_interval = '50' + spf_hold_interval = self.get_exist_spf_hold_interval() + if spf_hold_interval is None: + spf_hold_interval = '200' + + if self.route_id: + if self.state == 'present': + if self.route_id != self.get_exist_route(): + self.route_id_changed = True + config_route_id_xml = CE_NC_CREATE_ROUTE_ID % self.route_id + else: + if self.route_id != self.get_exist_route(): + self.module.fail_json( + msg='Error: The route id %s is not exist.' % self.route_id) + self.route_id_changed = True + configxmlstr = CE_NC_DELETE_OSPF % ( + self.ospf, self.get_exist_route(), self.get_exist_vrf()) + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + self.changed = True + return + if self.vrf != '_public_': + if self.state == 'present': + if self.vrf != self.get_exist_vrf(): + self.vrf_changed = True + vrf = self.vrf + else: + if self.vrf != self.get_exist_vrf(): + self.module.fail_json( + msg='Error: The vrf %s is not exist.' % self.vrf) + self.vrf_changed = True + configxmlstr = CE_NC_DELETE_OSPF % ( + self.ospf, self.get_exist_route(), self.get_exist_vrf()) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + self.changed = True + return + if self.bandwidth: + if self.state == 'present': + if self.bandwidth != self.get_exist_bandwidth(): + self.bandwidth_changed = True + bandwidth_reference = self.bandwidth + else: + if self.bandwidth != self.get_exist_bandwidth(): + self.module.fail_json( + msg='Error: The bandwidth %s is not exist.' % self.bandwidth) + if self.get_exist_bandwidth() != '100': + self.bandwidth_changed = True + bandwidth_reference = '100' + if self.description: + if self.state == 'present': + if self.description != self.get_exist_description(): + self.description_changed = True + description = self.description + else: + if self.description != self.get_exist_description(): + self.module.fail_json( + msg='Error: The description %s is not exist.' % self.description) + self.description_changed = True + description = '' + + if self.lsaalflag is False: + lsa_in_interval = '' + if self.state == 'present': + if self.lsaamaxinterval: + if self.lsaamaxinterval != self.get_exist_lsa_a_max_interval(): + self.lsa_arrival_changed = True + lsa_arrival_max_interval = self.lsaamaxinterval + if self.lsaastartinterval: + if self.lsaastartinterval != self.get_exist_lsa_a_start_interval(): + self.lsa_arrival_changed = True + lsa_arrival_start_interval = self.lsaastartinterval + if self.lsaaholdinterval: + if self.lsaaholdinterval != self.get_exist_lsa_a_hold_interval(): + self.lsa_arrival_changed = True + lsa_arrival_hold_interval = self.lsaaholdinterval + else: + if self.lsaamaxinterval: + if self.lsaamaxinterval != self.get_exist_lsa_a_max_interval(): + self.module.fail_json( + msg='Error: The lsaamaxinterval %s is not exist.' % self.lsaamaxinterval) + if self.get_exist_lsa_a_max_interval() != '1000': + lsa_arrival_max_interval = '1000' + self.lsa_arrival_changed = True + if self.lsaastartinterval: + if self.lsaastartinterval != self.get_exist_lsa_a_start_interval(): + self.module.fail_json( + msg='Error: The lsaastartinterval %s is not exist.' % self.lsaastartinterval) + if self.get_exist_lsa_a_start_interval() != '500': + lsa_arrival_start_interval = '500' + self.lsa_arrival_changed = True + if self.lsaaholdinterval: + if self.lsaaholdinterval != self.get_exist_lsa_a_hold_interval(): + self.module.fail_json( + msg='Error: The lsaaholdinterval %s is not exist.' % self.lsaaholdinterval) + if self.get_exist_lsa_a_hold_interval() != '500': + lsa_arrival_hold_interval = '500' + self.lsa_arrival_changed = True + else: + if self.state == 'present': + lsaalflag = "false" + if self.lsaalflag is True: + lsaalflag = "true" + if lsaalflag != self.get_exist_lsa_a_interval_flag(): + self.lsa_arrival_changed = True + if self.lsaainterval is None: + self.module.fail_json( + msg='Error: The lsaainterval is not supplied.') + else: + lsa_in_interval = self.lsaainterval + else: + if self.lsaainterval: + if self.lsaainterval != self.get_exist_lsa_a_interval(): + self.lsa_arrival_changed = True + lsa_in_interval = self.lsaainterval + else: + if self.lsaainterval: + if self.lsaainterval != self.get_exist_lsa_a_interval(): + self.module.fail_json( + msg='Error: The lsaainterval %s is not exist.' % self.lsaainterval) + self.lsaalflag = False + lsa_in_interval = '' + self.lsa_arrival_changed = True + + if self.lsaointervalflag is False: + if self.state == 'present': + if self.lsaomaxinterval: + if self.lsaomaxinterval != self.getexistlsaomaxinterval(): + self.lsa_originate_changed = True + lsa_originate_max_interval = self.lsaomaxinterval + if self.lsaostartinterval: + if self.lsaostartinterval != self.getexistlsaostartinterval(): + self.lsa_originate_changed = True + lsa_originate_start_interval = self.lsaostartinterval + if self.lsaoholdinterval: + if self.lsaoholdinterval != self.getexistlsaoholdinterval(): + self.lsa_originate_changed = True + lsa_originate_hold_interval = self.lsaoholdinterval + if self.lsaointerval: + if self.lsaointerval != self.getexistlsaointerval(): + self.lsa_originate_changed = True + lsa_originate_interval = self.lsaointerval + else: + if self.lsaomaxinterval: + if self.lsaomaxinterval != self.getexistlsaomaxinterval(): + self.module.fail_json( + msg='Error: The lsaomaxinterval %s is not exist.' % self.lsaomaxinterval) + if self.getexistlsaomaxinterval() != '5000': + lsa_originate_max_interval = '5000' + self.lsa_originate_changed = True + if self.lsaostartinterval: + if self.lsaostartinterval != self.getexistlsaostartinterval(): + self.module.fail_json( + msg='Error: The lsaostartinterval %s is not exist.' % self.lsaostartinterval) + if self.getexistlsaostartinterval() != '500': + lsa_originate_start_interval = '500' + self.lsa_originate_changed = True + if self.lsaoholdinterval: + if self.lsaoholdinterval != self.getexistlsaoholdinterval(): + self.module.fail_json( + msg='Error: The lsaoholdinterval %s is not exist.' % self.lsaoholdinterval) + if self.getexistlsaoholdinterval() != '1000': + lsa_originate_hold_interval = '1000' + self.lsa_originate_changed = True + if self.lsaointerval: + if self.lsaointerval != self.getexistlsaointerval(): + self.module.fail_json( + msg='Error: The lsaointerval %s is not exist.' % self.lsaointerval) + if self.getexistlsaointerval() != '5': + lsa_originate_interval = '5' + self.lsa_originate_changed = True + else: + if self.state == 'present': + if self.getexistlsaointerval_flag() != 'true': + self.lsa_originate_changed = True + lsa_originate_interval = '5' + lsa_originate_max_interval = '5000' + lsa_originate_start_interval = '500' + lsa_originate_hold_interval = '1000' + else: + if self.getexistlsaointerval_flag() == 'true': + self.lsaointervalflag = False + self.lsa_originate_changed = True + if self.spfintervaltype != self.get_exist_spf_interval_type(): + self.spf_changed = True + if self.spfintervaltype == 'timer': + if self.spfinterval: + if self.state == 'present': + if self.spfinterval != self.get_exist_spf_interval(): + self.spf_changed = True + spf_interval = self.spfinterval + spf_interval_milli = '' + else: + if self.spfinterval != self.get_exist_spf_interval(): + self.module.fail_json( + msg='Error: The spfinterval %s is not exist.' % self.spfinterval) + self.spfintervaltype = 'intelligent-timer' + spf_interval = '' + self.spf_changed = True + if self.spfintervaltype == 'millisecond': + if self.spfintervalmi: + if self.state == 'present': + if self.spfintervalmi != self.get_exist_spf_milli_interval(): + self.spf_changed = True + spf_interval_milli = self.spfintervalmi + spf_interval = '' + else: + if self.spfintervalmi != self.get_exist_spf_milli_interval(): + self.module.fail_json( + msg='Error: The spfintervalmi %s is not exist.' % self.spfintervalmi) + self.spfintervaltype = 'intelligent-timer' + spf_interval_milli = '' + self.spf_changed = True + if self.spfintervaltype == 'intelligent-timer': + spf_interval = '' + spf_interval_milli = '' + if self.spfmaxinterval: + if self.state == 'present': + if self.spfmaxinterval != self.get_exist_spf_max_interval(): + self.spf_changed = True + spf_max_interval = self.spfmaxinterval + else: + if self.spfmaxinterval != self.get_exist_spf_max_interval(): + self.module.fail_json( + msg='Error: The spfmaxinterval %s is not exist.' % self.spfmaxinterval) + if self.get_exist_spf_max_interval() != '5000': + self.spf_changed = True + spf_max_interval = '5000' + if self.spfstartinterval: + if self.state == 'present': + if self.spfstartinterval != self.get_exist_spf_start_interval(): + self.spf_changed = True + spf_start_interval = self.spfstartinterval + else: + if self.spfstartinterval != self.get_exist_spf_start_interval(): + self.module.fail_json( + msg='Error: The spfstartinterval %s is not exist.' % self.spfstartinterval) + if self.get_exist_spf_start_interval() != '50': + self.spf_changed = True + spf_start_interval = '50' + if self.spfholdinterval: + if self.state == 'present': + if self.spfholdinterval != self.get_exist_spf_hold_interval(): + self.spf_changed = True + spf_hold_interval = self.spfholdinterval + else: + if self.spfholdinterval != self.get_exist_spf_hold_interval(): + self.module.fail_json( + msg='Error: The spfholdinterval %s is not exist.' % self.spfholdinterval) + if self.get_exist_spf_hold_interval() != '200': + self.spf_changed = True + spf_hold_interval = '200' + + if not self.description_changed and not self.vrf_changed and not self.lsa_arrival_changed \ + and not self.lsa_originate_changed and not self.spf_changed \ + and not self.route_id_changed and not self.bandwidth_changed: + self.changed = False + return + else: + self.changed = True + lsaointervalflag = "false" + lsaalflag = "false" + if self.lsaointervalflag is True: + lsaointervalflag = "true" + if self.lsaalflag is True: + lsaalflag = "true" + configxmlstr = CE_NC_CREATE_OSPF_VRF % ( + self.ospf, config_route_id_xml, vrf, + description, bandwidth_reference, lsaalflag, + lsa_in_interval, lsa_arrival_max_interval, lsa_arrival_start_interval, + lsa_arrival_hold_interval, lsaointervalflag, lsa_originate_interval, + lsa_originate_max_interval, lsa_originate_start_interval, lsa_originate_hold_interval, + self.spfintervaltype, spf_interval, spf_interval_milli, + spf_max_interval, spf_start_interval, spf_hold_interval) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + + def get_existing(self): + """get existing info""" + + self.get_ospf_info() + self.existing['ospf_info'] = self.ospf_info["ospfsite"] + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + + if self.state == 'present': + if self.vrf_changed: + if self.vrf != '_public_': + if self.route_id_changed: + self.updates_cmd.append( + 'ospf %s router-id %s vpn-instance %s' % (self.ospf, self.route_id, self.vrf)) + else: + self.updates_cmd.append( + 'ospf %s vpn-instance %s ' % (self.ospf, self.vrf)) + else: + if self.route_id_changed: + self.updates_cmd.append( + 'ospf %s router-id %s' % (self.ospf, self.route_id)) + else: + if self.route_id_changed: + if self.vrf != '_public_': + self.updates_cmd.append( + 'ospf %s router-id %s vpn-instance %s' % (self.ospf, self.route_id, self.get_exist_vrf())) + else: + self.updates_cmd.append( + 'ospf %s router-id %s' % (self.ospf, self.route_id)) + else: + if self.route_id_changed: + self.updates_cmd.append('undo ospf %s' % self.ospf) + return + + self.updates_cmd.append('ospf %s' % self.ospf) + + if self.description: + if self.state == 'present': + if self.description_changed: + self.updates_cmd.append( + 'description %s' % self.description) + else: + if self.description_changed: + self.updates_cmd.append('undo description') + if self.bandwidth_changed: + if self.state == 'present': + if self.get_exist_bandwidth() != '100': + self.updates_cmd.append( + 'bandwidth-reference %s' % (self.get_exist_bandwidth())) + else: + self.updates_cmd.append('undo bandwidth-reference') + if self.lsaalflag is True: + if self.lsa_arrival_changed: + if self.state == 'present': + self.updates_cmd.append( + 'lsa-arrival-interval %s' % (self.get_exist_lsa_a_interval())) + else: + self.updates_cmd.append( + 'undo lsa-arrival-interval') + + if self.lsaalflag is False: + if self.lsa_arrival_changed: + if self.state == 'present': + if self.get_exist_lsa_a_max_interval() != '1000' \ + or self.get_exist_lsa_a_start_interval() != '500'\ + or self.get_exist_lsa_a_hold_interval() != '500': + self.updates_cmd.append('lsa-arrival-interval intelligent-timer %s %s %s' + % (self.get_exist_lsa_a_max_interval(), + self.get_exist_lsa_a_start_interval(), + self.get_exist_lsa_a_hold_interval())) + else: + if self.get_exist_lsa_a_max_interval() == '1000' \ + and self.get_exist_lsa_a_start_interval() == '500'\ + and self.get_exist_lsa_a_hold_interval() == '500': + self.updates_cmd.append( + 'undo lsa-arrival-interval') + if self.lsaointervalflag is False: + if self.lsa_originate_changed: + if self.state == 'present': + if self.getexistlsaointerval() != '5' \ + or self.getexistlsaomaxinterval() != '5000' \ + or self.getexistlsaostartinterval() != '500' \ + or self.getexistlsaoholdinterval() != '1000': + self.updates_cmd.append('lsa-originate-interval other-type %s intelligent-timer %s %s %s' + % (self.getexistlsaointerval(), + self.getexistlsaomaxinterval(), + self.getexistlsaostartinterval(), + self.getexistlsaoholdinterval())) + else: + self.updates_cmd.append( + 'undo lsa-originate-interval') + if self.lsaointervalflag is True: + if self.lsa_originate_changed: + if self.state == 'present': + self.updates_cmd.append('lsa-originate-interval 0 ') + else: + self.updates_cmd.append( + 'undo lsa-originate-interval') + if self.spfintervaltype == 'millisecond': + if self.spf_changed: + if self.state == 'present': + self.updates_cmd.append( + 'spf-schedule-interval millisecond %s' % self.get_exist_spf_milli_interval()) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + if self.spfintervaltype == 'timer': + if self.spf_changed: + if self.state == 'present': + self.updates_cmd.append( + 'spf-schedule-interval %s' % self.get_exist_spf_interval()) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + if self.spfintervaltype == 'intelligent-timer': + if self.spf_changed: + if self.state == 'present': + if self.get_exist_spf_max_interval() != '5000' \ + or self.get_exist_spf_start_interval() != '50' \ + or self.get_exist_spf_hold_interval() != '200': + self.updates_cmd.append('spf-schedule-interval intelligent-timer %s %s %s' + % (self.get_exist_spf_max_interval(), + self.get_exist_spf_start_interval(), + self.get_exist_spf_hold_interval())) + else: + self.updates_cmd.append( + 'undo spf-schedule-interval') + + def get_end_state(self): + """get end state info""" + + self.get_ospf_info() + self.end_state['ospf_info'] = self.ospf_info["ospfsite"] + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_ospf_info() + self.get_end_state() + self.set_update_cmd() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + ospf=dict(required=True, type='str'), + route_id=dict(required=False, type='str'), + vrf=dict(required=False, type='str', default='_public_'), + description=dict(required=False, type='str'), + bandwidth=dict(required=False, type='str'), + lsaalflag=dict(type='bool', default=False), + lsaainterval=dict(required=False, type='str'), + lsaamaxinterval=dict(required=False, type='str'), + lsaastartinterval=dict(required=False, type='str'), + lsaaholdinterval=dict(required=False, type='str'), + lsaointervalflag=dict(type='bool', default=False), + lsaointerval=dict(required=False, type='str'), + lsaomaxinterval=dict(required=False, type='str'), + lsaostartinterval=dict(required=False, type='str'), + lsaoholdinterval=dict(required=False, type='str'), + spfintervaltype=dict(required=False, default='intelligent-timer', + choices=['intelligent-timer', 'timer', 'millisecond']), + spfinterval=dict(required=False, type='str'), + spfintervalmi=dict(required=False, type='str'), + spfmaxinterval=dict(required=False, type='str'), + spfstartinterval=dict(required=False, type='str'), + spfholdinterval=dict(required=False, type='str'), + state=dict(required=False, choices=['present', 'absent'], default='present'), + ) + + argument_spec.update(ce_argument_spec) + module = OspfVrf(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_reboot.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_reboot.py new file mode 100644 index 00000000..0213a072 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_reboot.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_reboot +short_description: Reboot a HUAWEI CloudEngine switches. +description: + - Reboot a HUAWEI CloudEngine switches. +author: Gong Jianjun (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +requirements: ["ncclient"] +options: + confirm: + description: + - Safeguard boolean. Set to true if you're sure you want to reboot. + type: bool + required: true + save_config: + description: + - Flag indicating whether to save the configuration. + required: false + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Reboot module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Reboot the device + community.network.ce_reboot: + confirm: true + save_config: true + provider: "{{ cli }}" +''' + +RETURN = ''' +rebooted: + description: Whether the device was instructed to reboot. + returned: success + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import execute_nc_action, ce_argument_spec + +try: + from ncclient.operations.errors import TimeoutExpiredError + HAS_NCCLIENT = True +except ImportError: + HAS_NCCLIENT = False + +CE_NC_XML_EXECUTE_REBOOT = """ + + + + %s + + + +""" + + +class Reboot(object): + """ Reboot a network device """ + + def __init__(self, **kwargs): + """ __init___ """ + + self.network_module = None + self.netconf = None + self.init_network_module(**kwargs) + + self.confirm = self.network_module.params['confirm'] + self.save_config = self.network_module.params['save_config'] + + def init_network_module(self, **kwargs): + """ init network module """ + + self.network_module = AnsibleModule(**kwargs) + + def netconf_set_action(self, xml_str): + """ netconf execute action """ + + try: + execute_nc_action(self.network_module, xml_str) + except TimeoutExpiredError: + pass + + def work(self): + """ start to work """ + + if not self.confirm: + self.network_module.fail_json( + msg='Error: Confirm must be set to true for this module to work.') + + xml_str = CE_NC_XML_EXECUTE_REBOOT % str(self.save_config).lower() + self.netconf_set_action(xml_str) + + +def main(): + """ main """ + + argument_spec = dict( + confirm=dict(required=True, type='bool'), + save_config=dict(default=False, type='bool') + ) + + argument_spec.update(ce_argument_spec) + module = Reboot(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_NCCLIENT: + module.network_module.fail_json(msg='Error: The ncclient library is required.') + + changed = False + rebooted = False + + module.work() + + changed = True + rebooted = True + + results = dict() + results['changed'] = changed + results['rebooted'] = rebooted + + module.network_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_rollback.py new file mode 100644 index 00000000..75d82a3e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_rollback.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_rollback +short_description: Set a checkpoint or rollback to a checkpoint on HUAWEI CloudEngine switches. +description: + - This module offers the ability to set a configuration checkpoint + file or rollback to a configuration checkpoint file on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + commit_id: + description: + - Specifies the label of the configuration rollback point to which system configurations are + expected to roll back. + The value is an integer that the system generates automatically. + label: + description: + - Specifies a user label for a configuration rollback point. + The value is a string of 1 to 256 case-sensitive ASCII characters, spaces not supported. + The value must start with a letter and cannot be presented in a single hyphen (-). + filename: + description: + - Specifies a configuration file for configuration rollback. + The value is a string of 5 to 64 case-sensitive characters in the format of *.zip, *.cfg, or *.dat, + spaces not supported. + last: + description: + - Specifies the number of configuration rollback points. + The value is an integer that ranges from 1 to 80. + oldest: + description: + - Specifies the number of configuration rollback points. + The value is an integer that ranges from 1 to 80. + action: + description: + - The operation of configuration rollback. + required: true + choices: ['rollback','clear','set','display','commit'] +''' +EXAMPLES = ''' +- name: Rollback module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + +- name: Ensure commit_id is exist, and specifies the label of the configuration rollback point to + which system configurations are expected to roll back. + community.network.ce_rollback: + commit_id: 1000000748 + action: rollback + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: sometimes + type: dict + sample: {"commit_id": "1000000748", "action": "rollback"} +existing: + description: k/v pairs of existing rollback + returned: sometimes + type: dict + sample: {"commitId": "1000000748", "userLabel": "abc"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["rollback configuration to file a.cfg", + "set configuration commit 1000000783 label ddd", + "clear configuration commit 1000000783 label", + "display configuration commit list"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {"commitId": "1000000748", "userLabel": "abc"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, exec_command, run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList + + +class RollBack(object): + """ + Manages rolls back the system from the current configuration state to a historical configuration state. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + self.commands = list() + # module input info + self.commit_id = self.module.params['commit_id'] + self.label = self.module.params['label'] + self.filename = self.module.params['filename'] + self.last = self.module.params['last'] + self.oldest = self.module.params['oldest'] + self.action = self.module.params['action'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # configuration rollback points info + self.rollback_info = None + self.init_module() + + def init_module(self): + """ init module """ + + required_if = [('action', 'set', ['commit_id', 'label']), ('action', 'commit', ['label'])] + mutually_exclusive = None + required_one_of = None + if self.action == "rollback": + required_one_of = [['commit_id', 'label', 'filename', 'last']] + elif self.action == "clear": + required_one_of = [['commit_id', 'oldest']] + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True, required_if=required_if, mutually_exclusive=mutually_exclusive, required_one_of=required_one_of) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + self.commands.append("return") + self.commands.append("mmi-mode enable") + + if self.action == "commit": + self.commands.append("sys") + + self.commands.append(command) + self.updates_cmd.append(command) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + run_commands(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_rollback_dict(self): + """ get rollback attributes dict.""" + + rollback_info = dict() + rollback_info["RollBackInfos"] = list() + + flags = list() + exp = "commit list" + flags.append(exp) + cfg_info = self.get_config(flags) + if not cfg_info: + return rollback_info + + cfg_line = cfg_info.split("\n") + for cfg in cfg_line: + if re.findall(r'^\d', cfg): + pre_rollback_info = cfg.split() + rollback_info["RollBackInfos"].append(dict(commitId=pre_rollback_info[1], userLabel=pre_rollback_info[2])) + + return rollback_info + + def get_filename_type(self, filename): + """Gets the type of filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Configuration file name include spaces.') + + iftype = None + + if filename.endswith('.cfg'): + iftype = 'cfg' + elif filename.endswith('.zip'): + iftype = 'zip' + elif filename.endswith('.dat'): + iftype = 'dat' + else: + return None + return iftype.lower() + + def set_config(self): + + if self.action == "rollback": + if self.commit_id: + cmd = "rollback configuration to commit-id %s" % self.commit_id + self.cli_add_command(cmd) + if self.label: + cmd = "rollback configuration to label %s" % self.label + self.cli_add_command(cmd) + if self.filename: + cmd = "rollback configuration to file %s" % self.filename + self.cli_add_command(cmd) + if self.last: + cmd = "rollback configuration last %s" % self.last + self.cli_add_command(cmd) + elif self.action == "set": + if self.commit_id and self.label: + cmd = "set configuration commit %s label %s" % (self.commit_id, self.label) + self.cli_add_command(cmd) + elif self.action == "clear": + if self.commit_id: + cmd = "clear configuration commit %s label" % self.commit_id + self.cli_add_command(cmd) + if self.oldest: + cmd = "clear configuration commit oldest %s" % self.oldest + self.cli_add_command(cmd) + elif self.action == "commit": + if self.label: + cmd = "commit label %s" % self.label + self.cli_add_command(cmd) + + elif self.action == "display": + self.rollback_info = self.get_rollback_dict() + if self.commands: + self.commands.append('return') + self.commands.append('undo mmi-mode enable') + self.cli_load_config(self.commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + # commit_id check + rollback_info = self.rollback_info["RollBackInfos"] + if self.commit_id: + if not self.commit_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of commit_id is invalid.') + + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + info_bool = True + if not info_bool: + self.module.fail_json( + msg='Error: The parameter of commit_id is not exist.') + + if self.action == "clear": + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + if info.get("userLabel") == "-": + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: This commit_id does not have a label.') + + # filename check + if self.filename: + if not self.get_filename_type(self.filename): + self.module.fail_json( + msg='Error: Invalid file name or file name extension ( *.cfg, *.zip, *.dat ).') + # last check + if self.last: + if not self.last.isdigit(): + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not digit.') + if int(self.last) <= 0 or int(self.last) > 80: + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not in the range from 1 to 80.') + + # oldest check + if self.oldest: + if not self.oldest.isdigit(): + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not digit.') + if int(self.oldest) <= 0 or int(self.oldest) > 80: + self.module.fail_json( + msg='Error: Number of configuration checkpoints is not in the range from 1 to 80.') + + # label check + if self.label: + if self.label[0].isdigit(): + self.module.fail_json( + msg='Error: Commit label which should not start with a number.') + if len(self.label.replace(' ', '')) == 1: + if self.label == '-': + self.module.fail_json( + msg='Error: Commit label which should not be "-"') + if len(self.label.replace(' ', '')) < 1 or len(self.label) > 256: + self.module.fail_json( + msg='Error: Label of configuration checkpoints is a string of 1 to 256 characters.') + + if self.action == "rollback": + info_bool = False + for info in rollback_info: + if info.get("userLabel") == self.label: + info_bool = True + if not info_bool: + self.module.fail_json( + msg='Error: The parameter of userLabel is not exist.') + + if self.action == "commit": + info_bool = False + for info in rollback_info: + if info.get("userLabel") == self.label: + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: The parameter of userLabel is existing.') + + if self.action == "set": + info_bool = False + for info in rollback_info: + if info.get("commitId") == self.commit_id: + if info.get("userLabel") != "-": + info_bool = True + if info_bool: + self.module.fail_json( + msg='Error: The userLabel of this commitid is present and can be reset after deletion.') + + def get_proposed(self): + """get proposed info""" + + if self.commit_id: + self.proposed["commit_id"] = self.commit_id + if self.label: + self.proposed["label"] = self.label + if self.filename: + self.proposed["filename"] = self.filename + if self.last: + self.proposed["last"] = self.last + if self.oldest: + self.proposed["oldest"] = self.oldest + + def get_existing(self): + """get existing info""" + if not self.rollback_info: + self.existing["RollBackInfos"] = None + else: + self.existing["RollBackInfos"] = self.rollback_info["RollBackInfos"] + + def get_end_state(self): + """get end state info""" + + rollback_info = self.get_rollback_dict() + if not rollback_info: + self.end_state["RollBackInfos"] = None + else: + self.end_state["RollBackInfos"] = rollback_info["RollBackInfos"] + + def work(self): + """worker""" + + self.rollback_info = self.get_rollback_dict() + self.check_params() + self.get_proposed() + + self.set_config() + + self.get_existing() + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + commit_id=dict(required=False), + label=dict(required=False, type='str'), + filename=dict(required=False, type='str'), + last=dict(required=False, type='str'), + oldest=dict(required=False, type='str'), + action=dict(required=False, type='str', choices=[ + 'rollback', 'clear', 'set', 'commit', 'display']), + ) + argument_spec.update(ce_argument_spec) + module = RollBack(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_sflow.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_sflow.py new file mode 100644 index 00000000..166ac0aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_sflow.py @@ -0,0 +1,1169 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_sflow +short_description: Manages sFlow configuration on HUAWEI CloudEngine switches. +description: + - Configure Sampled Flow (sFlow) to monitor traffic on an interface in real time, + detect abnormal traffic, and locate the source of attack traffic, + ensuring stable running of the network. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + agent_ip: + description: + - Specifies the IPv4/IPv6 address of an sFlow agent. + source_ip: + description: + - Specifies the source IPv4/IPv6 address of sFlow packets. + collector_id: + description: + - Specifies the ID of an sFlow collector. This ID is used when you specify + the collector in subsequent sFlow configuration. + choices: ['1', '2'] + collector_ip: + description: + - Specifies the IPv4/IPv6 address of the sFlow collector. + collector_ip_vpn: + description: + - Specifies the name of a VPN instance. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + collector_datagram_size: + description: + - Specifies the maximum length of sFlow packets sent from an sFlow agent to an sFlow collector. + The value is an integer, in bytes. It ranges from 1024 to 8100. The default value is 1400. + collector_udp_port: + description: + - Specifies the UDP destination port number of sFlow packets. + The value is an integer that ranges from 1 to 65535. The default value is 6343. + collector_meth: + description: + - Configures the device to send sFlow packets through service interfaces, + enhancing the sFlow packet forwarding capability. + The enhanced parameter is optional. No matter whether you configure the enhanced mode, + the switch determines to send sFlow packets through service cards or management port + based on the routing information on the collector. + When the value is meth, the device forwards sFlow packets at the control plane. + When the value is enhanced, the device forwards sFlow packets at the forwarding plane to + enhance the sFlow packet forwarding capacity. + choices: ['meth', 'enhanced'] + collector_description: + description: + - Specifies the description of an sFlow collector. + The value is a string of 1 to 255 case-sensitive characters without spaces. + sflow_interface: + description: + - Full name of interface for Flow Sampling or Counter. + It must be a physical interface, Eth-Trunk, or Layer 2 subinterface. + sample_collector: + description: + - Indicates the ID list of the collector. + sample_rate: + description: + - Specifies the flow sampling rate in the format 1/rate. + The value is an integer and ranges from 1 to 4294967295. The default value is 8192. + sample_length: + description: + - Specifies the maximum length of sampled packets. + The value is an integer and ranges from 18 to 512, in bytes. The default value is 128. + sample_direction: + description: + - Enables flow sampling in the inbound or outbound direction. + choices: ['inbound', 'outbound', 'both'] + counter_collector: + description: + - Indicates the ID list of the counter collector. + counter_interval: + description: + - Indicates the counter sampling interval. + The value is an integer that ranges from 10 to 4294967295, in seconds. The default value is 20. + export_route: + description: + - Configures the sFlow packets sent by the switch not to carry routing information. + choices: ['enable', 'disable'] + rate_limit: + description: + - Specifies the rate of sFlow packets sent from a card to the control plane. + The value is an integer that ranges from 100 to 1500, in pps. + type: str + version_added: '0.2.0' + rate_limit_slot: + description: + - Specifies the slot where the rate of output sFlow packets is limited. + If this parameter is not specified, the rate of sFlow packets sent from + all cards to the control plane is limited. + The value is an integer or a string of characters. + type: str + version_added: '0.2.0' + forward_enp_slot: + description: + - Enable the Embedded Network Processor (ENP) chip function. + The switch uses the ENP chip to perform sFlow sampling, + and the maximum sFlow sampling interval is 65535. + If you set the sampling interval to be larger than 65535, + the switch automatically restores it to 65535. + The value is an integer or 'all'. + type: str + version_added: '0.2.0' + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +--- + +- name: Sflow module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Configuring sFlow Agent + community.network.ce_sflow: + agent_ip: 6.6.6.6 + provider: '{{ cli }}' + + - name: Configuring sFlow Collector + community.network.ce_sflow: + collector_id: 1 + collector_ip: 7.7.7.7 + collector_ip_vpn: vpn1 + collector_description: Collector1 + provider: '{{ cli }}' + + - name: Configure flow sampling. + community.network.ce_sflow: + sflow_interface: 10GE2/0/2 + sample_collector: 1 + sample_direction: inbound + provider: '{{ cli }}' + + - name: Configure counter sampling. + community.network.ce_sflow: + sflow_interface: 10GE2/0/2 + counter_collector: 1 + counter_interval: 1000 + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"agent_ip": "6.6.6.6", "state": "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"agent": {}} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"agent": {"family": "ipv4", "ipv4Addr": "1.2.3.4", "ipv6Addr": null}} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["sflow agent ip 6.6.6.6"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr + +CE_NC_GET_SFLOW = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %s + + + + + + + + + %s + + + + + + + + + + + +""" + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_ip_vpn(vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + return False + + if len(vpname) < 1 or len(vpname) > 31: + return False + + return True + + +def get_ip_version(address): + """get ip version fast""" + + if not address: + return None + + if address.count(':') >= 2 and address.count(":") <= 7: + return "ipv6" + elif address.count('.') == 3: + return "ipv4" + else: + return None + + +def get_interface_type(interface): + """get the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class Sflow(object): + """Manages sFlow""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.agent_ip = self.module.params['agent_ip'] + self.agent_version = None + self.source_ip = self.module.params['source_ip'] + self.source_version = None + self.export_route = self.module.params['export_route'] + self.rate_limit = self.module.params['rate_limit'] + self.rate_limit_slot = self.module.params['rate_limit_slot'] + self.forward_enp_slot = self.module.params['forward_enp_slot'] + self.collector_id = self.module.params['collector_id'] + self.collector_ip = self.module.params['collector_ip'] + self.collector_version = None + self.collector_ip_vpn = self.module.params['collector_ip_vpn'] + self.collector_datagram_size = self.module.params['collector_datagram_size'] + self.collector_udp_port = self.module.params['collector_udp_port'] + self.collector_meth = self.module.params['collector_meth'] + self.collector_description = self.module.params['collector_description'] + self.sflow_interface = self.module.params['sflow_interface'] + self.sample_collector = self.module.params['sample_collector'] or list() + self.sample_rate = self.module.params['sample_rate'] + self.sample_length = self.module.params['sample_length'] + self.sample_direction = self.module.params['sample_direction'] + self.counter_collector = self.module.params['counter_collector'] or list() + self.counter_interval = self.module.params['counter_interval'] + self.state = self.module.params['state'] + + # state + self.config = "" # current config + self.sflow_dict = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [("collector_id", "collector_ip")] + self.module = AnsibleModule( + argument_spec=self.spec, required_together=required_together, supports_check_mode=True) + + def check_response(self, con_obj, xml_name): + """Check if response message is already succeed""" + + xml_str = con_obj.xml + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def netconf_set_config(self, xml_str, xml_name): + """netconf set config""" + + rcv_xml = set_nc_config(self.module, xml_str) + if "" not in rcv_xml: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_sflow_dict(self): + """ sflow config dict""" + + sflow_dict = dict(source=list(), agent=dict(), collector=list(), + sampling=dict(), counter=dict(), export=dict()) + conf_str = CE_NC_GET_SFLOW % ( + self.sflow_interface, self.sflow_interface) + + if not self.collector_meth: + conf_str = conf_str.replace("", "") + + rcv_xml = get_nc_config(self.module, conf_str) + + if "" in rcv_xml: + return sflow_dict + + xml_str = rcv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + + # get source info + srcs = root.findall("sflow/sources/source") + if srcs: + for src in srcs: + attrs = dict() + for attr in src: + if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]: + attrs[attr.tag] = attr.text + sflow_dict["source"].append(attrs) + + # get agent info + agent = root.find("sflow/agents/agent") + if agent: + for attr in agent: + if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]: + sflow_dict["agent"][attr.tag] = attr.text + + # get collector info + collectors = root.findall("sflow/collectors/collector") + if collectors: + for collector in collectors: + attrs = dict() + for attr in collector: + if attr.tag in ["collectorID", "family", "ipv4Addr", "ipv6Addr", + "vrfName", "datagramSize", "port", "description", "meth"]: + attrs[attr.tag] = attr.text + sflow_dict["collector"].append(attrs) + + # get sampling info + sample = root.find("sflow/samplings/sampling") + if sample: + for attr in sample: + if attr.tag in ["ifName", "collectorID", "direction", "length", "rate"]: + sflow_dict["sampling"][attr.tag] = attr.text + + # get counter info + counter = root.find("sflow/counters/counter") + if counter: + for attr in counter: + if attr.tag in ["ifName", "collectorID", "interval"]: + sflow_dict["counter"][attr.tag] = attr.text + + # get export info + export = root.find("sflow/exports/export") + if export: + for attr in export: + if attr.tag == "ExportRoute": + sflow_dict["export"][attr.tag] = attr.text + + return sflow_dict + + def config_agent(self): + """configures sFlow agent""" + + xml_str = '' + if not self.agent_ip: + return xml_str + + self.agent_version = get_ip_version(self.agent_ip) + if not self.agent_version: + self.module.fail_json(msg="Error: agent_ip is invalid.") + + if self.state == "present": + if self.agent_ip != self.sflow_dict["agent"].get("ipv4Addr") \ + and self.agent_ip != self.sflow_dict["agent"].get("ipv6Addr"): + xml_str += '' + xml_str += '%s' % self.agent_version + if self.agent_version == "ipv4": + xml_str += '%s' % self.agent_ip + self.updates_cmd.append("sflow agent ip %s" % self.agent_ip) + else: + xml_str += '%s' % self.agent_ip + self.updates_cmd.append("sflow agent ipv6 %s" % self.agent_ip) + xml_str += '' + + else: + if self.agent_ip == self.sflow_dict["agent"].get("ipv4Addr") \ + or self.agent_ip == self.sflow_dict["agent"].get("ipv6Addr"): + xml_str += '' + self.updates_cmd.append("undo sflow agent") + + return xml_str + + def config_source(self): + """configures the source IP address for sFlow packets""" + + xml_str = '' + if not self.source_ip: + return xml_str + + self.source_version = get_ip_version(self.source_ip) + if not self.source_version: + self.module.fail_json(msg="Error: source_ip is invalid.") + + src_dict = dict() + for src in self.sflow_dict["source"]: + if src.get("family") == self.source_version: + src_dict = src + break + + if self.state == "present": + if self.source_ip != src_dict.get("ipv4Addr") \ + and self.source_ip != src_dict.get("ipv6Addr"): + xml_str += '' + xml_str += '%s' % self.source_version + if self.source_version == "ipv4": + xml_str += '%s' % self.source_ip + self.updates_cmd.append("sflow source ip %s" % self.source_ip) + else: + xml_str += '%s' % self.source_ip + self.updates_cmd.append( + "sflow source ipv6 %s" % self.source_ip) + xml_str += '' + else: + if self.source_ip == src_dict.get("ipv4Addr"): + xml_str += 'ipv4' + self.updates_cmd.append("undo sflow source ip %s" % self.source_ip) + elif self.source_ip == src_dict.get("ipv6Addr"): + xml_str += 'ipv6' + self.updates_cmd.append("undo sflow source ipv6 %s" % self.source_ip) + + return xml_str + + def config_collector(self): + """creates an sFlow collector and sets or modifies optional parameters for the sFlow collector""" + + xml_str = '' + if not self.collector_id: + return xml_str + + if self.state == "present" and not self.collector_ip: + return xml_str + + if self.collector_ip: + self.collector_version = get_ip_version(self.collector_ip) + if not self.collector_version: + self.module.fail_json(msg="Error: collector_ip is invalid.") + + # get collector dict + exist_dict = dict() + for collector in self.sflow_dict["collector"]: + if collector.get("collectorID") == self.collector_id: + exist_dict = collector + break + + change = False + if self.state == "present": + if not exist_dict: + change = True + elif self.collector_version != exist_dict.get("family"): + change = True + elif self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"): + change = True + elif self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"): + change = True + elif self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"): + change = True + elif not self.collector_ip_vpn and exist_dict.get("vrfName") != "_public_": + change = True + elif self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"): + change = True + elif not self.collector_udp_port and exist_dict.get("port") != "6343": + change = True + elif self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"): + change = True + elif not self.collector_datagram_size and exist_dict.get("datagramSize") != "1400": + change = True + elif self.collector_meth and self.collector_meth != exist_dict.get("meth"): + change = True + elif not self.collector_meth and exist_dict.get("meth") and exist_dict.get("meth") != "meth": + change = True + elif self.collector_description and self.collector_description != exist_dict.get("description"): + change = True + elif not self.collector_description and exist_dict.get("description"): + change = True + else: + pass + else: # absent + # collector not exist + if not exist_dict: + return xml_str + if self.collector_version and self.collector_version != exist_dict.get("family"): + return xml_str + if self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"): + return xml_str + if self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"): + return xml_str + if self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"): + return xml_str + if self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"): + return xml_str + if self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"): + return xml_str + if self.collector_meth and self.collector_meth != exist_dict.get("meth"): + return xml_str + if self.collector_description and self.collector_description != exist_dict.get("description"): + return xml_str + change = True + + if not change: + return xml_str + + # update or delete + if self.state == "absent": + xml_str += '%s' % self.collector_id + self.updates_cmd.append("undo collector %s" % self.collector_id) + else: + xml_str += '%s' % self.collector_id + cmd = "sflow collector %s" % self.collector_id + xml_str += '%s' % self.collector_version + if self.collector_version == "ipv4": + cmd += " ip %s" % self.collector_ip + xml_str += '%s' % self.collector_ip + else: + cmd += " ipv6 %s" % self.collector_ip + xml_str += '%s' % self.collector_ip + if self.collector_ip_vpn: + cmd += " vpn-instance %s" % self.collector_ip_vpn + xml_str += '%s' % self.collector_ip_vpn + if self.collector_datagram_size: + cmd += " length %s" % self.collector_datagram_size + xml_str += '%s' % self.collector_datagram_size + if self.collector_udp_port: + cmd += " udp-port %s" % self.collector_udp_port + xml_str += '%s' % self.collector_udp_port + if self.collector_description: + cmd += " description %s" % self.collector_description + xml_str += '%s' % self.collector_description + else: + xml_str += '' + if self.collector_meth: + if self.collector_meth == "enhanced": + cmd += " enhanced" + xml_str += '%s' % self.collector_meth + self.updates_cmd.append(cmd) + + xml_str += "" + + return xml_str + + def config_sampling(self): + """configure sflow sampling on an interface""" + + xml_str = '' + if not self.sflow_interface: + return xml_str + + if not self.sflow_dict["sampling"] and self.state == "absent": + return xml_str + + self.updates_cmd.append("interface %s" % self.sflow_interface) + if self.state == "present": + xml_str += '%s' % self.sflow_interface + else: + xml_str += '%s' % self.sflow_interface + + # sample_collector + if self.sample_collector: + if self.sflow_dict["sampling"].get("collectorID") \ + and self.sflow_dict["sampling"].get("collectorID") != "invalid": + existing = self.sflow_dict["sampling"].get("collectorID").split(',') + else: + existing = list() + + if self.state == "present": + diff = list(set(self.sample_collector) - set(existing)) + if diff: + self.updates_cmd.append( + "sflow sampling collector %s" % ' '.join(diff)) + new_set = list(self.sample_collector + existing) + xml_str += '%s' % ','.join(list(set(new_set))) + else: + same = list(set(self.sample_collector) & set(existing)) + if same: + self.updates_cmd.append( + "undo sflow sampling collector %s" % ' '.join(same)) + xml_str += '%s' % ','.join(list(set(same))) + + # sample_rate + if self.sample_rate: + exist = bool(self.sample_rate == self.sflow_dict["sampling"].get("rate")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow sampling rate %s" % self.sample_rate) + xml_str += '%s' % self.sample_rate + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow sampling rate %s" % self.sample_rate) + xml_str += '%s' % self.sample_rate + + # sample_length + if self.sample_length: + exist = bool(self.sample_length == self.sflow_dict["sampling"].get("length")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow sampling length %s" % self.sample_length) + xml_str += '%s' % self.sample_length + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow sampling length %s" % self.sample_length) + xml_str += '%s' % self.sample_length + + # sample_direction + if self.sample_direction: + direction = list() + if self.sample_direction == "both": + direction = ["inbound", "outbound"] + else: + direction.append(self.sample_direction) + existing = list() + if self.sflow_dict["sampling"].get("direction"): + if self.sflow_dict["sampling"].get("direction") == "both": + existing = ["inbound", "outbound"] + else: + existing.append( + self.sflow_dict["sampling"].get("direction")) + + if self.state == "present": + diff = list(set(direction) - set(existing)) + if diff: + new_set = list(set(direction + existing)) + self.updates_cmd.append( + "sflow sampling %s" % ' '.join(diff)) + if len(new_set) > 1: + new_dir = "both" + else: + new_dir = new_set[0] + xml_str += '%s' % new_dir + else: + same = list(set(existing) & set(direction)) + if same: + self.updates_cmd.append("undo sflow sampling %s" % ' '.join(same)) + if len(same) > 1: + del_dir = "both" + else: + del_dir = same[0] + xml_str += '%s' % del_dir + + if xml_str.endswith(""): + self.updates_cmd.pop() + return "" + + xml_str += '' + + return xml_str + + def config_counter(self): + """configures sflow counter on an interface""" + + xml_str = '' + if not self.sflow_interface: + return xml_str + + if not self.sflow_dict["counter"] and self.state == "absent": + return xml_str + + self.updates_cmd.append("interface %s" % self.sflow_interface) + if self.state == "present": + xml_str += '%s' % self.sflow_interface + else: + xml_str += '%s' % self.sflow_interface + + # counter_collector + if self.counter_collector: + if self.sflow_dict["counter"].get("collectorID") \ + and self.sflow_dict["counter"].get("collectorID") != "invalid": + existing = self.sflow_dict["counter"].get("collectorID").split(',') + else: + existing = list() + + if self.state == "present": + diff = list(set(self.counter_collector) - set(existing)) + if diff: + self.updates_cmd.append("sflow counter collector %s" % ' '.join(diff)) + new_set = list(self.counter_collector + existing) + xml_str += '%s' % ','.join(list(set(new_set))) + else: + same = list(set(self.counter_collector) & set(existing)) + if same: + self.updates_cmd.append( + "undo sflow counter collector %s" % ' '.join(same)) + xml_str += '%s' % ','.join(list(set(same))) + + # counter_interval + if self.counter_interval: + exist = bool(self.counter_interval == self.sflow_dict["counter"].get("interval")) + if self.state == "present" and not exist: + self.updates_cmd.append( + "sflow counter interval %s" % self.counter_interval) + xml_str += '%s' % self.counter_interval + elif self.state == "absent" and exist: + self.updates_cmd.append( + "undo sflow counter interval %s" % self.counter_interval) + xml_str += '%s' % self.counter_interval + + if xml_str.endswith(""): + self.updates_cmd.pop() + return "" + + xml_str += '' + + return xml_str + + def config_export(self): + """configure sflow export""" + + xml_str = '' + if not self.export_route: + return xml_str + + if self.export_route == "enable": + if self.sflow_dict["export"] and self.sflow_dict["export"].get("ExportRoute") == "disable": + xml_str = 'disable' + self.updates_cmd.append("undo sflow export extended-route-data disable") + else: # disable + if not self.sflow_dict["export"] or self.sflow_dict["export"].get("ExportRoute") != "disable": + xml_str = 'disable' + self.updates_cmd.append("sflow export extended-route-data disable") + + return xml_str + + def netconf_load_config(self, xml_str): + """load sflow config by netconf""" + + if not xml_str: + return + + xml_cfg = """ + + + %s + + """ % xml_str + + self.netconf_set_config(xml_cfg, "SET_SFLOW") + self.changed = True + + def check_params(self): + """Check all input params""" + + # check agent_ip + if self.agent_ip: + self.agent_ip = self.agent_ip.upper() + if not check_ip_addr(self.agent_ip): + self.module.fail_json(msg="Error: agent_ip is invalid.") + + # check source_ip + if self.source_ip: + self.source_ip = self.source_ip.upper() + if not check_ip_addr(self.source_ip): + self.module.fail_json(msg="Error: source_ip is invalid.") + + # check collector + if self.collector_id: + # check collector_ip and collector_ip_vpn + if self.collector_ip: + self.collector_ip = self.collector_ip.upper() + if not check_ip_addr(self.collector_ip): + self.module.fail_json( + msg="Error: collector_ip is invalid.") + if self.collector_ip_vpn and not is_valid_ip_vpn(self.collector_ip_vpn): + self.module.fail_json( + msg="Error: collector_ip_vpn is invalid.") + + # check collector_datagram_size ranges from 1024 to 8100 + if self.collector_datagram_size: + if not self.collector_datagram_size.isdigit(): + self.module.fail_json( + msg="Error: collector_datagram_size is not digit.") + if int(self.collector_datagram_size) < 1024 or int(self.collector_datagram_size) > 8100: + self.module.fail_json( + msg="Error: collector_datagram_size is not ranges from 1024 to 8100.") + + # check collector_udp_port ranges from 1 to 65535 + if self.collector_udp_port: + if not self.collector_udp_port.isdigit(): + self.module.fail_json( + msg="Error: collector_udp_port is not digit.") + if int(self.collector_udp_port) < 1 or int(self.collector_udp_port) > 65535: + self.module.fail_json( + msg="Error: collector_udp_port is not ranges from 1 to 65535.") + + # check collector_description 1 to 255 case-sensitive characters + if self.collector_description: + if self.collector_description.count(" "): + self.module.fail_json( + msg="Error: collector_description should without spaces.") + if len(self.collector_description) < 1 or len(self.collector_description) > 255: + self.module.fail_json( + msg="Error: collector_description is not ranges from 1 to 255.") + + # check sflow_interface + if self.sflow_interface: + intf_type = get_interface_type(self.sflow_interface) + if not intf_type: + self.module.fail_json(msg="Error: intf_type is invalid.") + if intf_type not in ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'eth-trunk']: + self.module.fail_json( + msg="Error: interface %s is not support sFlow." % self.sflow_interface) + + # check sample_collector + if self.sample_collector: + self.sample_collector.sort() + if self.sample_collector not in [["1"], ["2"], ["1", "2"]]: + self.module.fail_json( + msg="Error: sample_collector is invalid.") + + # check sample_rate ranges from 1 to 4294967295 + if self.sample_rate: + if not self.sample_rate.isdigit(): + self.module.fail_json( + msg="Error: sample_rate is not digit.") + if int(self.sample_rate) < 1 or int(self.sample_rate) > 4294967295: + self.module.fail_json( + msg="Error: sample_rate is not ranges from 1 to 4294967295.") + + # check sample_length ranges from 18 to 512 + if self.sample_length: + if not self.sample_length.isdigit(): + self.module.fail_json( + msg="Error: sample_rate is not digit.") + if int(self.sample_length) < 18 or int(self.sample_length) > 512: + self.module.fail_json( + msg="Error: sample_length is not ranges from 18 to 512.") + + # check counter_collector + if self.counter_collector: + self.counter_collector.sort() + if self.counter_collector not in [["1"], ["2"], ["1", "2"]]: + self.module.fail_json( + msg="Error: counter_collector is invalid.") + + # counter_interval ranges from 10 to 4294967295 + if self.counter_interval: + if not self.counter_interval.isdigit(): + self.module.fail_json( + msg="Error: counter_interval is not digit.") + if int(self.counter_interval) < 10 or int(self.counter_interval) > 4294967295: + self.module.fail_json( + msg="Error: sample_length is not ranges from 10 to 4294967295.") + + if self.rate_limit or self.rate_limit_slot or self.forward_enp_slot: + self.module.fail_json(msg="Error: The following parameters cannot be configured" + "because XML mode is not supported:rate_limit,rate_limit_slot,forward_enp_slot.") + + def get_proposed(self): + """get proposed info""" + + # base config + if self.agent_ip: + self.proposed["agent_ip"] = self.agent_ip + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.export_route: + self.proposed["export_route"] = self.export_route + if self.rate_limit: + self.proposed["rate_limit"] = self.rate_limit + self.proposed["rate_limit_slot"] = self.rate_limit_slot + if self.forward_enp_slot: + self.proposed["forward_enp_slot"] = self.forward_enp_slot + if self.collector_id: + self.proposed["collector_id"] = self.collector_id + if self.collector_ip: + self.proposed["collector_ip"] = self.collector_ip + self.proposed["collector_ip_vpn"] = self.collector_ip_vpn + if self.collector_datagram_size: + self.proposed[ + "collector_datagram_size"] = self.collector_datagram_size + if self.collector_udp_port: + self.proposed["collector_udp_port"] = self.collector_udp_port + if self.collector_meth: + self.proposed["collector_meth"] = self.collector_meth + if self.collector_description: + self.proposed[ + "collector_description"] = self.collector_description + + # sample and counter config + if self.sflow_interface: + self.proposed["sflow_interface"] = self.sflow_interface + if self.sample_collector: + self.proposed["sample_collector"] = self.sample_collector + if self.sample_rate: + self.proposed["sample_rate"] = self.sample_rate + if self.sample_length: + self.proposed["sample_length"] = self.sample_length + if self.sample_direction: + self.proposed["sample_direction"] = self.sample_direction + if self.counter_collector: + self.proposed["counter_collector"] = self.counter_collector + if self.counter_interval: + self.proposed["counter_interval"] = self.counter_interval + + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.sflow_dict: + return + + if self.agent_ip: + self.existing["agent"] = self.sflow_dict["agent"] + if self.source_ip: + self.existing["source"] = self.sflow_dict["source"] + if self.collector_id: + self.existing["collector"] = self.sflow_dict["collector"] + if self.export_route: + self.existing["export"] = self.sflow_dict["export"] + + if self.sflow_interface: + self.existing["sampling"] = self.sflow_dict["sampling"] + self.existing["counter"] = self.sflow_dict["counter"] + + def get_end_state(self): + """get end state info""" + + sflow_dict = self.get_sflow_dict() + if not sflow_dict: + return + + if self.agent_ip: + self.end_state["agent"] = sflow_dict["agent"] + if self.source_ip: + self.end_state["source"] = sflow_dict["source"] + if self.collector_id: + self.end_state["collector"] = sflow_dict["collector"] + if self.export_route: + self.end_state["export"] = sflow_dict["export"] + + if self.sflow_interface: + self.end_state["sampling"] = sflow_dict["sampling"] + self.end_state["counter"] = sflow_dict["counter"] + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.sflow_dict = self.get_sflow_dict() + self.get_existing() + self.get_proposed() + + # deal present or absent + xml_str = '' + if self.export_route: + xml_str += self.config_export() + if self.agent_ip: + xml_str += self.config_agent() + if self.source_ip: + xml_str += self.config_source() + + if self.state == "present": + if self.collector_id and self.collector_ip: + xml_str += self.config_collector() + if self.sflow_interface: + xml_str += self.config_sampling() + xml_str += self.config_counter() + else: + if self.sflow_interface: + xml_str += self.config_sampling() + xml_str += self.config_counter() + if self.collector_id: + xml_str += self.config_collector() + + if xml_str: + self.netconf_load_config(xml_str) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + agent_ip=dict(required=False, type='str'), + source_ip=dict(required=False, type='str'), + export_route=dict(required=False, type='str', + choices=['enable', 'disable']), + rate_limit=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + rate_limit_slot=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + forward_enp_slot=dict(required=False, removed_in_version='3.0.0', # was Ansible 2.13 + removed_from_collection='community.network', type='str'), + collector_id=dict(required=False, type='str', choices=['1', '2']), + collector_ip=dict(required=False, type='str'), + collector_ip_vpn=dict(required=False, type='str'), + collector_datagram_size=dict(required=False, type='str'), + collector_udp_port=dict(required=False, type='str'), + collector_meth=dict(required=False, type='str', + choices=['meth', 'enhanced']), + collector_description=dict(required=False, type='str'), + sflow_interface=dict(required=False, type='str'), + sample_collector=dict(required=False, type='list'), + sample_rate=dict(required=False, type='str'), + sample_length=dict(required=False, type='str'), + sample_direction=dict(required=False, type='str', + choices=['inbound', 'outbound', 'both']), + counter_collector=dict(required=False, type='list'), + counter_interval=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = Sflow(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_community.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_community.py new file mode 100644 index 00000000..08704ba9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_community.py @@ -0,0 +1,975 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_community +short_description: Manages SNMP community configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP community configuration on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_number: + description: + - Access control list number. + community_name: + description: + - Unique name to identify the community. + access_right: + description: + - Access right read or write. + choices: ['read','write'] + community_mib_view: + description: + - Mib view name. + group_name: + description: + - Unique name to identify the SNMPv3 group. + security_level: + description: + - Security level indicating whether to use authentication and encryption. + choices: ['noAuthNoPriv', 'authentication', 'privacy'] + read_view: + description: + - Mib view name for read. + write_view: + description: + - Mib view name for write. + notify_view: + description: + - Mib view name for notification. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp community test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP community" + community.network.ce_snmp_community: + state: present + community_name: Wdz123456789 + access_right: write + provider: "{{ cli }}" + + - name: "Undo SNMP community" + community.network.ce_snmp_community: + state: absent + community_name: Wdz123456789 + access_right: write + provider: "{{ cli }}" + + - name: "Config SNMP group" + community.network.ce_snmp_community: + state: present + group_name: wdz_group + security_level: noAuthNoPriv + acl_number: 2000 + provider: "{{ cli }}" + + - name: "Undo SNMP group" + community.network.ce_snmp_community: + state: absent + group_name: wdz_group + security_level: noAuthNoPriv + acl_number: 2000 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_number": "2000", "group_name": "wdz_group", + "security_level": "noAuthNoPriv", "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp v3 group": {"snmp_group": ["wdz_group", "noAuthNoPriv", "2000"]}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent group v3 wdz_group noauthentication acl 2000"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +# get snmp community +CE_GET_SNMP_COMMUNITY_HEADER = """ + + + + + + +""" +CE_GET_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# merge snmp community +CE_MERGE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_MERGE_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# create snmp community +CE_CREATE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_CREATE_SNMP_COMMUNITY_TAIL = """ + + + + +""" +# delete snmp community +CE_DELETE_SNMP_COMMUNITY_HEADER = """ + + + + + %s + %s +""" +CE_DELETE_SNMP_COMMUNITY_TAIL = """ + + + + +""" + +# get snmp v3 group +CE_GET_SNMP_V3_GROUP_HEADER = """ + + + + + + +""" +CE_GET_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# merge snmp v3 group +CE_MERGE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_MERGE_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# create snmp v3 group +CE_CREATE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_CREATE_SNMP_V3_GROUP_TAIL = """ + + + + +""" +# delete snmp v3 group +CE_DELETE_SNMP_V3_GROUP_HEADER = """ + + + + + %s + %s +""" +CE_DELETE_SNMP_V3_GROUP_TAIL = """ + + + + +""" + + +class SnmpCommunity(object): + """ Manages SNMP community configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure through netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure through netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_snmp_community_args(self, **kwargs): + """ Check snmp community args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + result["community_info"] = [] + state = module.params['state'] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + if community_name and access_right: + if len(community_name) > 32 or len(community_name) == 0: + module.fail_json( + msg='Error: The len of community_name %s is out of [1 - 32].' % community_name) + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The len of acl_number %s is out of [1 - 32] or is invalid.' % acl_number) + + if community_mib_view: + if len(community_mib_view) > 32 or len(community_mib_view) == 0: + module.fail_json( + msg='Error: The len of community_mib_view %s is out of [1 - 32].' % community_mib_view) + + conf_str = CE_GET_SNMP_COMMUNITY_HEADER + if acl_number: + conf_str += "" + if community_mib_view: + conf_str += "" + + conf_str += CE_GET_SNMP_COMMUNITY_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + community_info = root.findall("snmp/communitys/community") + if community_info: + for tmp in community_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["communityName", "accessRight", "aclNumber", "mibViewName"]: + tmp_dict[site.tag] = site.text + + result["community_info"].append(tmp_dict) + + if result["community_info"]: + community_name_list = list() + for tmp in result["community_info"]: + if "communityName" in tmp.keys(): + community_name_list.append(tmp["communityName"]) + + if community_name not in community_name_list: + need_cfg = True + else: + need_cfg_bool = True + + for tmp in result["community_info"]: + if tmp["communityName"] == community_name: + + cfg_bool_list = list() + + if access_right: + if "accessRight" in tmp.keys(): + need_cfg_access = False + if tmp["accessRight"] != access_right: + need_cfg_access = True + else: + need_cfg_access = True + + cfg_bool_list.append(need_cfg_access) + + if acl_number: + if "aclNumber" in tmp.keys(): + need_cfg_acl = False + if tmp["aclNumber"] != acl_number: + need_cfg_acl = True + else: + need_cfg_acl = True + + cfg_bool_list.append(need_cfg_acl) + + if community_mib_view: + if "mibViewName" in tmp.keys(): + need_cfg_mib = False + if tmp["mibViewName"] != community_mib_view: + need_cfg_mib = True + else: + need_cfg_mib = True + cfg_bool_list.append(need_cfg_mib) + + if True not in cfg_bool_list: + need_cfg_bool = False + + if state == "present": + if not need_cfg_bool: + need_cfg = False + else: + need_cfg = True + else: + if not need_cfg_bool: + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def check_snmp_v3_group_args(self, **kwargs): + """ Check snmp v3 group args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + result["group_info"] = [] + state = module.params['state'] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + community_name = module.params['community_name'] + access_right = module.params['access_right'] + + if group_name and security_level: + + if community_name and access_right: + module.fail_json( + msg='Error: Community is used for v1/v2c, group_name is used for v3, do not ' + 'input at the same time.') + + if len(group_name) > 32 or len(group_name) == 0: + module.fail_json( + msg='Error: The len of group_name %s is out of [1 - 32].' % group_name) + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The len of acl_number %s is out of [1 - 32] or is invalid.' % acl_number) + + if read_view: + if len(read_view) > 32 or len(read_view) < 1: + module.fail_json( + msg='Error: The len of read_view %s is out of [1 - 32].' % read_view) + + if write_view: + if len(write_view) > 32 or len(write_view) < 1: + module.fail_json( + msg='Error: The len of write_view %s is out of [1 - 32].' % write_view) + + if notify_view: + if len(notify_view) > 32 or len(notify_view) < 1: + module.fail_json( + msg='Error: The len of notify_view %s is out of [1 - 32].' % notify_view) + + conf_str = CE_GET_SNMP_V3_GROUP_HEADER + if acl_number: + conf_str += "" + if read_view: + conf_str += "" + if write_view: + conf_str += "" + if notify_view: + conf_str += "" + + conf_str += CE_GET_SNMP_V3_GROUP_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + group_info = root.findall("snmp/snmpv3Groups/snmpv3Group") + if group_info: + for tmp in group_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["groupName", "securityLevel", "readViewName", "writeViewName", + "notifyViewName", "aclNumber"]: + tmp_dict[site.tag] = site.text + + result["group_info"].append(tmp_dict) + + if result["group_info"]: + group_name_list = list() + + for tmp in result["group_info"]: + if "groupName" in tmp.keys(): + group_name_list.append(tmp["groupName"]) + if group_name not in group_name_list: + if state == "present": + need_cfg = True + else: + need_cfg = False + else: + need_cfg_bool = True + for tmp in result["group_info"]: + if tmp["groupName"] == group_name: + + cfg_bool_list = list() + + if security_level: + if "securityLevel" in tmp.keys(): + need_cfg_group = False + if tmp["securityLevel"] != security_level: + need_cfg_group = True + else: + need_cfg_group = True + + cfg_bool_list.append(need_cfg_group) + + if acl_number: + if "aclNumber" in tmp.keys(): + need_cfg_acl = False + if tmp["aclNumber"] != acl_number: + need_cfg_acl = True + else: + need_cfg_acl = True + + cfg_bool_list.append(need_cfg_acl) + + if read_view: + if "readViewName" in tmp.keys(): + need_cfg_read = False + if tmp["readViewName"] != read_view: + need_cfg_read = True + else: + need_cfg_read = True + cfg_bool_list.append(need_cfg_read) + + if write_view: + if "writeViewName" in tmp.keys(): + need_cfg_write = False + if tmp["writeViewName"] != write_view: + need_cfg_write = True + else: + need_cfg_write = True + cfg_bool_list.append(need_cfg_write) + + if notify_view: + if "notifyViewName" in tmp.keys(): + need_cfg_notify = False + if tmp["notifyViewName"] != notify_view: + need_cfg_notify = True + else: + need_cfg_notify = True + cfg_bool_list.append(need_cfg_notify) + + if True not in cfg_bool_list: + need_cfg_bool = False + + if state == "present": + if not need_cfg_bool: + need_cfg = False + else: + need_cfg = True + else: + if not need_cfg_bool: + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def merge_snmp_community(self, **kwargs): + """ Merge snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_MERGE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_MERGE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp community failed.') + + community_safe_name = "******" + + cmd = "snmp-agent community %s %s" % (access_right, community_safe_name) + + if acl_number: + cmd += " acl %s" % acl_number + if community_mib_view: + cmd += " mib-view %s" % community_mib_view + + return cmd + + def create_snmp_community(self, **kwargs): + """ Create snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_CREATE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_CREATE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp community failed.') + + community_safe_name = "******" + + cmd = "snmp-agent community %s %s" % (access_right, community_safe_name) + + if acl_number: + cmd += " acl %s" % acl_number + if community_mib_view: + cmd += " mib-view %s" % community_mib_view + + return cmd + + def delete_snmp_community(self, **kwargs): + """ Delete snmp community operation """ + + module = kwargs["module"] + community_name = module.params['community_name'] + access_right = module.params['access_right'] + acl_number = module.params['acl_number'] + community_mib_view = module.params['community_mib_view'] + + conf_str = CE_DELETE_SNMP_COMMUNITY_HEADER % ( + community_name, access_right) + if acl_number: + conf_str += "%s" % acl_number + if community_mib_view: + conf_str += "%s" % community_mib_view + + conf_str += CE_DELETE_SNMP_COMMUNITY_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp community failed.') + + community_safe_name = "******" + cmd = "undo snmp-agent community %s %s" % ( + access_right, community_safe_name) + + return cmd + + def merge_snmp_v3_group(self, **kwargs): + """ Merge snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_MERGE_SNMP_V3_GROUP_HEADER % (group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_MERGE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "snmp-agent group v3 %s %s" % (group_name, security_level_cli) + + if read_view: + cmd += " read-view %s" % read_view + if write_view: + cmd += " write-view %s" % write_view + if notify_view: + cmd += " notify-view %s" % notify_view + if acl_number: + cmd += " acl %s" % acl_number + + return cmd + + def create_snmp_v3_group(self, **kwargs): + """ Create snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_CREATE_SNMP_V3_GROUP_HEADER % ( + group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_CREATE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "snmp-agent group v3 %s %s" % (group_name, security_level_cli) + + if read_view: + cmd += " read-view %s" % read_view + if write_view: + cmd += " write-view %s" % write_view + if notify_view: + cmd += " notify-view %s" % notify_view + if acl_number: + cmd += " acl %s" % acl_number + + return cmd + + def delete_snmp_v3_group(self, **kwargs): + """ Delete snmp v3 group operation """ + + module = kwargs["module"] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + acl_number = module.params['acl_number'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + conf_str = CE_DELETE_SNMP_V3_GROUP_HEADER % ( + group_name, security_level) + if acl_number: + conf_str += "%s" % acl_number + if read_view: + conf_str += "%s" % read_view + if write_view: + conf_str += "%s" % write_view + if notify_view: + conf_str += "%s" % notify_view + conf_str += CE_DELETE_SNMP_V3_GROUP_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 group failed.') + + if security_level == "noAuthNoPriv": + security_level_cli = "noauthentication" + elif security_level == "authentication": + security_level_cli = "authentication" + elif security_level == "privacy": + security_level_cli = "privacy" + + cmd = "undo snmp-agent group v3 %s %s" % ( + group_name, security_level_cli) + + return cmd + + +def main(): + """ main function """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_number=dict(type='str'), + community_name=dict(type='str', no_log=True), + access_right=dict(choices=['read', 'write']), + community_mib_view=dict(type='str'), + group_name=dict(type='str'), + security_level=dict( + choices=['noAuthNoPriv', 'authentication', 'privacy']), + read_view=dict(type='str'), + write_view=dict(type='str'), + notify_view=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + required_together = [("community_name", "access_right"), ("security_level", "group_name")] + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + supports_check_mode=True + ) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + acl_number = module.params['acl_number'] + community_name = module.params['community_name'] + community_mib_view = module.params['community_mib_view'] + access_right = module.params['access_right'] + group_name = module.params['group_name'] + security_level = module.params['security_level'] + read_view = module.params['read_view'] + write_view = module.params['write_view'] + notify_view = module.params['notify_view'] + + snmp_community_obj = SnmpCommunity() + + if not snmp_community_obj: + module.fail_json(msg='Error: Init module failed.') + + snmp_community_rst = snmp_community_obj.check_snmp_community_args( + module=module) + snmp_v3_group_rst = snmp_community_obj.check_snmp_v3_group_args( + module=module) + + # get proposed + proposed["state"] = state + if acl_number: + proposed["acl_number"] = acl_number + if community_name: + proposed["community_name"] = community_name + if community_mib_view: + proposed["community_mib_view"] = community_mib_view + if access_right: + proposed["access_right"] = access_right + if group_name: + proposed["group_name"] = group_name + if security_level: + proposed["security_level"] = security_level + if read_view: + proposed["read_view"] = read_view + if write_view: + proposed["write_view"] = write_view + if notify_view: + proposed["notify_view"] = notify_view + + # state exist snmp community config + exist_tmp = dict() + for item in snmp_community_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_community_rst[item] + + if exist_tmp: + existing["snmp community"] = exist_tmp + # state exist snmp v3 group config + exist_tmp = dict() + for item in snmp_v3_group_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_group_rst[item] + + if exist_tmp: + existing["snmp v3 group"] = exist_tmp + + if state == "present": + if snmp_community_rst["need_cfg"]: + if len(snmp_community_rst["community_info"]) != 0: + cmd = snmp_community_obj.merge_snmp_community(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_community_obj.create_snmp_community(module=module) + changed = True + updates.append(cmd) + + if snmp_v3_group_rst["need_cfg"]: + if len(snmp_v3_group_rst["group_info"]): + cmd = snmp_community_obj.merge_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_community_obj.create_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + + else: + if snmp_community_rst["need_cfg"]: + cmd = snmp_community_obj.delete_snmp_community(module=module) + changed = True + updates.append(cmd) + if snmp_v3_group_rst["need_cfg"]: + cmd = snmp_community_obj.delete_snmp_v3_group(module=module) + changed = True + updates.append(cmd) + + # state end snmp community config + snmp_community_rst = snmp_community_obj.check_snmp_community_args( + module=module) + end_tmp = dict() + for item in snmp_community_rst: + if item != "need_cfg": + end_tmp[item] = snmp_community_rst[item] + end_tmp[item] = snmp_community_rst[item] + if end_tmp: + end_state["snmp community"] = end_tmp + # state end snmp v3 group config + snmp_v3_group_rst = snmp_community_obj.check_snmp_v3_group_args( + module=module) + end_tmp = dict() + for item in snmp_v3_group_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_group_rst[item] + if end_tmp: + end_state["snmp v3 group"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_contact.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_contact.py new file mode 100644 index 00000000..873a2aa6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_contact.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_contact +short_description: Manages SNMP contact configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP contact configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + contact: + description: + - Contact information. + required: true + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp contact test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP contact" + community.network.ce_snmp_contact: + state: present + contact: call Operator at 010-99999999 + provider: "{{ cli }}" + + - name: "Undo SNMP contact" + community.network.ce_snmp_contact: + state: absent + contact: call Operator at 010-99999999 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"contact": "call Operator at 010-99999999", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"contact": "call Operator at 010-99999999"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent sys-info contact call Operator at 010-99999999"] +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +class SnmpContact(object): + """ Manages SNMP contact configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + + # module args + self.state = self.module.params['state'] + self.contact = self.module.params['contact'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check invalid args """ + + if self.contact: + if len(self.contact) > 255 or len(self.contact) < 1: + self.module.fail_json( + msg='Error: The len of contact %s is out of [1 - 255].' % self.contact) + else: + self.module.fail_json( + msg='Error: The len of contact is 0.') + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.contact: + self.proposed["contact"] = self.contact + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"contact ") + if len(temp_data) > 1: + self.cur_cfg["contact"] = temp_data[1] + self.existing["contact"] = temp_data[1] + + def get_end_state(self): + """ Get end state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"contact ") + if len(temp_data) > 1: + self.end_state["contact"] = temp_data[1] + + def cli_load_config(self, commands): + """ Load configure by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get configure by cli """ + + regular = "| include snmp | include contact" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_config(self): + """ Set configure by cli """ + + cmd = "snmp-agent sys-info contact %s" % self.contact + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config(self): + """ Undo configure by cli """ + + cmd = "undo snmp-agent sys-info contact" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Main work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if "contact" in self.cur_cfg.keys() and self.contact == self.cur_cfg["contact"]: + pass + else: + self.set_config() + else: + if "contact" in self.cur_cfg.keys() and self.contact == self.cur_cfg["contact"]: + self.undo_config() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + contact=dict(type='str', required=True) + ) + + argument_spec.update(ce_argument_spec) + module = SnmpContact(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_location.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_location.py new file mode 100644 index 00000000..ad19f568 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_location.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_location +short_description: Manages SNMP location configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP location configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + location: + description: + - Location information. + required: true + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp location test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP location" + community.network.ce_snmp_location: + state: present + location: nanjing China + provider: "{{ cli }}" + + - name: "Remove SNMP location" + community.network.ce_snmp_location: + state: absent + location: nanjing China + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"location": "nanjing China", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"location": "nanjing China"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent sys-info location nanjing China"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +class SnmpLocation(object): + """ Manages SNMP location configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + + # module args + self.state = self.module.params['state'] + self.location = self.module.params['location'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def check_args(self): + """ Check invalid args """ + + if self.location: + if len(self.location) > 255 or len(self.location) < 1: + self.module.fail_json( + msg='Error: The len of location %s is out of [1 - 255].' % self.location) + else: + self.module.fail_json( + msg='Error: The len of location is 0.') + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.location: + self.proposed["location"] = self.location + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"location ") + if len(temp_data) > 1: + self.cur_cfg["location"] = temp_data[1] + self.existing["location"] = temp_data[1] + + def get_end_state(self): + """ Get end state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_data = tmp_cfg.split(r"location ") + if len(temp_data) > 1: + self.end_state["location"] = temp_data[1] + + def cli_load_config(self, commands): + """ Load config by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get config by cli """ + + regular = "| include snmp | include location" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_config(self): + """ Set configure by cli """ + + cmd = "snmp-agent sys-info location %s" % self.location + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config(self): + """ Undo configure by cli """ + + cmd = "undo snmp-agent sys-info location" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Main work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + if self.state == "present": + if "location" in self.cur_cfg.keys() and self.location == self.cur_cfg["location"]: + pass + else: + self.set_config() + else: + if "location" in self.cur_cfg.keys() and self.location == self.cur_cfg["location"]: + self.undo_config() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + location=dict(type='str', required=True) + ) + + argument_spec.update(ce_argument_spec) + module = SnmpLocation(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_target_host.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_target_host.py new file mode 100644 index 00000000..f1f52087 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_target_host.py @@ -0,0 +1,940 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_target_host +short_description: Manages SNMP target host configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP target host configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + version: + description: + - Version(s) Supported by SNMP Engine. + choices: ['none', 'v1', 'v2c', 'v3', 'v1v2c', 'v1v3', 'v2cv3', 'all'] + connect_port: + description: + - Udp port used by SNMP agent to connect the Network management. + host_name: + description: + - Unique name to identify target host entry. + address: + description: + - Network Address. + notify_type: + description: + - To configure notify type as trap or inform. + choices: ['trap','inform'] + vpn_name: + description: + - VPN instance Name. + recv_port: + description: + - UDP Port number used by network management to receive alarm messages. + security_model: + description: + - Security Model. + choices: ['v1','v2c', 'v3'] + security_name: + description: + - Security Name. + security_name_v3: + description: + - Security Name V3. + security_level: + description: + - Security level indicating whether to use authentication and encryption. + choices: ['noAuthNoPriv','authentication', 'privacy'] + is_public_net: + description: + - To enable or disable Public Net-manager for target Host. + default: no_use + choices: ['no_use','true','false'] + interface_name: + description: + - Name of the interface to send the trap message. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp target host test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP version" + community.network.ce_snmp_target_host: + state: present + version: v2cv3 + provider: "{{ cli }}" + + - name: "Config SNMP target host" + community.network.ce_snmp_target_host: + state: present + host_name: test1 + address: 1.1.1.1 + notify_type: trap + vpn_name: js + security_model: v2c + security_name: wdz + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"address": "10.135.182.158", "host_name": "test2", + "notify_type": "trap", "security_level": "authentication", + "security_model": "v3", "security_name_v3": "wdz", + "state": "present", "vpn_name": "js"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"target host info": [{"address": "10.135.182.158", "domain": "snmpUDPDomain", + "nmsName": "test2", "notifyType": "trap", + "securityLevel": "authentication", "securityModel": "v3", + "securityNameV3": "wdz", "vpnInstanceName": "js"}]} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent target-host host-name test2 trap address udp-domain 10.135.182.158 vpn-instance js params securityname wdz v3 authentication"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, \ + ce_argument_spec, load_config, check_ip_addr + +# get snmp version +CE_GET_SNMP_VERSION = """ + + + + + + + +""" +# merge snmp version +CE_MERGE_SNMP_VERSION = """ + + + + %s + + + +""" + +# get snmp target host +CE_GET_SNMP_TARGET_HOST_HEADER = """ + + + + + +""" +CE_GET_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# merge snmp target host +CE_MERGE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_MERGE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# create snmp target host +CE_CREATE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_CREATE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# delete snmp target host +CE_DELETE_SNMP_TARGET_HOST_HEADER = """ + + + + + %s +""" +CE_DELETE_SNMP_TARGET_HOST_TAIL = """ + + + + +""" + +# get snmp listen port +CE_GET_SNMP_PORT = """ + + + + + + + +""" + +# merge snmp listen port +CE_MERGE_SNMP_PORT = """ + + + + %s + + + +""" + + +INTERFACE_TYPE = ['ethernet', 'eth-trunk', 'tunnel', 'null', 'loopback', + 'vlanif', '100ge', '40ge', 'mtunnel', '10ge', 'ge', 'meth', 'vbdif', 'nve'] + + +class SnmpTargetHost(object): + """ Manages SNMP target host configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + required_together = [("address", "notify_type"), ("address", "notify_type")] + required_if = [ + ["security_model", "v1", ["security_name"]], + ["security_model", "v2c", ["security_name"]], + ["security_model", "v3", ["security_name_v3"]] + ] + self.module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + required_if=required_if, + supports_check_mode=True + ) + + # module args + self.state = self.module.params['state'] + self.version = self.module.params['version'] + self.connect_port = self.module.params['connect_port'] + self.host_name = self.module.params['host_name'] + self.domain = "snmpUDPDomain" + self.address = self.module.params['address'] + self.notify_type = self.module.params['notify_type'] + self.vpn_name = self.module.params['vpn_name'] + self.recv_port = self.module.params['recv_port'] + self.security_model = self.module.params['security_model'] + self.security_name = self.module.params['security_name'] + self.security_name_v3 = self.module.params['security_name_v3'] + self.security_level = self.module.params['security_level'] + self.is_public_net = self.module.params['is_public_net'] + self.interface_name = self.module.params['interface_name'] + + # config + self.cur_cli_cfg = dict() + self.cur_netconf_cfg = dict() + self.end_netconf_cfg = dict() + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def netconf_get_config(self, conf_str): + """ Get configure by netconf """ + + xml_str = get_nc_config(self.module, conf_str) + + return xml_str + + def netconf_set_config(self, conf_str): + """ Set configure by netconf """ + + xml_str = set_nc_config(self.module, conf_str) + + return xml_str + + def check_cli_args(self): + """ Check invalid cli args """ + + if self.connect_port: + if int(self.connect_port) != 161 and (int(self.connect_port) > 65535 or int(self.connect_port) < 1025): + self.module.fail_json( + msg='Error: The value of connect_port %s is out of [161, 1025 - 65535].' % self.connect_port) + + def check_netconf_args(self, result): + """ Check invalid netconf args """ + + need_cfg = True + same_flag = True + delete_flag = False + result["target_host_info"] = [] + + if self.host_name: + + if len(self.host_name) > 32 or len(self.host_name) < 1: + self.module.fail_json( + msg='Error: The len of host_name is out of [1 - 32].') + + if self.vpn_name and self.is_public_net != 'no_use': + if self.is_public_net == "true": + self.module.fail_json( + msg='Error: Do not support vpn_name and is_public_net at the same time.') + + conf_str = CE_GET_SNMP_TARGET_HOST_HEADER + + if self.domain: + conf_str += "" + + if self.address: + if not check_ip_addr(ipaddr=self.address): + self.module.fail_json( + msg='Error: The host address [%s] is invalid.' % self.address) + conf_str += "
" + + if self.notify_type: + conf_str += "" + + if self.vpn_name: + if len(self.vpn_name) > 31 or len(self.vpn_name) < 1: + self.module.fail_json( + msg='Error: The len of vpn_name is out of [1 - 31].') + conf_str += "" + + if self.recv_port: + if int(self.recv_port) > 65535 or int(self.recv_port) < 0: + self.module.fail_json( + msg='Error: The value of recv_port is out of [0 - 65535].') + conf_str += "" + + if self.security_model: + conf_str += "" + + if self.security_name: + if len(self.security_name) > 32 or len(self.security_name) < 1: + self.module.fail_json( + msg='Error: The len of security_name is out of [1 - 32].') + conf_str += "" + + if self.security_name_v3: + if len(self.security_name_v3) > 32 or len(self.security_name_v3) < 1: + self.module.fail_json( + msg='Error: The len of security_name_v3 is out of [1 - 32].') + conf_str += "" + + if self.security_level: + conf_str += "" + + if self.is_public_net != 'no_use': + conf_str += "" + + if self.interface_name: + if len(self.interface_name) > 63 or len(self.interface_name) < 1: + self.module.fail_json( + msg='Error: The len of interface_name is out of [1 - 63].') + + find_flag = False + for item in INTERFACE_TYPE: + if item in self.interface_name.lower(): + find_flag = True + break + if not find_flag: + self.module.fail_json( + msg='Error: Please input full name of interface_name.') + + conf_str += "" + + conf_str += CE_GET_SNMP_TARGET_HOST_TAIL + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + if self.state == "present": + same_flag = False + else: + delete_flag = False + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + target_host_info = root.findall( + "snmp/targetHosts/targetHost") + if target_host_info: + for tmp in target_host_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["nmsName", "domain", "address", "notifyType", "vpnInstanceName", + "portNumber", "securityModel", "securityName", "securityNameV3", + "securityLevel", "isPublicNet", "interface-name"]: + tmp_dict[site.tag] = site.text + + result["target_host_info"].append(tmp_dict) + + if result["target_host_info"]: + for tmp in result["target_host_info"]: + + same_flag = True + + if "nmsName" in tmp.keys(): + if tmp["nmsName"] != self.host_name: + same_flag = False + else: + delete_flag = True + + if "domain" in tmp.keys(): + if tmp["domain"] != self.domain: + same_flag = False + + if "address" in tmp.keys(): + if tmp["address"] != self.address: + same_flag = False + + if "notifyType" in tmp.keys(): + if tmp["notifyType"] != self.notify_type: + same_flag = False + + if "vpnInstanceName" in tmp.keys(): + if tmp["vpnInstanceName"] != self.vpn_name: + same_flag = False + + if "portNumber" in tmp.keys(): + if tmp["portNumber"] != self.recv_port: + same_flag = False + + if "securityModel" in tmp.keys(): + if tmp["securityModel"] != self.security_model: + same_flag = False + + if "securityName" in tmp.keys(): + if tmp["securityName"] != self.security_name: + same_flag = False + + if "securityNameV3" in tmp.keys(): + if tmp["securityNameV3"] != self.security_name_v3: + same_flag = False + + if "securityLevel" in tmp.keys(): + if tmp["securityLevel"] != self.security_level: + same_flag = False + + if "isPublicNet" in tmp.keys(): + if tmp["isPublicNet"] != self.is_public_net: + same_flag = False + + if "interface-name" in tmp.keys(): + if tmp.get("interface-name") is not None: + if tmp["interface-name"].lower() != self.interface_name.lower(): + same_flag = False + else: + same_flag = False + + if same_flag: + break + + if self.state == "present": + need_cfg = True + if same_flag: + need_cfg = False + else: + need_cfg = False + if delete_flag: + need_cfg = True + + result["need_cfg"] = need_cfg + + def cli_load_config(self, commands): + """ Load configure by cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_snmp_version(self): + """ Get snmp version """ + + version = None + conf_str = CE_GET_SNMP_VERSION + recv_xml = self.netconf_get_config(conf_str=conf_str) + + if "" in recv_xml: + pass + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + version_info = root.find("snmp/engine") + if version_info: + for site in version_info: + if site.tag in ["version"]: + version = site.text + + return version + + def xml_get_connect_port(self): + """ Get connect port by xml """ + tmp_cfg = None + conf_str = CE_GET_SNMP_PORT + recv_xml = self.netconf_get_config(conf_str=conf_str) + if "" in recv_xml: + pass + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + snmp_port_info = root.findall("snmp/systemCfg/snmpListenPort") + + if snmp_port_info: + tmp_cfg = snmp_port_info[0].text + return tmp_cfg + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.version: + self.proposed["version"] = self.version + if self.connect_port: + self.proposed["connect_port"] = self.connect_port + if self.host_name: + self.proposed["host_name"] = self.host_name + if self.address: + self.proposed["address"] = self.address + if self.notify_type: + self.proposed["notify_type"] = self.notify_type + if self.vpn_name: + self.proposed["vpn_name"] = self.vpn_name + if self.recv_port: + self.proposed["recv_port"] = self.recv_port + if self.security_model: + self.proposed["security_model"] = self.security_model + if self.security_name: + self.proposed["security_name"] = "******" + if self.security_name_v3: + self.proposed["security_name_v3"] = self.security_name_v3 + if self.security_level: + self.proposed["security_level"] = self.security_level + if self.is_public_net != 'no_use': + self.proposed["is_public_net"] = self.is_public_net + if self.interface_name: + self.proposed["interface_name"] = self.interface_name + + def get_existing(self): + """ Get existing state """ + + if self.version: + version = self.get_snmp_version() + if version: + self.cur_cli_cfg["version"] = version + self.existing["version"] = version + + if self.connect_port: + tmp_cfg = self.xml_get_connect_port() + if tmp_cfg: + self.cur_cli_cfg["connect port"] = tmp_cfg + self.existing["connect port"] = tmp_cfg + + if self.host_name: + self.existing["target host info"] = self.cur_netconf_cfg[ + "target_host_info"] + + def get_end_state(self): + """ Get end state """ + + if self.version: + version = self.get_snmp_version() + if version: + self.end_state["version"] = version + + if self.connect_port: + tmp_cfg = self.xml_get_connect_port() + if tmp_cfg: + self.end_state["connect port"] = tmp_cfg + + if self.host_name: + self.end_state["target host info"] = self.end_netconf_cfg[ + "target_host_info"] + if self.existing == self.end_state: + self.changed = False + self.updates_cmd = list() + + def config_version_cli(self): + """ Config version by cli """ + + if "disable" in self.cur_cli_cfg["version"]: + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + else: + if self.version != self.cur_cli_cfg["version"]: + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_config_version_cli(self): + """ Undo config version by cli """ + + if "disable" in self.cur_cli_cfg["version"]: + pass + else: + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + self.cli_load_config(cmds) + self.changed = True + + def config_connect_port_xml(self): + """ Config connect port by xml """ + + if "connect port" in self.cur_cli_cfg.keys(): + if self.cur_cli_cfg["connect port"] == self.connect_port: + pass + else: + cmd = "snmp-agent udp-port %s" % self.connect_port + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + conf_str = CE_MERGE_SNMP_PORT % self.connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + else: + cmd = "snmp-agent udp-port %s" % self.connect_port + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + conf_str = CE_MERGE_SNMP_PORT % self.connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + + def undo_config_connect_port_cli(self): + """ Undo config connect port by cli """ + + if "connect port" in self.cur_cli_cfg.keys(): + if not self.cur_cli_cfg["connect port"]: + pass + else: + cmd = "undo snmp-agent udp-port" + + cmds = list() + cmds.append(cmd) + + self.updates_cmd.append(cmd) + connect_port = "161" + conf_str = CE_MERGE_SNMP_PORT % connect_port + self.netconf_set_config(conf_str=conf_str) + self.changed = True + + def merge_snmp_target_host(self): + """ Merge snmp target host operation """ + + conf_str = CE_MERGE_SNMP_TARGET_HOST_HEADER % self.host_name + + if self.domain: + conf_str += "%s" % self.domain + if self.address: + conf_str += "
%s
" % self.address + if self.notify_type: + conf_str += "%s" % self.notify_type + if self.vpn_name: + conf_str += "%s" % self.vpn_name + if self.recv_port: + conf_str += "%s" % self.recv_port + if self.security_model: + conf_str += "%s" % self.security_model + if self.security_name: + conf_str += "%s" % self.security_name + if self.security_name_v3: + conf_str += "%s" % self.security_name_v3 + if self.security_level: + conf_str += "%s" % self.security_level + if self.is_public_net != 'no_use': + conf_str += "%s" % self.is_public_net + if self.interface_name: + conf_str += "%s" % self.interface_name + + conf_str += CE_MERGE_SNMP_TARGET_HOST_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge snmp target host failed.') + + cmd = "snmp-agent target-host host-name %s " % self.host_name + cmd += "%s " % self.notify_type + cmd += "address udp-domain %s " % self.address + + if self.recv_port: + cmd += "udp-port %s " % self.recv_port + if self.interface_name: + cmd += "source %s " % self.interface_name + if self.vpn_name: + cmd += "vpn-instance %s " % self.vpn_name + if self.is_public_net == "true": + cmd += "public-net " + if self.security_model in ["v1", "v2c"] and self.security_name: + cmd += "params securityname %s %s " % ( + "******", self.security_model) + if self.security_model == "v3" and self.security_name_v3: + cmd += "params securityname %s %s " % ( + self.security_name_v3, self.security_model) + if self.security_level and self.security_level in ["authentication", "privacy"]: + cmd += "%s" % self.security_level + + self.changed = True + self.updates_cmd.append(cmd) + + def delete_snmp_target_host(self): + """ Delete snmp target host operation """ + + conf_str = CE_DELETE_SNMP_TARGET_HOST_HEADER % self.host_name + + if self.domain: + conf_str += "%s" % self.domain + if self.address: + conf_str += "
%s
" % self.address + if self.notify_type: + conf_str += "%s" % self.notify_type + if self.vpn_name: + conf_str += "%s" % self.vpn_name + if self.recv_port: + conf_str += "%s" % self.recv_port + if self.security_model: + conf_str += "%s" % self.security_model + if self.security_name: + conf_str += "%s" % self.security_name + if self.security_name_v3: + conf_str += "%s" % self.security_name_v3 + if self.security_level: + conf_str += "%s" % self.security_level + if self.is_public_net != 'no_use': + conf_str += "%s" % self.is_public_net + if self.interface_name: + conf_str += "%s" % self.interface_name + + conf_str += CE_DELETE_SNMP_TARGET_HOST_TAIL + + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Delete snmp target host failed.') + + if not self.address: + cmd = "undo snmp-agent target-host host-name %s " % self.host_name + else: + if self.notify_type == "trap": + cmd = "undo snmp-agent target-host trap address udp-domain %s " % self.address + else: + cmd = "undo snmp-agent target-host inform address udp-domain %s " % self.address + if self.recv_port: + cmd += "udp-port %s " % self.recv_port + if self.interface_name: + cmd += "source %s " % self.interface_name + if self.vpn_name: + cmd += "vpn-instance %s " % self.vpn_name + if self.is_public_net == "true": + cmd += "public-net " + if self.security_model in ["v1", "v2c"] and self.security_name: + cmd += "params securityname %s" % "******" + if self.security_model == "v3" and self.security_name_v3: + cmd += "params securityname %s" % self.security_name_v3 + + self.changed = True + self.updates_cmd.append(cmd) + + def merge_snmp_version(self): + """ Merge snmp version operation """ + + conf_str = CE_MERGE_SNMP_VERSION % self.version + recv_xml = self.netconf_set_config(conf_str=conf_str) + + if "" not in recv_xml: + self.module.fail_json(msg='Error: Merge snmp version failed.') + + if self.version == "none": + cmd = "snmp-agent sys-info version %s disable" % self.cur_cli_cfg[ + "version"] + self.updates_cmd.append(cmd) + elif self.version == "v1v2c": + cmd = "snmp-agent sys-info version v1" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v2c" + self.updates_cmd.append(cmd) + elif self.version == "v1v3": + cmd = "snmp-agent sys-info version v1" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v3" + self.updates_cmd.append(cmd) + elif self.version == "v2cv3": + cmd = "snmp-agent sys-info version v2c" + self.updates_cmd.append(cmd) + cmd = "snmp-agent sys-info version v3" + self.updates_cmd.append(cmd) + else: + cmd = "snmp-agent sys-info version %s" % self.version + self.updates_cmd.append(cmd) + + self.changed = True + + def work(self): + """ Main work function """ + + self.check_cli_args() + self.check_netconf_args(self.cur_netconf_cfg) + self.get_proposed() + self.get_existing() + + if self.state == "present": + if self.version: + if self.version != self.cur_cli_cfg["version"]: + self.merge_snmp_version() + if self.connect_port: + self.config_connect_port_xml() + if self.cur_netconf_cfg["need_cfg"]: + self.merge_snmp_target_host() + + else: + if self.connect_port: + self.undo_config_connect_port_cli() + if self.cur_netconf_cfg["need_cfg"]: + self.delete_snmp_target_host() + + self.check_netconf_args(self.end_netconf_cfg) + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + version=dict(choices=['none', 'v1', 'v2c', 'v3', + 'v1v2c', 'v1v3', 'v2cv3', 'all']), + connect_port=dict(type='str'), + host_name=dict(type='str'), + address=dict(type='str'), + notify_type=dict(choices=['trap', 'inform']), + vpn_name=dict(type='str'), + recv_port=dict(type='str'), + security_model=dict(choices=['v1', 'v2c', 'v3']), + security_name=dict(type='str', no_log=True), + security_name_v3=dict(type='str'), + security_level=dict( + choices=['noAuthNoPriv', 'authentication', 'privacy']), + is_public_net=dict(type='str', default='no_use', choices=['no_use', 'true', 'false']), + interface_name=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + module = SnmpTargetHost(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_traps.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_traps.py new file mode 100644 index 00000000..6d9ea6a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_traps.py @@ -0,0 +1,559 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_traps +short_description: Manages SNMP traps configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP traps configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + feature_name: + description: + - Alarm feature name. + choices: ['aaa', 'arp', 'bfd', 'bgp', 'cfg', 'configuration', 'dad', 'devm', + 'dhcpsnp', 'dldp', 'driver', 'efm', 'erps', 'error-down', 'fcoe', + 'fei', 'fei_comm', 'fm', 'ifnet', 'info', 'ipsg', 'ipv6', 'isis', + 'l3vpn', 'lacp', 'lcs', 'ldm', 'ldp', 'ldt', 'lldp', 'mpls_lspm', + 'msdp', 'mstp', 'nd', 'netconf', 'nqa', 'nvo3', 'openflow', 'ospf', + 'ospfv3', 'pim', 'pim-std', 'qos', 'radius', 'rm', 'rmon', 'securitytrap', + 'smlktrap', 'snmp', 'ssh', 'stackmng', 'sysclock', 'sysom', 'system', + 'tcp', 'telnet', 'trill', 'trunk', 'tty', 'vbst', 'vfs', 'virtual-perception', + 'vrrp', 'vstm', 'all'] + trap_name: + description: + - Alarm trap name. + interface_type: + description: + - Interface type. + choices: ['Ethernet', 'Eth-Trunk', 'Tunnel', 'NULL', 'LoopBack', 'Vlanif', '100GE', + '40GE', 'MTunnel', '10GE', 'GE', 'MEth', 'Vbdif', 'Nve'] + interface_number: + description: + - Interface number. + port_number: + description: + - Source port number. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp traps test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP trap all enable" + community.network.ce_snmp_traps: + state: present + feature_name: all + provider: "{{ cli }}" + + - name: "Config SNMP trap interface" + community.network.ce_snmp_traps: + state: present + interface_type: 40GE + interface_number: 2/0/1 + provider: "{{ cli }}" + + - name: "Config SNMP trap port" + community.network.ce_snmp_traps: + state: present + port_number: 2222 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"feature_name": "all", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"snmp-agent trap": [], + "undo snmp-agent trap": []} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp-agent trap": ["enable"], + "undo snmp-agent trap": []} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent trap enable"] +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config, ce_argument_spec, run_commands +from ansible.module_utils.connection import exec_command + + +class SnmpTraps(object): + """ Manages SNMP trap configuration """ + + def __init__(self, **kwargs): + """ Class init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule( + argument_spec=self.spec, + required_together=[("interface_type", "interface_number")], + supports_check_mode=True + ) + + # config + self.cur_cfg = dict() + self.cur_cfg["snmp-agent trap"] = [] + self.cur_cfg["undo snmp-agent trap"] = [] + + # module args + self.state = self.module.params['state'] + self.feature_name = self.module.params['feature_name'] + self.trap_name = self.module.params['trap_name'] + self.interface_type = self.module.params['interface_type'] + self.interface_number = self.module.params['interface_number'] + self.port_number = self.module.params['port_number'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.existing["snmp-agent trap"] = [] + self.existing["undo snmp-agent trap"] = [] + self.end_state = dict() + self.end_state["snmp-agent trap"] = [] + self.end_state["undo snmp-agent trap"] = [] + + commands = list() + cmd1 = 'display interface brief' + commands.append(cmd1) + self.interface = run_commands(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def check_args(self): + """ Check invalid args """ + + if self.port_number: + if self.port_number.isdigit(): + if int(self.port_number) < 1025 or int(self.port_number) > 65535: + self.module.fail_json( + msg='Error: The value of port_number is out of [1025 - 65535].') + else: + self.module.fail_json( + msg='Error: The port_number is not digit.') + + if self.interface_type and self.interface_number: + tmp_interface = self.interface_type + self.interface_number + if tmp_interface not in self.interface[0]: + self.module.fail_json( + msg='Error: The interface %s is not in the device.' % tmp_interface) + + def get_proposed(self): + """ Get proposed state """ + + self.proposed["state"] = self.state + + if self.feature_name: + self.proposed["feature_name"] = self.feature_name + + if self.trap_name: + self.proposed["trap_name"] = self.trap_name + + if self.interface_type: + self.proposed["interface_type"] = self.interface_type + + if self.interface_number: + self.proposed["interface_number"] = self.interface_number + + if self.port_number: + self.proposed["port_number"] = self.port_number + + def get_existing(self): + """ Get existing state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_cfg_lower = tmp_cfg.lower() + temp_data = tmp_cfg.split("\n") + temp_data_lower = temp_cfg_lower.split("\n") + + for item in temp_data: + if "snmp-agent trap source-port " in item: + if self.port_number: + item_tmp = item.split("snmp-agent trap source-port ") + self.cur_cfg["trap source-port"] = item_tmp[1] + self.existing["trap source-port"] = item_tmp[1] + elif "snmp-agent trap source " in item: + if self.interface_type: + item_tmp = item.split("snmp-agent trap source ") + self.cur_cfg["trap source interface"] = item_tmp[1] + self.existing["trap source interface"] = item_tmp[1] + + if self.feature_name: + for item in temp_data_lower: + if item == "snmp-agent trap enable": + self.cur_cfg["snmp-agent trap"].append("enable") + self.existing["snmp-agent trap"].append("enable") + elif item == "snmp-agent trap disable": + self.cur_cfg["snmp-agent trap"].append("disable") + self.existing["snmp-agent trap"].append("disable") + elif "undo snmp-agent trap enable " in item: + item_tmp = item.split("undo snmp-agent trap enable ") + self.cur_cfg[ + "undo snmp-agent trap"].append(item_tmp[1]) + self.existing[ + "undo snmp-agent trap"].append(item_tmp[1]) + elif "snmp-agent trap enable " in item: + item_tmp = item.split("snmp-agent trap enable ") + self.cur_cfg["snmp-agent trap"].append(item_tmp[1]) + self.existing["snmp-agent trap"].append(item_tmp[1]) + else: + del self.existing["snmp-agent trap"] + del self.existing["undo snmp-agent trap"] + + def get_end_state(self): + """ Get end_state state """ + + tmp_cfg = self.cli_get_config() + if tmp_cfg: + temp_cfg_lower = tmp_cfg.lower() + temp_data = tmp_cfg.split("\n") + temp_data_lower = temp_cfg_lower.split("\n") + + for item in temp_data: + if "snmp-agent trap source-port " in item: + if self.port_number: + item_tmp = item.split("snmp-agent trap source-port ") + self.end_state["trap source-port"] = item_tmp[1] + elif "snmp-agent trap source " in item: + if self.interface_type: + item_tmp = item.split("snmp-agent trap source ") + self.end_state["trap source interface"] = item_tmp[1] + + if self.feature_name: + for item in temp_data_lower: + if item == "snmp-agent trap enable": + self.end_state["snmp-agent trap"].append("enable") + elif item == "snmp-agent trap disable": + self.end_state["snmp-agent trap"].append("disable") + elif "undo snmp-agent trap enable " in item: + item_tmp = item.split("undo snmp-agent trap enable ") + self.end_state[ + "undo snmp-agent trap"].append(item_tmp[1]) + elif "snmp-agent trap enable " in item: + item_tmp = item.split("snmp-agent trap enable ") + self.end_state["snmp-agent trap"].append(item_tmp[1]) + else: + del self.end_state["snmp-agent trap"] + del self.end_state["undo snmp-agent trap"] + if self.end_state == self.existing: + self.changed = False + self.updates_cmd = list() + + def cli_load_config(self, commands): + """ Load configure through cli """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_config(self): + """ Get configure through cli """ + + regular = "| include snmp | include trap" + flags = list() + flags.append(regular) + tmp_cfg = self.get_config(flags) + + return tmp_cfg + + def set_trap_feature_name(self): + """ Set feature name for trap """ + + if self.feature_name == "all": + cmd = "snmp-agent trap enable" + else: + cmd = "snmp-agent trap enable feature-name %s" % self.feature_name + if self.trap_name: + cmd += " trap-name %s" % self.trap_name + + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_feature_name(self): + """ Undo feature name for trap """ + + if self.feature_name == "all": + cmd = "undo snmp-agent trap enable" + else: + cmd = "undo snmp-agent trap enable feature-name %s" % self.feature_name + if self.trap_name: + cmd += " trap-name %s" % self.trap_name + + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def set_trap_source_interface(self): + """ Set source interface for trap """ + + cmd = "snmp-agent trap source %s %s" % ( + self.interface_type, self.interface_number) + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_source_interface(self): + """ Undo source interface for trap """ + + cmd = "undo snmp-agent trap source" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def set_trap_source_port(self): + """ Set source port for trap """ + + cmd = "snmp-agent trap source-port %s" % self.port_number + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def undo_trap_source_port(self): + """ Undo source port for trap """ + + cmd = "undo snmp-agent trap source-port" + self.updates_cmd.append(cmd) + + cmds = list() + cmds.append(cmd) + + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ The work function """ + + self.check_args() + self.get_proposed() + self.get_existing() + + find_flag = False + find_undo_flag = False + tmp_interface = None + + if self.state == "present": + if self.feature_name: + if self.trap_name: + tmp_cfg = "feature-name %s trap-name %s" % ( + self.feature_name, self.trap_name.lower()) + else: + tmp_cfg = "feature-name %s" % self.feature_name + + find_undo_flag = False + if self.cur_cfg["undo snmp-agent trap"]: + for item in self.cur_cfg["undo snmp-agent trap"]: + if item == tmp_cfg: + find_undo_flag = True + elif tmp_cfg in item: + find_undo_flag = True + elif self.feature_name == "all": + find_undo_flag = True + if find_undo_flag: + self.set_trap_feature_name() + + if not find_undo_flag: + find_flag = False + if self.cur_cfg["snmp-agent trap"]: + for item in self.cur_cfg["snmp-agent trap"]: + if item == "enable": + find_flag = True + elif item == tmp_cfg: + find_flag = True + if not find_flag: + self.set_trap_feature_name() + + if self.interface_type: + find_flag = False + tmp_interface = self.interface_type + self.interface_number + + if "trap source interface" in self.cur_cfg.keys(): + if self.cur_cfg["trap source interface"] == tmp_interface: + find_flag = True + + if not find_flag: + self.set_trap_source_interface() + + if self.port_number: + find_flag = False + + if "trap source-port" in self.cur_cfg.keys(): + if self.cur_cfg["trap source-port"] == self.port_number: + find_flag = True + + if not find_flag: + self.set_trap_source_port() + + else: + if self.feature_name: + if self.trap_name: + tmp_cfg = "feature-name %s trap-name %s" % ( + self.feature_name, self.trap_name.lower()) + else: + tmp_cfg = "feature-name %s" % self.feature_name + + find_flag = False + if self.cur_cfg["snmp-agent trap"]: + for item in self.cur_cfg["snmp-agent trap"]: + if item == tmp_cfg: + find_flag = True + elif item == "enable": + find_flag = True + elif tmp_cfg in item: + find_flag = True + else: + find_flag = True + + find_undo_flag = False + if self.cur_cfg["undo snmp-agent trap"]: + for item in self.cur_cfg["undo snmp-agent trap"]: + if item == tmp_cfg: + find_undo_flag = True + elif tmp_cfg in item: + find_undo_flag = True + + if find_undo_flag: + pass + elif find_flag: + self.undo_trap_feature_name() + + if self.interface_type: + if "trap source interface" in self.cur_cfg.keys(): + self.undo_trap_source_interface() + + if self.port_number: + if "trap source-port" in self.cur_cfg.keys(): + self.undo_trap_source_port() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + feature_name=dict(choices=['aaa', 'arp', 'bfd', 'bgp', 'cfg', 'configuration', 'dad', + 'devm', 'dhcpsnp', 'dldp', 'driver', 'efm', 'erps', 'error-down', + 'fcoe', 'fei', 'fei_comm', 'fm', 'ifnet', 'info', 'ipsg', 'ipv6', + 'isis', 'l3vpn', 'lacp', 'lcs', 'ldm', 'ldp', 'ldt', 'lldp', + 'mpls_lspm', 'msdp', 'mstp', 'nd', 'netconf', 'nqa', 'nvo3', + 'openflow', 'ospf', 'ospfv3', 'pim', 'pim-std', 'qos', 'radius', + 'rm', 'rmon', 'securitytrap', 'smlktrap', 'snmp', 'ssh', 'stackmng', + 'sysclock', 'sysom', 'system', 'tcp', 'telnet', 'trill', 'trunk', + 'tty', 'vbst', 'vfs', 'virtual-perception', 'vrrp', 'vstm', 'all']), + trap_name=dict(type='str'), + interface_type=dict(choices=['Ethernet', 'Eth-Trunk', 'Tunnel', 'NULL', 'LoopBack', 'Vlanif', + '100GE', '40GE', 'MTunnel', '10GE', 'GE', 'MEth', 'Vbdif', 'Nve']), + interface_number=dict(type='str'), + port_number=dict(type='str') + ) + + argument_spec.update(ce_argument_spec) + module = SnmpTraps(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_user.py new file mode 100644 index 00000000..e666ce8b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_snmp_user.py @@ -0,0 +1,1044 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_snmp_user +short_description: Manages SNMP user configuration on HUAWEI CloudEngine switches. +description: + - Manages SNMP user configurations on CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + acl_number: + description: + - Access control list number. + usm_user_name: + description: + - Unique name to identify the USM user. + aaa_local_user: + description: + - Unique name to identify the local user. + remote_engine_id: + description: + - Remote engine id of the USM user. + user_group: + description: + - Name of the group where user belongs to. + auth_protocol: + description: + - Authentication protocol. + choices: ['noAuth', 'md5', 'sha'] + auth_key: + description: + - The authentication password. Password length, 8-255 characters. + priv_protocol: + description: + - Encryption protocol. + choices: ['noPriv', 'des56', '3des168', 'aes128', 'aes192', 'aes256'] + priv_key: + description: + - The encryption password. Password length 8-255 characters. +''' + +EXAMPLES = ''' + +- name: CloudEngine snmp user test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config SNMP usm user" + community.network.ce_snmp_user: + state: present + usm_user_name: wdz_snmp + remote_engine_id: 800007DB03389222111200 + acl_number: 2000 + user_group: wdz_group + provider: "{{ cli }}" + + - name: "Undo SNMP usm user" + community.network.ce_snmp_user: + state: absent + usm_user_name: wdz_snmp + remote_engine_id: 800007DB03389222111200 + acl_number: 2000 + user_group: wdz_group + provider: "{{ cli }}" + + - name: "Config SNMP local user" + community.network.ce_snmp_user: + state: present + aaa_local_user: wdz_user + auth_protocol: md5 + auth_key: huawei123 + priv_protocol: des56 + priv_key: huawei123 + provider: "{{ cli }}" + + - name: "Config SNMP local user" + community.network.ce_snmp_user: + state: absent + aaa_local_user: wdz_user + auth_protocol: md5 + auth_key: huawei123 + priv_protocol: des56 + priv_key: huawei123 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"acl_number": "2000", "remote_engine_id": "800007DB03389222111200", + "state": "present", "user_group": "wdz_group", + "usm_user_name": "wdz_snmp"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"snmp local user": {"local_user_info": []}, + "snmp usm user": {"usm_user_info": []}} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"snmp local user": {"local_user_info": []}, + "snmp usm user": {"usm_user_info": [{"aclNumber": "2000", "engineID": "800007DB03389222111200", + "groupName": "wdz_group", "userName": "wdz_snmp"}]}} +updates: + description: command sent to the device + returned: always + type: list + sample: ["snmp-agent remote-engineid 800007DB03389222111200 usm-user v3 wdz_snmp wdz_group acl 2000"] +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec + +# get snmp v3 USM user +CE_GET_SNMP_V3_USM_USER_HEADER = """ + + + + + + + +""" +CE_GET_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# merge snmp v3 USM user +CE_MERGE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_MERGE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# create snmp v3 USM user +CE_CREATE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_CREATE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" +# delete snmp v3 USM user +CE_DELETE_SNMP_V3_USM_USER_HEADER = """ + + + + + %s + %s + %s +""" +CE_DELETE_SNMP_V3_USM_USER_TAIL = """ + + + + +""" + +# get snmp v3 aaa local user +CE_GET_SNMP_V3_LOCAL_USER = """ + + + + + + + + + + + + + +""" +# merge snmp v3 aaa local user +CE_MERGE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# create snmp v3 aaa local user +CE_CREATE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# delete snmp v3 aaa local user +CE_DELETE_SNMP_V3_LOCAL_USER = """ + + + + + %s + %s + %s + %s + %s + + + + +""" +# display info +GET_SNMP_LOCAL_ENGINE = """ + + + + + + + +""" + + +class SnmpUser(object): + """ Manages SNMP user configuration """ + + def netconf_get_config(self, **kwargs): + """ Get configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = get_nc_config(module, conf_str) + + return xml_str + + def netconf_set_config(self, **kwargs): + """ Set configure by netconf """ + + module = kwargs["module"] + conf_str = kwargs["conf_str"] + + xml_str = set_nc_config(module, conf_str) + + return xml_str + + def check_snmp_v3_usm_user_args(self, **kwargs): + """ Check snmp v3 usm user invalid args """ + + module = kwargs["module"] + result = dict() + need_cfg = False + state = module.params['state'] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + local_user_name = module.params['aaa_local_user'] + + if usm_user_name: + if len(usm_user_name) > 32 or len(usm_user_name) == 0: + module.fail_json( + msg='Error: The length of usm_user_name %s is out of [1 - 32].' % usm_user_name) + if remote_engine_id: + if len(remote_engine_id) > 64 or len(remote_engine_id) < 10: + module.fail_json( + msg='Error: The length of remote_engine_id %s is out of [10 - 64].' % remote_engine_id) + + conf_str = CE_GET_SNMP_V3_USM_USER_HEADER + + if acl_number: + if acl_number.isdigit(): + if int(acl_number) > 2999 or int(acl_number) < 2000: + module.fail_json( + msg='Error: The value of acl_number %s is out of [2000 - 2999].' % acl_number) + else: + if not acl_number[0].isalpha() or len(acl_number) > 32 or len(acl_number) < 1: + module.fail_json( + msg='Error: The length of acl_number %s is out of [1 - 32].' % acl_number) + + conf_str += "" + + if user_group: + if len(user_group) > 32 or len(user_group) == 0: + module.fail_json( + msg='Error: The length of user_group %s is out of [1 - 32].' % user_group) + + conf_str += "" + + if auth_protocol: + conf_str += "" + + if auth_key: + if len(auth_key) > 255 or len(auth_key) == 0: + module.fail_json( + msg='Error: The length of auth_key %s is out of [1 - 255].' % auth_key) + + conf_str += "" + + if priv_protocol: + if not auth_protocol: + module.fail_json( + msg='Error: Please input auth_protocol at the same time.') + + conf_str += "" + + if priv_key: + if len(priv_key) > 255 or len(priv_key) == 0: + module.fail_json( + msg='Error: The length of priv_key %s is out of [1 - 255].' % priv_key) + conf_str += "" + + result["usm_user_info"] = [] + + conf_str += CE_GET_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + usm_user_info = root.findall("snmp/usmUsers/usmUser") + if usm_user_info: + for tmp in usm_user_info: + tmp_dict = dict() + tmp_dict["remoteEngineID"] = None + for site in tmp: + if site.tag in ["userName", "remoteEngineID", "engineID", "groupName", "authProtocol", + "authKey", "privProtocol", "privKey", "aclNumber"]: + tmp_dict[site.tag] = site.text + + result["usm_user_info"].append(tmp_dict) + + cur_cfg = dict() + if usm_user_name: + cur_cfg["userName"] = usm_user_name + if user_group: + cur_cfg["groupName"] = user_group + if auth_protocol: + cur_cfg["authProtocol"] = auth_protocol + if auth_key: + cur_cfg["authKey"] = auth_key + if priv_protocol: + cur_cfg["privProtocol"] = priv_protocol + if priv_key: + cur_cfg["privKey"] = priv_key + if acl_number: + cur_cfg["aclNumber"] = acl_number + + if remote_engine_id: + cur_cfg["engineID"] = remote_engine_id + cur_cfg["remoteEngineID"] = "true" + else: + cur_cfg["engineID"] = self.local_engine_id + cur_cfg["remoteEngineID"] = "false" + + if result["usm_user_info"]: + num = 0 + for tmp in result["usm_user_info"]: + if cur_cfg == tmp: + num += 1 + + if num == 0: + if state == "present": + need_cfg = True + else: + need_cfg = False + else: + if state == "present": + need_cfg = False + else: + need_cfg = True + + else: + if state == "present": + need_cfg = True + else: + need_cfg = False + + result["need_cfg"] = need_cfg + return result + + def check_snmp_v3_local_user_args(self, **kwargs): + """ Check snmp v3 local user invalid args """ + + module = kwargs["module"] + result = dict() + + need_cfg = False + state = module.params['state'] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + usm_user_name = module.params['usm_user_name'] + + if local_user_name: + + if usm_user_name: + module.fail_json( + msg='Error: Please do not input usm_user_name and local_user_name at the same time.') + + if not auth_protocol or not auth_key or not priv_protocol or not priv_key: + module.fail_json( + msg='Error: Please input auth_protocol auth_key priv_protocol priv_key for local user.') + + if len(local_user_name) > 32 or len(local_user_name) == 0: + module.fail_json( + msg='Error: The length of local_user_name %s is out of [1 - 32].' % local_user_name) + + if len(auth_key) > 255 or len(auth_key) == 0: + module.fail_json( + msg='Error: The length of auth_key %s is out of [1 - 255].' % auth_key) + + if len(priv_key) > 255 or len(priv_key) == 0: + module.fail_json( + msg='Error: The length of priv_key %s is out of [1 - 255].' % priv_key) + + result["local_user_info"] = [] + + conf_str = CE_GET_SNMP_V3_LOCAL_USER + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + + if "" in recv_xml: + if state == "present": + need_cfg = True + + else: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_user_info = root.findall( + "snmp/localUsers/localUser") + if local_user_info: + for tmp in local_user_info: + tmp_dict = dict() + for site in tmp: + if site.tag in ["userName", "authProtocol", "authKey", "privProtocol", "privKey"]: + tmp_dict[site.tag] = site.text + + result["local_user_info"].append(tmp_dict) + + if result["local_user_info"]: + for tmp in result["local_user_info"]: + if "userName" in tmp.keys(): + if state == "present": + if tmp["userName"] != local_user_name: + need_cfg = True + else: + if tmp["userName"] == local_user_name: + need_cfg = True + if auth_protocol: + if "authProtocol" in tmp.keys(): + if state == "present": + if tmp["authProtocol"] != auth_protocol: + need_cfg = True + else: + if tmp["authProtocol"] == auth_protocol: + need_cfg = True + if auth_key: + if "authKey" in tmp.keys(): + if state == "present": + if tmp["authKey"] != auth_key: + need_cfg = True + else: + if tmp["authKey"] == auth_key: + need_cfg = True + if priv_protocol: + if "privProtocol" in tmp.keys(): + if state == "present": + if tmp["privProtocol"] != priv_protocol: + need_cfg = True + else: + if tmp["privProtocol"] == priv_protocol: + need_cfg = True + if priv_key: + if "privKey" in tmp.keys(): + if state == "present": + if tmp["privKey"] != priv_key: + need_cfg = True + else: + if tmp["privKey"] == priv_key: + need_cfg = True + + result["need_cfg"] = need_cfg + return result + + def merge_snmp_v3_usm_user(self, **kwargs): + """ Merge snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + cmds = [] + + if remote_engine_id: + conf_str = CE_MERGE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_MERGE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + cmd += " %s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + cmd += " acl %s" % acl_number + + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_protocol != "noAuth": + cmd += " authentication-mode %s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if auth_protocol != "noAuth": + cmd += " cipher %s" % "******" + if auth_protocol or auth_key: + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " privacy-mode %s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " cipher %s" % "******" + if priv_key or priv_protocol: + cmds.append(cmd) + + conf_str += CE_MERGE_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 usm user failed.') + + return cmds + + def create_snmp_v3_usm_user(self, **kwargs): + """ Create snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + cmds = [] + + if remote_engine_id: + conf_str = CE_CREATE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_CREATE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + cmd += " %s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + cmd += " acl %s" % acl_number + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_protocol != "noAuth": + cmd += " authentication-mode %s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if auth_protocol != "noAuth": + cmd += " cipher %s" % "******" + + if auth_key or auth_protocol: + cmds.append(cmd) + + if remote_engine_id: + cmd = "snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + cmd = "snmp-agent usm-user v3 %s" % usm_user_name + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " privacy-mode %s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + if auth_protocol != "noAuth" and priv_protocol != "noPriv": + cmd += " cipher %s" % "******" + + if priv_protocol or priv_key: + cmds.append(cmd) + + conf_str += CE_CREATE_SNMP_V3_USM_USER_TAIL + + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 usm user failed.') + + return cmds + + def delete_snmp_v3_usm_user(self, **kwargs): + """ Delete snmp v3 usm user operation """ + + module = kwargs["module"] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + acl_number = module.params['acl_number'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + if remote_engine_id: + conf_str = CE_DELETE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "true", remote_engine_id) + cmd = "undo snmp-agent remote-engineid %s usm-user v3 %s" % ( + remote_engine_id, usm_user_name) + else: + if not self.local_engine_id: + module.fail_json( + msg='Error: The local engine id is null, please input remote_engine_id.') + + conf_str = CE_DELETE_SNMP_V3_USM_USER_HEADER % ( + usm_user_name, "false", self.local_engine_id) + cmd = "undo snmp-agent usm-user v3 %s" % usm_user_name + + if user_group: + conf_str += "%s" % user_group + + if acl_number: + conf_str += "%s" % acl_number + + if auth_protocol: + conf_str += "%s" % auth_protocol + + if auth_key: + conf_str += "%s" % auth_key + + if priv_protocol: + conf_str += "%s" % priv_protocol + + if priv_key: + conf_str += "%s" % priv_key + + conf_str += CE_DELETE_SNMP_V3_USM_USER_TAIL + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 usm user failed.') + + return cmd + + def merge_snmp_v3_local_user(self, **kwargs): + """ Merge snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_MERGE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Merge snmp v3 local user failed.') + + cmd = "snmp-agent local-user v3 %s " % local_user_name + "authentication-mode %s " % auth_protocol + \ + "cipher ****** " + "privacy-mode %s " % priv_protocol + "cipher ******" + + return cmd + + def create_snmp_v3_local_user(self, **kwargs): + """ Create snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_CREATE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Create snmp v3 local user failed.') + + cmd = "snmp-agent local-user v3 %s " % local_user_name + "authentication-mode %s " % auth_protocol + \ + "cipher ****** " + "privacy-mode %s " % priv_protocol + "cipher ******" + + return cmd + + def delete_snmp_v3_local_user(self, **kwargs): + """ Delete snmp v3 local user operation """ + + module = kwargs["module"] + local_user_name = module.params['aaa_local_user'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + + conf_str = CE_DELETE_SNMP_V3_LOCAL_USER % ( + local_user_name, auth_protocol, auth_key, priv_protocol, priv_key) + recv_xml = self.netconf_set_config(module=module, conf_str=conf_str) + + if "" not in recv_xml: + module.fail_json(msg='Error: Delete snmp v3 local user failed.') + + cmd = "undo snmp-agent local-user v3 %s" % local_user_name + + return cmd + + def get_snmp_local_engine(self, **kwargs): + """ Get snmp local engine operation """ + + module = kwargs["module"] + + conf_str = GET_SNMP_LOCAL_ENGINE + recv_xml = self.netconf_get_config(module=module, conf_str=conf_str) + if "" in recv_xml: + xml_str = recv_xml.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + local_engine_info = root.findall("snmp/engine/engineID") + if local_engine_info: + self.local_engine_id = local_engine_info[0].text + + +def main(): + """ Module main function """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + acl_number=dict(type='str'), + usm_user_name=dict(type='str'), + remote_engine_id=dict(type='str'), + user_group=dict(type='str'), + auth_protocol=dict(choices=['noAuth', 'md5', 'sha']), + auth_key=dict(type='str', no_log=True), + priv_protocol=dict( + choices=['noPriv', 'des56', '3des168', 'aes128', 'aes192', 'aes256']), + priv_key=dict(type='str', no_log=True), + aaa_local_user=dict(type='str') + ) + + mutually_exclusive = [("usm_user_name", "local_user_name")] + argument_spec.update(ce_argument_spec) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True + ) + + changed = False + proposed = dict() + existing = dict() + end_state = dict() + updates = [] + + state = module.params['state'] + acl_number = module.params['acl_number'] + usm_user_name = module.params['usm_user_name'] + remote_engine_id = module.params['remote_engine_id'] + user_group = module.params['user_group'] + auth_protocol = module.params['auth_protocol'] + auth_key = module.params['auth_key'] + priv_protocol = module.params['priv_protocol'] + priv_key = module.params['priv_key'] + aaa_local_user = module.params['aaa_local_user'] + + snmp_user_obj = SnmpUser() + + if not snmp_user_obj: + module.fail_json(msg='Error: Init module failed.') + + # get proposed + proposed["state"] = state + if acl_number: + proposed["acl_number"] = acl_number + if usm_user_name: + proposed["usm_user_name"] = usm_user_name + if remote_engine_id: + proposed["remote_engine_id"] = remote_engine_id + if user_group: + proposed["user_group"] = user_group + if auth_protocol: + proposed["auth_protocol"] = auth_protocol + if auth_key: + proposed["auth_key"] = auth_key + if priv_protocol: + proposed["priv_protocol"] = priv_protocol + if priv_key: + proposed["priv_key"] = priv_key + if aaa_local_user: + proposed["aaa_local_user"] = aaa_local_user + + snmp_user_obj.get_snmp_local_engine(module=module) + snmp_v3_usm_user_rst = snmp_user_obj.check_snmp_v3_usm_user_args( + module=module) + snmp_v3_local_user_rst = snmp_user_obj.check_snmp_v3_local_user_args( + module=module) + + # state exist snmp v3 user config + exist_tmp = dict() + for item in snmp_v3_usm_user_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_usm_user_rst[item] + if exist_tmp: + existing["snmp usm user"] = exist_tmp + + exist_tmp = dict() + for item in snmp_v3_local_user_rst: + if item != "need_cfg": + exist_tmp[item] = snmp_v3_local_user_rst[item] + if exist_tmp: + existing["snmp local user"] = exist_tmp + + if state == "present": + if snmp_v3_usm_user_rst["need_cfg"]: + if len(snmp_v3_usm_user_rst["usm_user_info"]) != 0: + cmd = snmp_user_obj.merge_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_user_obj.create_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + + if snmp_v3_local_user_rst["need_cfg"]: + if len(snmp_v3_local_user_rst["local_user_info"]) != 0: + cmd = snmp_user_obj.merge_snmp_v3_local_user( + module=module) + changed = True + updates.append(cmd) + else: + cmd = snmp_user_obj.create_snmp_v3_local_user( + module=module) + changed = True + updates.append(cmd) + + else: + if snmp_v3_usm_user_rst["need_cfg"]: + cmd = snmp_user_obj.delete_snmp_v3_usm_user(module=module) + changed = True + updates.append(cmd) + if snmp_v3_local_user_rst["need_cfg"]: + cmd = snmp_user_obj.delete_snmp_v3_local_user(module=module) + changed = True + updates.append(cmd) + + # state exist snmp v3 user config + snmp_v3_usm_user_rst = snmp_user_obj.check_snmp_v3_usm_user_args( + module=module) + end_tmp = dict() + for item in snmp_v3_usm_user_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_usm_user_rst[item] + if end_tmp: + end_state["snmp usm user"] = end_tmp + + snmp_v3_local_user_rst = snmp_user_obj.check_snmp_v3_local_user_args( + module=module) + end_tmp = dict() + for item in snmp_v3_local_user_rst: + if item != "need_cfg": + end_tmp[item] = snmp_v3_local_user_rst[item] + if end_tmp: + end_state["snmp local user"] = end_tmp + + results = dict() + results['proposed'] = proposed + results['existing'] = existing + results['changed'] = changed + results['end_state'] = end_state + results['updates'] = updates + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_startup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_startup.py new file mode 100644 index 00000000..fe0e143a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_startup.py @@ -0,0 +1,465 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_startup +short_description: Manages a system startup information on HUAWEI CloudEngine switches. +description: + - Manages a system startup information on HUAWEI CloudEngine switches. +author: + - Li Yanfeng (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + cfg_file: + description: + - Name of the configuration file that is applied for the next startup. + The value is a string of 5 to 255 characters. + default: present + software_file: + description: + - File name of the system software that is applied for the next startup. + The value is a string of 5 to 255 characters. + patch_file: + description: + - Name of the patch file that is applied for the next startup. + slot: + description: + - Position of the device.The value is a string of 1 to 32 characters. + The possible value of slot is all, slave-board, or the specific slotID. + action: + description: + - Display the startup information. + choices: ['display'] + +''' + +EXAMPLES = ''' +- name: Startup module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Display startup information + community.network.ce_startup: + action: display + provider: "{{ cli }}" + + - name: Set startup patch file + community.network.ce_startup: + patch_file: 2.PAT + slot: all + provider: "{{ cli }}" + + - name: Set startup software file + community.network.ce_startup: + software_file: aa.cc + slot: 1 + provider: "{{ cli }}" + + - name: Set startup cfg file + community.network.ce_startup: + cfg_file: 2.cfg + slot: 1 + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"patch_file": "2.PAT", + "slot": "all"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { + "configSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "curentPatchFile": "NULL", + "curentStartupFile": "NULL", + "curentSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "nextPatchFile": "flash:/1.PAT", + "nextStartupFile": "flash:/1.cfg", + "nextSysSoft": "flash:/CE12800-V200R002C20_issuB071.cc", + "position": "5" + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"StartupInfos": null} +updates: + description: command sent to the device + returned: always + type: list + sample: {"startup patch 2.PAT all"} +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, run_commands +from ansible.module_utils.connection import exec_command + + +class StartUp(object): + """ + Manages system startup information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.cfg_file = self.module.params['cfg_file'] + self.software_file = self.module.params['software_file'] + self.patch_file = self.module.params['patch_file'] + self.slot = self.module.params['slot'] + self.action = self.module.params['action'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # system startup info + self.startup_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_startup_dict(self): + """Retrieves the current config from the device or cache + """ + cmd = 'display startup' + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + startup_info = dict() + startup_info["StartupInfos"] = list() + if not cfg: + return startup_info + else: + re_find = re.findall(r'(.*)\s*' + r'\s*Configured\s*startup\s*system\s*software:\s*(.*)' + r'\s*Startup\s*system\s*software:\s*(.*)' + r'\s*Next\s*startup\s*system\s*software:\s*(.*)' + r'\s*Startup\s*saved-configuration\s*file:\s*(.*)' + r'\s*Next\s*startup\s*saved-configuration\s*file:\s*(.*)' + r'\s*Startup\s*paf\s*file:\s*(.*)' + r'\s*Next\s*startup\s*paf\s*file:\s*(.*)' + r'\s*Startup\s*patch\s*package:\s*(.*)' + r'\s*Next\s*startup\s*patch\s*package:\s*(.*)', cfg) + + if re_find: + for mem in re_find: + startup_info["StartupInfos"].append( + dict(nextStartupFile=mem[5], configSysSoft=mem[1], curentSysSoft=mem[2], + nextSysSoft=mem[3], curentStartupFile=mem[4], curentPatchFile=mem[8], + nextPatchFile=mem[9], postion=mem[0])) + return startup_info + return startup_info + + def get_cfg_filename_type(self, filename): + """Gets the type of cfg filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Configuration file name include spaces.') + + iftype = None + + if filename.endswith('.cfg'): + iftype = 'cfg' + elif filename.endswith('.zip'): + iftype = 'zip' + elif filename.endswith('.dat'): + iftype = 'dat' + else: + return None + return iftype.lower() + + def get_pat_filename_type(self, filename): + """Gets the type of patch filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Patch file name include spaces.') + + iftype = None + + if filename.endswith('.PAT'): + iftype = 'PAT' + else: + return None + return iftype.upper() + + def get_software_filename_type(self, filename): + """Gets the type of software filename, such as cfg, zip, dat...""" + + if filename is None: + return None + if ' ' in filename: + self.module.fail_json( + msg='Error: Software file name include spaces.') + + iftype = None + + if filename.endswith('.cc'): + iftype = 'cc' + else: + return None + return iftype.lower() + + def startup_next_cfg_file(self): + """set next cfg file""" + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + cmd['command'] = "startup saved-configuration %s slot %s" % ( + self.cfg_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup saved-configuration %s slot %s" % (self.cfg_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup saved-configuration %s" % self.cfg_file + commands.append(cmd) + self.updates_cmd.append( + "startup saved-configuration %s" % self.cfg_file) + run_commands(self.module, commands) + self.changed = True + + def startup_next_software_file(self): + """set next software file""" + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + if self.slot == "all" or self.slot == "slave-board": + cmd['command'] = "startup system-software %s %s" % ( + self.software_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s %s" % (self.software_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup system-software %s slot %s" % ( + self.software_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s slot %s" % (self.software_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + + if not self.slot: + cmd['command'] = "startup system-software %s" % self.software_file + commands.append(cmd) + self.updates_cmd.append( + "startup system-software %s" % self.software_file) + run_commands(self.module, commands) + self.changed = True + + def startup_next_pat_file(self): + """set next patch file""" + + commands = list() + cmd = {'output': None, 'command': ''} + if self.slot: + if self.slot == "all": + cmd['command'] = "startup patch %s %s" % ( + self.patch_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s %s" % (self.patch_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + else: + cmd['command'] = "startup patch %s slot %s" % ( + self.patch_file, self.slot) + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s slot %s" % (self.patch_file, self.slot)) + run_commands(self.module, commands) + self.changed = True + + if not self.slot: + cmd['command'] = "startup patch %s" % self.patch_file + commands.append(cmd) + self.updates_cmd.append( + "startup patch %s" % self.patch_file) + run_commands(self.module, commands) + self.changed = True + + def check_params(self): + """Check all input params""" + + # cfg_file check + if self.cfg_file: + if not self.get_cfg_filename_type(self.cfg_file): + self.module.fail_json( + msg='Error: Invalid cfg file name or cfg file name extension ( *.cfg, *.zip, *.dat ).') + + # software_file check + if self.software_file: + if not self.get_software_filename_type(self.software_file): + self.module.fail_json( + msg='Error: Invalid software file name or software file name extension ( *.cc).') + + # patch_file check + if self.patch_file: + if not self.get_pat_filename_type(self.patch_file): + self.module.fail_json( + msg='Error: Invalid patch file name or patch file name extension ( *.PAT ).') + + # slot check + if self.slot: + if self.slot.isdigit(): + if int(self.slot) <= 0 or int(self.slot) > 16: + self.module.fail_json( + msg='Error: The number of slot is not in the range from 1 to 16.') + else: + if len(self.slot) <= 0 or len(self.slot) > 32: + self.module.fail_json( + msg='Error: The length of slot is not in the range from 1 to 32.') + + def get_proposed(self): + """get proposed info""" + + if self.cfg_file: + self.proposed["cfg_file"] = self.cfg_file + if self.software_file: + self.proposed["system_file"] = self.software_file + if self.patch_file: + self.proposed["patch_file"] = self.patch_file + if self.slot: + self.proposed["slot"] = self.slot + + def get_existing(self): + """get existing info""" + + if not self.startup_info: + self.existing["StartupInfos"] = None + else: + self.existing["StartupInfos"] = self.startup_info["StartupInfos"] + + def get_end_state(self): + """get end state info""" + if not self.startup_info: + self.end_state["StartupInfos"] = None + else: + self.end_state["StartupInfos"] = self.startup_info["StartupInfos"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_proposed() + + self.startup_info = self.get_startup_dict() + self.get_existing() + + startup_info = self.startup_info["StartupInfos"][0] + if self.cfg_file: + if self.cfg_file != startup_info["nextStartupFile"]: + self.startup_next_cfg_file() + + if self.software_file: + if self.software_file != startup_info["nextSysSoft"]: + self.startup_next_software_file() + if self.patch_file: + if self.patch_file != startup_info["nextPatchFile"]: + self.startup_next_pat_file() + if self.action == "display": + self.startup_info = self.get_startup_dict() + + self.startup_info = self.get_startup_dict() + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + cfg_file=dict(type='str'), + software_file=dict(type='str'), + patch_file=dict(type='str'), + slot=dict(type='str'), + action=dict(type='str', choices=['display']) + ) + argument_spec.update(ce_argument_spec) + module = StartUp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route.py new file mode 100644 index 00000000..c3d28d8d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route.py @@ -0,0 +1,829 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_static_route +short_description: Manages static route configuration on HUAWEI CloudEngine switches. +description: + - Manages the static routes on HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + prefix: + description: + - Destination ip address of static route. + required: true + mask: + description: + - Destination ip mask of static route. + required: true + aftype: + description: + - Destination ip address family type of static route. + required: true + choices: ['v4','v6'] + next_hop: + description: + - Next hop address of static route. + nhp_interface: + description: + - Next hop interface full name of static route. + vrf: + description: + - VPN instance of destination ip address. + destvrf: + description: + - VPN instance of next hop ip address. + tag: + description: + - Route tag value (numeric). + description: + description: + - Name of the route. Used with the name parameter on the CLI. + pref: + description: + - Preference or administrative difference of route (range 1-255). + state: + description: + - Specify desired state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Static route module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config a ipv4 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + prefix: 2.1.1.2 + mask: 24 + next_hop: 3.1.1.2 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" + - name: Config a ipv4 static route ,next hop is an interface and that it has the proper description + community.network.ce_static_route: + prefix: 2.1.1.2 + mask: 24 + next_hop: 10GE1/0/1 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" + - name: Config a ipv6 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + prefix: fc00:0:0:2001::1 + mask: 64 + next_hop: fc00:0:0:2004::1 + description: 'Configured by Ansible' + aftype: v6 + provider: "{{ cli }}" + - name: Config a ipv4 static route, next hop is an interface and that it has the proper description + community.network.ce_static_route: + prefix: fc00:0:0:2001::1 + mask: 64 + next_hop: 10GE1/0/1 + description: 'Configured by Ansible' + aftype: v6 + provider: "{{ cli }}" + - name: Config a VRF and set ipv4 static route, next hop is an address and that it has the proper description + community.network.ce_static_route: + vrf: vpna + prefix: 2.1.1.2 + mask: 24 + next_hop: 3.1.1.2 + description: 'Configured by Ansible' + aftype: v4 + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.642", "mask": "24", "description": "testing", + "vrf": "_public_"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip route-static 192.168.20.0 255.255.255.0 3.3.3.3 preference 100 description testing"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_STATIC_ROUTE = """ + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_ABSENT = """ + + + + + + + + + + + + + + + + + + +""" + +CE_NC_SET_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s%s%s%s + + + + +""" +CE_NC_SET_DESCRIPTION = """ +%s +""" + +CE_NC_SET_PREFERENCE = """ +%s +""" + +CE_NC_SET_TAG = """ +%s +""" + +CE_NC_DELETE_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_v4addr(addr): + """check if ipv4 addr is valid""" + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + +def is_valid_v6addr(addr): + """check if ipv6 addr is valid""" + if addr.find(':') != -1: + addr_list = addr.split(':') + # The IPv6 binary system has a length of 128 bits and is grouped by 16 bits. + # Each group is separated by a colon ":" and can be divided into 8 groups, each group being represented by 4 hexadecimal + if len(addr_list) > 8: + return False + # You can use a double colon "::" to represent a group of 0 or more consecutive 0s, but only once. + if addr.count('::') > 1: + return False + # if do not use '::', the length of address should not be less than 8. + if addr.count('::') == 0 and len(addr_list) < 8: + return False + for group in addr_list: + if group.strip() == '': + continue + try: + # Each group is represented in 4-digit hexadecimal + int(group, base=16) + except ValueError: + return False + return True + return False + + +def is_valid_tag(tag): + """check if the tag is valid""" + + if not tag.isdigit(): + return False + + if int(tag) < 1 or int(tag) > 4294967295: + return False + + return True + + +def is_valid_preference(pref): + """check if the preference is valid""" + if pref.isdigit(): + return int(pref) > 0 and int(pref) < 256 + else: + return False + + +def is_valid_description(description): + """check if the description is valid""" + if description.find('?') != -1: + return False + if len(description) < 1 or len(description) > 255: + return False + return True + + +class StaticRoute(object): + """static route module""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # static route info + self.prefix = self.module.params['prefix'] + self.mask = self.module.params['mask'] + self.aftype = self.module.params['aftype'] + self.next_hop = self.module.params['next_hop'] + self.nhp_interface = self.module.params['nhp_interface'] + if self.nhp_interface is None: + self.nhp_interface = "Invalid0" + self.tag = self.module.params['tag'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + self.pref = self.module.params['pref'] + + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + self.destvrf = self.module.params['destvrf'] + if self.destvrf is None: + self.destvrf = "_public_" + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.static_routes_info = dict() + + def init_module(self): + """init module""" + + required_one_of = [["next_hop", "nhp_interface"]] + self.module = AnsibleModule( + argument_spec=self.spec, required_one_of=required_one_of, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def convert_len_to_mask(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def convert_ip_prefix(self): + """convert prefix to real value i.e. 2.2.2.2/24 to 2.2.2.0/24""" + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + if self.mask == '32': + return True + if self.mask == '0': + self.prefix = '0.0.0.0' + return True + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + byte_len = 8 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + else: + if self.prefix.find(':') == -1: + return False + if self.mask == '128': + return True + if self.mask == '0': + self.prefix = '::' + return True + addr_list = self.prefix.split(':') + length = len(addr_list) + if length > 6: + return False + byte_len = 16 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + + if self.aftype == "v4": + for i in range(ip_len + 1, length): + addr_list[i] = 0 + else: + for i in range(length - ip_len, length): + addr_list[i] = 0 + for j in range(0, byte_len - ip_bit): + if self.aftype == "v4": + addr_list[ip_len] = int(addr_list[ip_len]) & (0 << j) + else: + if addr_list[length - ip_len - 1] == "": + continue + addr_list[length - ip_len - + 1] = '0x%s' % addr_list[length - ip_len - 1] + addr_list[length - ip_len - + 1] = int(addr_list[length - ip_len - 1], 16) & (0 << j) + + if self.aftype == "v4": + self.prefix = '%s.%s.%s.%s' % (addr_list[0], addr_list[1], addr_list[2], addr_list[3]) + return True + else: + ipv6_addr_str = "" + for num in range(0, length - ip_len): + ipv6_addr_str += '%s:' % addr_list[num] + self.prefix = ipv6_addr_str + return True + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + if self.aftype == "v4": + aftype = "ip" + maskstr = self.convert_len_to_mask(self.mask) + else: + aftype = "ipv6" + maskstr = self.mask + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + if self.vrf == "_public_": + vrf = '' + else: + vrf = self.vrf + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.state == "present": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('%s route-static vpn-instance %s %s %s vpn-instance %s %s' + % (aftype, vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('%s route-static vpn-instance %s %s %s %s %s' + % (aftype, vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('%s route-static %s %s vpn-instance %s %s' + % (aftype, self.prefix, maskstr, self.destvrf, next_hop)) + else: + self.updates_cmd.append('%s route-static %s %s %s %s' + % (aftype, self.prefix, maskstr, nhp_interface, next_hop)) + if self.pref: + self.updates_cmd[0] += ' preference %s' % (self.pref) + if self.tag: + self.updates_cmd[0] += ' tag %s' % (self.tag) + if self.description: + self.updates_cmd[0] += ' description %s' % (self.description) + + if self.state == "absent": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('undo %s route-static vpn-instance %s %s %s vpn-instance %s %s' + % (aftype, vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('undo %s route-static vpn-instance %s %s %s %s %s' + % (aftype, vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('undo %s route-static %s %s vpn-instance %s %s' + % (aftype, self.prefix, maskstr, self.destvrf, self.next_hop)) + else: + self.updates_cmd.append('undo %s route-static %s %s %s %s' + % (aftype, self.prefix, maskstr, nhp_interface, next_hop)) + + def operate_static_route(self, version, prefix, mask, nhp_interface, next_hop, vrf, destvrf, state): + """operate ipv4 static route""" + + description_xml = """\n""" + preference_xml = """\n""" + tag_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + if nhp_interface is None: + nhp_interface = "Invalid0" + + if vrf is None: + vpn_instance = "_public_" + else: + vpn_instance = vrf + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + if self.description: + description_xml = CE_NC_SET_DESCRIPTION % self.description + if self.pref: + preference_xml = CE_NC_SET_PREFERENCE % self.pref + if self.tag: + tag_xml = CE_NC_SET_TAG % self.tag + + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, + dest_vpn_instance, next_hop, description_xml, preference_xml, tag_xml) + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_STATIC_ROUTE") + + def get_static_route(self, state): + """get ipv4 static route""" + + self.static_routes_info["sroute"] = list() + + if state == 'absent': + getxmlstr = CE_NC_GET_STATIC_ROUTE_ABSENT + else: + getxmlstr = CE_NC_GET_STATIC_ROUTE + + xml_str = get_nc_config(self.module, getxmlstr) + + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + static_routes = root.findall( + "staticrt/staticrtbase/srRoutes/srRoute") + + if static_routes: + for static_route in static_routes: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["vrfName", "afType", "topologyName", + "prefix", "maskLength", "destVrfName", + "nexthop", "ifName", "preference", "description"]: + static_info[ + static_ele.tag] = static_ele.text + if static_ele.tag == "tag": + if static_ele.text is not None: + static_info["tag"] = static_ele.text + else: + static_info["tag"] = "None" + self.static_routes_info["sroute"].append(static_info) + + def check_params(self): + """check all input params""" + + # check prefix and mask + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: Mask is invalid.') + # ipv4 check + if self.aftype == "v4": + if int(self.mask) > 32 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv4 mask must be an integer between 1 and 32.') + # next_hop check + if self.next_hop: + if not is_valid_v4addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.next_hop) + # ipv6 check + if self.aftype == "v6": + if int(self.mask) > 128 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv6 mask must be an integer between 1 and 128.') + if self.next_hop: + if not is_valid_v6addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.next_hop) + + # description check + if self.description: + if not is_valid_description(self.description): + self.module.fail_json( + msg='Error: Dsecription length should be 1 - 35, and can not contain "?".') + # tag check + if self.tag: + if not is_valid_tag(self.tag): + self.module.fail_json( + msg='Error: Tag should be integer 1 - 4294967295.') + # preference check + if self.pref: + if not is_valid_preference(self.pref): + self.module.fail_json( + msg='Error: Preference should be integer 1 - 255.') + if self.nhp_interface != "Invalid0" and self.destvrf != "_public_": + self.module.fail_json( + msg='Error: Destination vrf dose no support next hop is interface.') + # convert prefix + if not self.convert_ip_prefix(): + self.module.fail_json( + msg='Error: The %s is not a valid address' % self.prefix) + + def set_ip_static_route(self): + """set ip static route""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route(version, self.prefix, self.mask, self.nhp_interface, + self.next_hop, self.vrf, self.destvrf, self.state) + + def is_prefix_exist(self, static_route, version): + """is prefix mask nex_thop exist""" + if static_route is None: + return False + if self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if self.next_hop and not self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if not self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() + + def get_ip_static_route(self): + """get ip static route""" + + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + change = False + self.get_static_route(self.state) + if self.state == 'present': + for static_route in self.static_routes_info["sroute"]: + if self.is_prefix_exist(static_route, version): + if self.vrf: + if static_route["vrfName"] != self.vrf: + change = True + if self.tag: + if static_route["tag"] != self.tag: + change = True + if self.destvrf: + if static_route["destVrfName"] != self.destvrf: + change = True + if self.description: + if static_route["description"] != self.description: + change = True + if self.pref: + if static_route["preference"] != self.pref: + change = True + if self.nhp_interface: + if static_route["ifName"].lower() != self.nhp_interface.lower(): + change = True + if self.next_hop: + if static_route["nexthop"].lower() != self.next_hop.lower(): + change = True + return change + else: + continue + change = True + else: + for static_route in self.static_routes_info["sroute"]: + if static_route["nexthop"] and self.next_hop: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + if static_route["ifName"] and self.nhp_interface: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_proposed(self): + """get proposed information""" + + self.proposed['prefix'] = self.prefix + self.proposed['mask'] = self.mask + self.proposed['afType'] = self.aftype + self.proposed['next_hop'] = self.next_hop + self.proposed['ifName'] = self.nhp_interface + self.proposed['vrfName'] = self.vrf + self.proposed['destVrfName'] = self.destvrf + if self.tag: + self.proposed['tag'] = self.tag + if self.description: + self.proposed['description'] = self.description + if self.pref is None: + self.proposed['preference'] = 60 + else: + self.proposed['preference'] = self.pref + self.proposed['state'] = self.state + + def get_existing(self): + """get existing information""" + + change = self.get_ip_static_route() + self.existing['sroute'] = self.static_routes_info["sroute"] + self.changed = bool(change) + + def get_end_state(self): + """get end state information""" + + self.get_static_route(self.state) + self.end_state['sroute'] = self.static_routes_info["sroute"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.set_ip_static_route() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + prefix=dict(required=True, type='str'), + mask=dict(required=True, type='str'), + aftype=dict(choices=['v4', 'v6'], required=True), + next_hop=dict(required=False, type='str'), + nhp_interface=dict(required=False, type='str'), + vrf=dict(required=False, type='str'), + destvrf=dict(required=False, type='str'), + tag=dict(required=False, type='str'), + description=dict(required=False, type='str'), + pref=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = StaticRoute(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route_bfd.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route_bfd.py new file mode 100644 index 00000000..a35f05ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_static_route_bfd.py @@ -0,0 +1,1593 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ce_static_route_bfd +version_added: '0.2.0' +short_description: Manages static route configuration on HUAWEI CloudEngine switches. +description: + - Manages the static routes on HUAWEI CloudEngine switches. +author: xuxiaowei0512 (@CloudEngine-Ansible) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. + - If no vrf is supplied, vrf is set to default. + - If I(state=absent), the route configuration will be removed, regardless of the non-required parameters. +options: + prefix: + description: + - Destination ip address of static route. + required: true + type: str + mask: + description: + - Destination ip mask of static route. + type: str + aftype: + description: + - Destination ip address family type of static route. + required: true + type: str + choices: ['v4','v6'] + next_hop: + description: + - Next hop address of static route. + type: str + nhp_interface: + description: + - Next hop interface full name of static route. + type: str + vrf: + description: + - VPN instance of destination ip address. + type: str + destvrf: + description: + - VPN instance of next hop ip address. + type: str + tag: + description: + - Route tag value (numeric). + type: int + description: + description: + - Name of the route. Used with the name parameter on the CLI. + type: str + pref: + description: + - Preference or administrative difference of route (range 1-255). + type: int + function_flag: + description: + - Used to distinguish between command line functions. + required: true + choices: ['globalBFD','singleBFD','dynamicBFD','staticBFD'] + type: str + min_tx_interval: + description: + - Set the minimum BFD session sending interval (range 50-1000). + type: int + min_rx_interval: + description: + - Set the minimum BFD receive interval (range 50-1000). + type: int + detect_multiplier: + description: + - Configure the BFD multiplier (range 3-50). + type: int + bfd_session_name: + description: + - bfd name (range 1-15). + type: str + commands: + description: + - Incoming command line is used to send sys,undo ip route-static default-bfd,commit. + type: list + state: + description: + - Specify desired state of the resource. + required: false + choices: ['present','absent'] + type: str + default: present +''' + +EXAMPLES = ''' + #ip route-static bfd interface-type interface-number nexthop-address [ local-address address ] + #[ min-rx-interval min-rx-interval | min-tx-interval min-tx-interval | detect-multiplier multiplier ] + - name: Config an ip route-static bfd 10GE1/0/1 3.3.3.3 min-rx-interval 50 min-tx-interval 50 detect-multiplier 5 + community.network.ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.3 + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 5 + aftype: v4 + state: present + + #undo ip route-static bfd [ interface-type interface-number | vpn-instance vpn-instance-name ] nexthop-address + - name: Undo ip route-static bfd 10GE1/0/1 3.3.3.4 + community.network.ce_static_route_bfd: + function_flag: 'singleBFD' + nhp_interface: 10GE1/0/1 + next_hop: 3.3.3.4 + aftype: v4 + state: absent + + #ip route-static default-bfd { min-rx-interval {min-rx-interval} | min-tx-interval {min-tx-interval} | detect-multiplier {multiplier}} + - name: Config an ip route-static default-bfd min-rx-interval 50 min-tx-interval 50 detect-multiplier 6 + community.network.ce_static_route_bfd: + function_flag: 'globalBFD' + min_tx_interval: 50 + min_rx_interval: 50 + detect_multiplier: 6 + aftype: v4 + state: present + + - name: Undo ip route-static default-bfd + community.network.ce_static_route_bfd: + function_flag: 'globalBFD' + aftype: v4 + state: absent + commands: 'sys,undo ip route-static default-bfd,commit' + + - name: Config an ipv4 static route 2.2.2.0/24 2.2.2.1 preference 1 tag 2 description test for staticBFD + community.network.ce_static_route_bfd: + function_flag: 'staticBFD' + prefix: 2.2.2.2 + mask: 24 + next_hop: 2.2.2.1 + tag: 2 + description: test + pref: 1 + aftype: v4 + bfd_session_name: btoa + state: present +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.642", "mask": "24", "description": "testing", + "vrf": "_public_", "bfd_session_name": "btoa"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"function_flag": "", "next_hop": "", "pref": "101", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"function_flag": "staticBFD", "next_hop": "3.3.3.3", "pref": "100", + "prefix": "192.168.20.0", "mask": "24", "description": "testing", + "tag" : "null", "bfd_session_name": "btoa"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip route-static 192.168.20.0 255.255.255.0 3.3.3.3 preference 100 description testing"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config + +CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME = """ + + + + + + + + + + + + + + + + + + + + + + +""" +# bfd enable +CE_NC_GET_STATIC_ROUTE_BFD_ENABLE = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD_ABSENT = """ + + + + + + %s + %s + %s + %s + + + + + +""" + +CE_NC_GET_STATIC_ROUTE_BFD = """ + + + + + + %s + %s + %s + %s + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD = """ + + + + + + + + + + + +""" +CE_NC_GET_STATIC_ROUTE_ABSENT = """ + + + + + + + + + + + + + + + + + + +""" + +CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD = """ + + + + + %s + %s + %s + %s%s%s%s%s + + + + + +""" +CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL = """ +%s +""" +CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD = """ + + + + %s%s%s + + + +""" + +CE_NC_SET_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s%s%s%s%s + + + + +""" +CE_NC_SET_DESCRIPTION = """ +%s +""" + +CE_NC_SET_PREFERENCE = """ +%s +""" + +CE_NC_SET_TAG = """ +%s +""" +CE_NC_SET_BFDSESSIONNAME = """ +%s +""" +CE_NC_SET_BFDENABLE = """ +true +""" +CE_NC_DELETE_STATIC_ROUTE = """ + + + + + %s + %s + base + %s + %s + %s + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build config xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_v4addr(addr): + """check if ipv4 addr is valid""" + if addr.find('.') != -1: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + return False + + +def is_valid_v6addr(addr): + """check if ipv6 addr is valid""" + if addr.find(':') != -1: + addr_list = addr.split(':') + if len(addr_list) > 6: + return False + if addr_list[1] == "": + return False + return True + return False + + +def is_valid_tag(tag): + """check if the tag is valid""" + + if int(tag) < 1 or int(tag) > 4294967295: + return False + return True + + +def is_valid_bdf_interval(interval): + """check if the min_tx_interva,min-rx-interval is valid""" + + if interval < 50 or interval > 1000: + return False + return True + + +def is_valid_bdf_multiplier(multiplier): + """check if the detect_multiplier is valid""" + + if multiplier < 3 or multiplier > 50: + return False + return True + + +def is_valid_bdf_session_name(session_name): + """check if the bfd_session_name is valid""" + if session_name.find(' ') != -1: + return False + if len(session_name) < 1 or len(session_name) > 15: + return False + return True + + +def is_valid_preference(pref): + """check if the preference is valid""" + + if int(pref) > 0 and int(pref) < 256: + return True + return False + + +def is_valid_description(description): + """check if the description is valid""" + if description.find('?') != -1: + return False + if len(description) < 1 or len(description) > 255: + return False + return True + + +def compare_command(commands): + """check if the commands is valid""" + if len(commands) < 3: + return True + if commands[0] != 'sys' or commands[1] != 'undo ip route-static default-bfd' \ + or commands[2] != 'commit': + return True + + +def get_to_lines(stdout): + """data conversion""" + lines = list() + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + lines.append(item) + return lines + + +def get_change_state(oldvalue, newvalue, change): + """get change state""" + if newvalue is not None: + if oldvalue != str(newvalue): + change = True + else: + if oldvalue != newvalue: + change = True + return change + + +def get_xml(xml, value): + """operate xml""" + if value is None: + value = '' + else: + value = value + tempxml = xml % value + return tempxml + + +class StaticRouteBFD(object): + """static route module""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self._initmodule_() + + # static route info + self.function_flag = self.module.params['function_flag'] + self.aftype = self.module.params['aftype'] + self.state = self.module.params['state'] + if self.aftype == "v4": + self.version = "ipv4unicast" + else: + self.version = "ipv6unicast" + if self.function_flag != 'globalBFD': + self.nhp_interface = self.module.params['nhp_interface'] + if self.nhp_interface is None: + self.nhp_interface = "Invalid0" + + self.destvrf = self.module.params['destvrf'] + if self.destvrf is None: + self.destvrf = "_public_" + + self.next_hop = self.module.params['next_hop'] + self.prefix = self.module.params['prefix'] + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.mask = self.module.params['mask'] + self.tag = self.module.params['tag'] + self.description = self.module.params['description'] + self.pref = self.module.params['pref'] + if self.pref is None: + self.pref = 60 + # vpn instance info + self.vrf = self.module.params['vrf'] + if self.vrf is None: + self.vrf = "_public_" + # bfd session name + self.bfd_session_name = self.module.params['bfd_session_name'] + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.min_tx_interval = self.module.params['min_tx_interval'] + self.min_rx_interval = self.module.params['min_rx_interval'] + self.detect_multiplier = self.module.params['detect_multiplier'] + if self.function_flag == 'globalBFD' and self.state == 'absent': + self.commands = self.module.params['commands'] + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.static_routes_info = dict() + + def _initmodule_(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=False) + + def _checkresponse_(self, xml_str, xml_name): + """check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def _convertlentomask_(self, masklen): + """convert mask length to ip address mask, i.e. 24 to 255.255.255.0""" + + mask_int = ["0"] * 4 + length = int(masklen) + + if length > 32: + self.module.fail_json(msg='IPv4 ipaddress mask length is invalid') + if length < 8: + mask_int[0] = str(int((0xFF << (8 - length % 8)) & 0xFF)) + if length >= 8: + mask_int[0] = '255' + mask_int[1] = str(int((0xFF << (16 - (length % 16))) & 0xFF)) + if length >= 16: + mask_int[1] = '255' + mask_int[2] = str(int((0xFF << (24 - (length % 24))) & 0xFF)) + if length >= 24: + mask_int[2] = '255' + mask_int[3] = str(int((0xFF << (32 - (length % 32))) & 0xFF)) + if length == 32: + mask_int[3] = '255' + + return '.'.join(mask_int) + + def _convertipprefix_(self): + """convert prefix to real value i.e. 2.2.2.2/24 to 2.2.2.0/24""" + if self.function_flag == 'singleBFD': + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + else: + if self.prefix.find(':') == -1: + return False + else: + if self.aftype == "v4": + if self.prefix.find('.') == -1: + return False + if self.mask == '32': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '0.0.0.0' + return True + addr_list = self.prefix.split('.') + length = len(addr_list) + if length > 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + byte_len = 8 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + else: + if self.prefix.find(':') == -1: + return False + if self.mask == '128': + self.prefix = self.prefix + return True + if self.mask == '0': + self.prefix = '::' + return True + addr_list = self.prefix.split(':') + length = len(addr_list) + if length > 6: + return False + byte_len = 16 + ip_len = int(self.mask) // byte_len + ip_bit = int(self.mask) % byte_len + + if self.aftype == "v4": + for i in range(ip_len + 1, length): + addr_list[i] = 0 + else: + for i in range(length - ip_len, length): + addr_list[i] = 0 + for j in range(0, byte_len - ip_bit): + if self.aftype == "v4": + addr_list[ip_len] = int(addr_list[ip_len]) & (0 << j) + else: + if addr_list[length - ip_len - 1] == "": + continue + addr_list[length - ip_len - + 1] = '0x%s' % addr_list[length - ip_len - 1] + addr_list[length - ip_len - + 1] = int(addr_list[length - ip_len - 1], 16) & (0 << j) + + if self.aftype == "v4": + self.prefix = '%s.%s.%s.%s' % (addr_list[0], addr_list[1], addr_list[2], addr_list[3]) + return True + if self.aftype == "v6": + ipv6_addr_str = "" + for num in range(0, length - ip_len): + ipv6_addr_str += '%s:' % addr_list[num] + self.prefix = ipv6_addr_str + + return True + + def set_update_cmd_globalbfd(self): + """set globalBFD update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('ip route-static default-bfd') + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + self.updates_cmd.append('undo ip route-static default-bfd') + + def set_update_cmd_singlebfd(self): + """set singleBFD update command""" + if not self.changed: + return + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.prefix == "0.0.0.0": + prefix = '' + else: + prefix = self.prefix + if self.state == "present": + if nhp_interface: + self.updates_cmd.append('ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static bfd %s' % (next_hop)) + if prefix: + self.updates_cmd.append(' local-address %s' % (self.prefix)) + if self.min_tx_interval: + self.updates_cmd.append(' min-rx-interval %s' % (self.min_tx_interval)) + if self.min_rx_interval: + self.updates_cmd.append(' min-tx-interval %s' % (self.min_rx_interval)) + if self.detect_multiplier: + self.updates_cmd.append(' detect-multiplier %s' % (self.detect_multiplier)) + else: + if nhp_interface: + self.updates_cmd.append('undo ip route-static bfd %s %s' % (nhp_interface, next_hop)) + elif destvrf: + self.updates_cmd.append('undo ip route-static bfd vpn-instance %s %s' % (destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static bfd %s' % (next_hop)) + + def set_update_cmd(self): + """set update command""" + if not self.changed: + return + + if self.aftype == "v4": + maskstr = self._convertlentomask_(self.mask) + else: + maskstr = self.mask + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if self.next_hop is None: + next_hop = '' + else: + next_hop = self.next_hop + if self.vrf == "_public_": + vrf = '' + else: + vrf = self.vrf + if self.destvrf == "_public_": + destvrf = '' + else: + destvrf = self.destvrf + if self.nhp_interface == "Invalid0": + nhp_interface = '' + else: + nhp_interface = self.nhp_interface + if self.state == "present": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, next_hop)) + else: + self.updates_cmd.append('ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + if self.pref != 60: + self.updates_cmd.append(' preference %s' % (self.pref)) + if self.tag: + self.updates_cmd.append(' tag %s' % (self.tag)) + if not static_bfd_flag: + self.updates_cmd.append(' track bfd-session %s' % (self.bfd_session_name)) + else: + self.updates_cmd.append(' bfd enable') + if self.description: + self.updates_cmd.append(' description %s' % (self.description)) + + if self.state == "absent": + if self.vrf != "_public_": + if self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s vpn-instance %s %s' + % (vrf, self.prefix, maskstr, destvrf, next_hop)) + else: + self.updates_cmd.append('undo ip route-static vpn-instance %s %s %s %s %s' + % (vrf, self.prefix, maskstr, nhp_interface, next_hop)) + elif self.destvrf != "_public_": + self.updates_cmd.append('undo ip route-static %s %s vpn-instance %s %s' + % (self.prefix, maskstr, self.destvrf, self.next_hop)) + else: + self.updates_cmd.append('undo ip route-static %s %s %s %s' + % (self.prefix, maskstr, nhp_interface, next_hop)) + + def operate_static_route_globalbfd(self): + """set globalbfd update command""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + if self.state == "present": + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + if self.state == "absent" and self.commands: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % 1000 + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % 1000 + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % 3 + + configxmlstr = CE_NC_SET_IPV4_STATIC_ROUTE_GLOBALBFD % ( + min_tx_interval_xml, min_rx_interval_xml, multiplier_xml) + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_globalBFD") + + def operate_static_route_singlebfd(self, version, prefix, nhp_interface, next_hop, destvrf, state): + """operate ipv4 static route singleBFD""" + min_tx_interval = self.min_tx_interval + min_rx_interval = self.min_rx_interval + multiplier = self.detect_multiplier + min_tx_interval_xml = """\n""" + min_rx_interval_xml = """\n""" + multiplier_xml = """\n""" + local_address_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + if nhp_interface is None: + nhp_interface = "Invalid0" + + if min_tx_interval is not None: + min_tx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINTX % min_tx_interval + if min_rx_interval is not None: + min_rx_interval_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MINRX % min_rx_interval + if multiplier is not None: + multiplier_xml = CE_NC_SET_IPV4_STATIC_ROUTE_BFDCOMMON_MUL % multiplier + + if prefix is not None: + local_address_xml = CE_NC_SET_STATIC_ROUTE_SINGLEBFD_LOCALADRESS % prefix + + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, + next_hop, local_address_xml, min_tx_interval_xml, + min_rx_interval_xml, multiplier_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE_SINGLEBFD % ( + version, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE_singleBFD") + + def operate_static_route(self, version, prefix, mask, nhp_interface, next_hop, vrf, destvrf, state): + """operate ipv4 static route""" + description_xml = """\n""" + preference_xml = """\n""" + tag_xml = """\n""" + bfd_xml = """\n""" + if next_hop is None: + next_hop = '0.0.0.0' + if nhp_interface is None: + nhp_interface = "Invalid0" + + if vrf is None: + vpn_instance = "_public_" + else: + vpn_instance = vrf + + if destvrf is None: + dest_vpn_instance = "_public_" + else: + dest_vpn_instance = destvrf + + description_xml = get_xml(CE_NC_SET_DESCRIPTION, self.description) + + preference_xml = get_xml(CE_NC_SET_PREFERENCE, self.pref) + + tag_xml = get_xml(CE_NC_SET_TAG, self.tag) + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + bfd_xml = CE_NC_SET_BFDSESSIONNAME % self.bfd_session_name + else: + bfd_xml = CE_NC_SET_BFDENABLE + if state == "present": + configxmlstr = CE_NC_SET_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, + dest_vpn_instance, next_hop, description_xml, preference_xml, tag_xml, bfd_xml) + + else: + configxmlstr = CE_NC_DELETE_STATIC_ROUTE % ( + vpn_instance, version, prefix, mask, nhp_interface, dest_vpn_instance, next_hop) + + conf_str = build_config_xml(configxmlstr) + recv_xml = set_nc_config(self.module, conf_str) + self._checkresponse_(recv_xml, "OPERATE_STATIC_ROUTE") + + def get_change_state_global_bfd(self): + """get ipv4 global bfd change state""" + + self.get_global_bfd(self.state) + change = False + if self.state == "present": + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if self.min_tx_interval is not None: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval is not None: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier is not None: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + else: + continue + else: + change = True + else: + if self.commands: + if self.static_routes_info["sroute_global_bfd"]: + for static_route in self.static_routes_info["sroute_global_bfd"]: + if static_route is not None: + if int(static_route["minTxInterval"]) != 1000 or \ + int(static_route["minRxInterval"]) != 1000 or \ + int(static_route["multiplier"]) != 3: + change = True + return change + + def get_global_bfd(self, state): + """get ipv4 global bfd""" + + self.static_routes_info["sroute_global_bfd"] = list() + + getglobalbfdxmlstr = None + if self.aftype == 'v4': + getglobalbfdxmlstr = CE_NC_GET_STATIC_ROUTE_IPV4_GLOBAL_BFD + + if getglobalbfdxmlstr is not None: + xml_global_bfd_str = get_nc_config(self.module, getglobalbfdxmlstr) + + if 'data/' in xml_global_bfd_str: + return + + xml_global_bfd_str = xml_global_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_global_bfd_str) + static_routes_global_bfd = root.findall( + "staticrt/staticrtbase/srIPv4StaticSite") + + if static_routes_global_bfd: + for static_route in static_routes_global_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + + self.static_routes_info["sroute_global_bfd"].append(static_info) + + def get_change_state_single_bfd(self): + """get ipv4 single bfd change state""" + + self.get_single_bfd(self.state) + change = False + version = self.version + if self.state == 'present': + if self.static_routes_info["sroute_single_bfd"]: + for static_route in self.static_routes_info["sroute_single_bfd"]: + if static_route is not None and static_route['afType'] == version: + if self.nhp_interface: + if static_route["ifName"].lower() != self.nhp_interface.lower(): + change = True + if self.destvrf: + if static_route["destVrfName"].lower() != self.destvrf.lower(): + change = True + if self.next_hop: + if static_route["nexthop"].lower() != self.next_hop.lower(): + change = True + if self.prefix: + if static_route["localAddress"].lower() != self.prefix.lower(): + change = True + if self.min_tx_interval: + if int(static_route["minTxInterval"]) != self.min_tx_interval: + change = True + if self.min_rx_interval: + if int(static_route["minRxInterval"]) != self.min_rx_interval: + change = True + if self.detect_multiplier: + if int(static_route["multiplier"]) != self.detect_multiplier: + change = True + return change + + else: + continue + else: + change = True + else: + for static_route in self.static_routes_info["sroute_single_bfd"]: + # undo ip route-static bfd [ interface-type interface-number | + # vpn-instance vpn-instance-name ] nexthop-address + + if static_route["ifName"] and self.nhp_interface: + if static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["destVrfName"] and self.destvrf: + if static_route["destVrfName"].lower() == self.destvrf.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + + if static_route["nexthop"] and self.next_hop: + if static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_single_bfd(self, state): + """get ipv4 sigle bfd""" + self.static_routes_info["sroute_single_bfd"] = list() + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + if state == 'absent': + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ABSENT % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + else: + getbfdxmlstr = CE_NC_GET_STATIC_ROUTE_BFD % ( + version, self.nhp_interface, self.destvrf, self.next_hop) + xml_bfd_str = get_nc_config(self.module, getbfdxmlstr) + + if 'data/' in xml_bfd_str: + return + xml_bfd_str = xml_bfd_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_bfd_str) + static_routes_bfd = root.findall( + "staticrt/staticrtbase/srBfdParas/srBfdPara") + if static_routes_bfd: + for static_route in static_routes_bfd: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["afType", "destVrfName", "nexthop", "ifName"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "localAddress": + if static_ele.text is not None: + static_info["localAddress"] = static_ele.text + else: + static_info["localAddress"] = "None" + if static_ele.tag == "minTxInterval": + if static_ele.text is not None: + static_info["minTxInterval"] = static_ele.text + if static_ele.tag == "minRxInterval": + if static_ele.text is not None: + static_info["minRxInterval"] = static_ele.text + if static_ele.tag == "multiplier": + if static_ele.text is not None: + static_info["multiplier"] = static_ele.text + self.static_routes_info["sroute_single_bfd"].append(static_info) + + def get_static_route(self, state): + """get ipv4 static route about BFD""" + self.static_routes_info["sroute"] = list() + # Increase the parameter used to distinguish whether the incoming bfdSessionName + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + + if state == 'absent': + getxmlstr = CE_NC_GET_STATIC_ROUTE_ABSENT + else: + # self.static_bfd_flag is true + if static_bfd_flag: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_ENABLE + + else: + getxmlstr = CE_NC_GET_STATIC_ROUTE_BFD_SESSIONNAME + xml_str = get_nc_config(self.module, getxmlstr) + if 'data/' in xml_str: + return + xml_str = xml_str.replace('\r', '').replace('\n', ''). \ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + root = ElementTree.fromstring(xml_str) + static_routes = root.findall( + "staticrt/staticrtbase/srRoutes/srRoute") + + if static_routes: + for static_route in static_routes: + static_info = dict() + for static_ele in static_route: + if static_ele.tag in ["vrfName", "afType", "topologyName", + "prefix", "maskLength", "destVrfName", + "nexthop", "ifName", "preference", "description"]: + static_info[static_ele.tag] = static_ele.text + if static_ele.tag == "tag": + if static_ele.text is not None: + static_info["tag"] = static_ele.text + else: + static_info["tag"] = "None" + if static_bfd_flag: + if static_ele.tag == "bfdEnable": + if static_ele.text is not None: + static_info["bfdEnable"] = static_ele.text + else: + static_info["bfdEnable"] = "None" + else: + if static_ele.tag == "sessionName": + if static_ele.text is not None: + static_info["sessionName"] = static_ele.text + else: + static_info["sessionName"] = "None" + self.static_routes_info["sroute"].append(static_info) + + def _checkparams_(self): + """check all input params""" + if self.function_flag == 'singleBFD': + if not self.next_hop: + self.module.fail_json(msg='Error: missing required argument: next_hop.') + if self.state != 'absent': + if self.nhp_interface == "Invalid0" and (not self.prefix or self.prefix == '0.0.0.0'): + self.module.fail_json(msg='Error: If a nhp_interface is not configured, ' + 'the prefix must be configured.') + + if self.function_flag != 'globalBFD': + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if not self.mask: + self.module.fail_json(msg='Error: missing required argument: mask.') + # check prefix and mask + if not self.mask.isdigit(): + self.module.fail_json(msg='Error: Mask is invalid.') + if self.function_flag != 'singleBFD' or (self.function_flag == 'singleBFD' and self.destvrf != "_public_"): + if not self.prefix: + self.module.fail_json(msg='Error: missing required argument: prefix.') + # convert prefix + if not self._convertipprefix_(): + self.module.fail_json(msg='Error: The %s is not a valid address' % self.prefix) + + if self.nhp_interface != "Invalid0" and self.destvrf != "_public_": + self.module.fail_json(msg='Error: Destination vrf dose not support next hop is interface.') + + if not self.next_hop and self.nhp_interface == "Invalid0": + self.module.fail_json(msg='Error: one of the following is required: next_hop,nhp_interface.') + + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + # description check + if self.description: + if not is_valid_description(self.description): + self.module.fail_json( + msg='Error: Dsecription length should be 1 - 35, and can not contain "?".') + # tag check + if self.tag is not None: + if not is_valid_tag(self.tag): + self.module.fail_json( + msg='Error: Tag should be integer 1 - 4294967295.') + # preference check + if self.pref is not None: + if not is_valid_preference(self.pref): + self.module.fail_json( + msg='Error: Preference should be integer 1 - 255.') + + if self.function_flag == 'staticBFD': + if self.bfd_session_name: + if not is_valid_bdf_session_name(self.bfd_session_name): + self.module.fail_json( + msg='Error: bfd_session_name length should be 1 - 15, and can not contain Space.') + + # ipv4 check + if self.aftype == "v4": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 32 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv4 mask must be an integer between 1 and 32.') + # next_hop check + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v4addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + # ipv6 check + if self.aftype == "v6": + if self.function_flag == 'dynamicBFD' or self.function_flag == 'staticBFD': + if int(self.mask) > 128 or int(self.mask) < 0: + self.module.fail_json( + msg='Error: Ipv6 mask must be an integer between 1 and 128.') + if self.function_flag != 'globalBFD': + if self.next_hop: + if not is_valid_v6addr(self.next_hop): + self.module.fail_json( + msg='Error: The %s is not a valid address.' % self.next_hop) + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + # BFD prarams + if self.min_tx_interval: + if not is_valid_bdf_interval(self.min_tx_interval): + self.module.fail_json( + msg='Error: min_tx_interval should be integer 50 - 1000.') + if self.min_rx_interval: + if not is_valid_bdf_interval(self.min_rx_interval): + self.module.fail_json( + msg='Error: min_rx_interval should be integer 50 - 1000.') + if self.detect_multiplier: + if not is_valid_bdf_multiplier(self.detect_multiplier): + self.module.fail_json( + msg='Error: detect_multiplier should be integer 3 - 50.') + + if self.function_flag == 'globalBFD': + if self.state != 'absent': + if not self.min_tx_interval and not self.min_rx_interval and not self.detect_multiplier: + self.module.fail_json( + msg='Error: one of the following is required: min_tx_interval,' + 'detect_multiplier,min_rx_interval.') + else: + if not self.commands: + self.module.fail_json( + msg='Error: missing required argument: command.') + if compare_command(self.commands): + self.module.fail_json( + msg='Error: The command %s line is incorrect.' % (',').join(self.commands)) + + def set_ip_static_route_globalbfd(self): + """set ip static route globalBFD""" + if not self.changed: + return + if self.aftype == "v4": + self.operate_static_route_globalbfd() + + def set_ip_static_route_singlebfd(self): + """set ip static route singleBFD""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route_singlebfd(version, self.prefix, self.nhp_interface, + self.next_hop, self.destvrf, self.state) + + def set_ip_static_route(self): + """set ip static route""" + if not self.changed: + return + version = None + if self.aftype == "v4": + version = "ipv4unicast" + else: + version = "ipv6unicast" + self.operate_static_route(version, self.prefix, self.mask, self.nhp_interface, + self.next_hop, self.vrf, self.destvrf, self.state) + + def is_prefix_exist(self, static_route, version): + """is prefix mask nex_thop exist""" + if static_route is None: + return False + if self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if self.next_hop and not self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["nexthop"].lower() == self.next_hop.lower() + + if not self.next_hop and self.nhp_interface: + return static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["afType"] == version \ + and static_route["ifName"].lower() == self.nhp_interface.lower() + + def get_ip_static_route(self): + """get ip static route""" + change = False + version = self.version + self.get_static_route(self.state) + change_list = list() + if self.state == 'present': + for static_route in self.static_routes_info["sroute"]: + if self.is_prefix_exist(static_route, self.version): + info_dict = dict() + exist_dict = dict() + if self.vrf: + info_dict["vrfName"] = self.vrf + exist_dict["vrfName"] = static_route["vrfName"] + if self.destvrf: + info_dict["destVrfName"] = self.destvrf + exist_dict["destVrfName"] = static_route["destVrfName"] + if self.description: + info_dict["description"] = self.description + exist_dict["description"] = static_route["description"] + if self.tag: + info_dict["tag"] = self.tag + exist_dict["tag"] = static_route["tag"] + if self.pref: + info_dict["preference"] = str(self.pref) + exist_dict["preference"] = static_route["preference"] + if self.nhp_interface: + if self.nhp_interface.lower() == "invalid0": + info_dict["ifName"] = "Invalid0" + else: + info_dict["ifName"] = "Invalid0" + exist_dict["ifName"] = static_route["ifName"] + if self.next_hop: + info_dict["nexthop"] = self.next_hop + exist_dict["nexthop"] = static_route["nexthop"] + + if self.bfd_session_name: + info_dict["bfdEnable"] = 'true' + + else: + info_dict["bfdEnable"] = 'false' + exist_dict["bfdEnable"] = static_route["bfdEnable"] + + if exist_dict != info_dict: + change = True + else: + change = False + change_list.append(change) + + if False in change_list: + change = False + else: + change = True + return change + + else: + for static_route in self.static_routes_info["sroute"]: + if static_route["nexthop"] and self.next_hop: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["nexthop"].lower() == self.next_hop.lower() \ + and static_route["afType"] == version: + change = True + return change + if static_route["ifName"] and self.nhp_interface: + if static_route["prefix"].lower() == self.prefix.lower() \ + and static_route["maskLength"] == self.mask \ + and static_route["ifName"].lower() == self.nhp_interface.lower() \ + and static_route["afType"] == version: + change = True + return change + else: + continue + change = False + return change + + def get_proposed(self): + """get proposed information""" + self.proposed['afType'] = self.aftype + self.proposed['state'] = self.state + if self.function_flag != 'globalBFD': + self.proposed['ifName'] = self.nhp_interface + self.proposed['destVrfName'] = self.destvrf + self.proposed['next_hop'] = self.next_hop + + if self.function_flag == 'singleBFD': + if self.prefix: + self.proposed['localAddress'] = self.prefix + + if self.function_flag == 'globalBFD' or self.function_flag == 'singleBFD': + self.proposed['minTxInterval'] = self.min_tx_interval + self.proposed['minRxInterval'] = self.min_rx_interval + self.proposed['multiplier'] = self.detect_multiplier + + if self.function_flag != 'globalBFD' and self.function_flag != 'singleBFD': + self.proposed['prefix'] = self.prefix + self.proposed['mask'] = self.mask + self.proposed['vrfName'] = self.vrf + if self.tag: + self.proposed['tag'] = self.tag + if self.description: + self.proposed['description'] = self.description + if self.pref is None: + self.proposed['preference'] = 60 + else: + self.proposed['preference'] = self.pref + + static_bfd_flag = True + if self.bfd_session_name: + static_bfd_flag = False + if not static_bfd_flag: + self.proposed['sessionName'] = self.bfd_session_name + else: + self.proposed['bfdEnable'] = 'true' + + def get_existing(self): + """get existing information""" + # globalBFD + if self.function_flag == 'globalBFD': + change = self.get_change_state_global_bfd() + self.existing['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + change = self.get_change_state_single_bfd() + self.existing['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + change = self.get_ip_static_route() + self.existing['static_sroute'] = self.static_routes_info["sroute"] + self.changed = bool(change) + + def get_end_state(self): + """get end state information""" + + # globalBFD + if self.function_flag == 'globalBFD': + self.get_global_bfd(self.state) + self.end_state['sroute_global_bfd'] = self.static_routes_info["sroute_global_bfd"] + # singleBFD + elif self.function_flag == 'singleBFD': + self.static_routes_info["sroute_single_bfd"] = list() + self.get_single_bfd(self.state) + self.end_state['sroute_single_bfd'] = self.static_routes_info["sroute_single_bfd"] + # dynamicBFD / staticBFD + else: + self.get_static_route(self.state) + self.end_state['static_sroute'] = self.static_routes_info["sroute"] + + def work(self): + """worker""" + self._checkparams_() + self.get_existing() + self.get_proposed() + + if self.function_flag == 'globalBFD': + self.set_ip_static_route_globalbfd() + self.set_update_cmd_globalbfd() + elif self.function_flag == 'singleBFD': + self.set_ip_static_route_singlebfd() + self.set_update_cmd_singlebfd() + else: + self.set_ip_static_route() + self.set_update_cmd() + + self.get_end_state() + if self.existing == self.end_state: + self.changed = False + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + aftype=dict(choices=['v4', 'v6'], required=True), + next_hop=dict(type='str'), + nhp_interface=dict(type='str'), + vrf=dict(type='str'), + destvrf=dict(type='str'), + tag=dict(type='int'), + description=dict(type='str'), + pref=dict(type='int'), + # bfd + function_flag=dict(required=True, choices=['globalBFD', 'singleBFD', 'dynamicBFD', 'staticBFD']), + min_tx_interval=dict(type='int'), + min_rx_interval=dict(type='int'), + detect_multiplier=dict(type='int'), + # bfd session name + bfd_session_name=dict(type='str'), + commands=dict(type='list', required=False), + state=dict(choices=['absent', 'present'], default='present', required=False), + ) + interface = StaticRouteBFD(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_stp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_stp.py new file mode 100644 index 00000000..0f3d67ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_stp.py @@ -0,0 +1,969 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_stp +short_description: Manages STP configuration on HUAWEI CloudEngine switches. +description: + - Manages STP configurations on HUAWEI CloudEngine switches. +author: + - wangdezhuang (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + state: + description: + - Specify desired state of the resource. + default: present + choices: ['present', 'absent'] + stp_mode: + description: + - Set an operation mode for the current MSTP process. + The mode can be STP, RSTP, or MSTP. + choices: ['stp', 'rstp', 'mstp'] + stp_enable: + description: + - Enable or disable STP on a switch. + choices: ['enable', 'disable'] + stp_converge: + description: + - STP convergence mode. + Fast means set STP aging mode to Fast. + Normal means set STP aging mode to Normal. + choices: ['fast', 'normal'] + bpdu_protection: + description: + - Configure BPDU protection on an edge port. + This function prevents network flapping caused by attack packets. + choices: ['enable', 'disable'] + tc_protection: + description: + - Configure the TC BPDU protection function for an MSTP process. + choices: ['enable', 'disable'] + tc_protection_interval: + description: + - Set the time the MSTP device takes to handle the maximum number of TC BPDUs + and immediately refresh forwarding entries. + The value is an integer ranging from 1 to 600, in seconds. + tc_protection_threshold: + description: + - Set the maximum number of TC BPDUs that the MSTP can handle. + The value is an integer ranging from 1 to 255. The default value is 1 on the switch. + interface: + description: + - Interface name. + If the value is C(all), will apply configuration to all interfaces. + if the value is a special name, only support input the full name. + edged_port: + description: + - Set the current port as an edge port. + choices: ['enable', 'disable'] + bpdu_filter: + description: + - Specify a port as a BPDU filter port. + choices: ['enable', 'disable'] + cost: + description: + - Set the path cost of the current port. + The default instance is 0. + root_protection: + description: + - Enable root protection on the current port. + choices: ['enable', 'disable'] + loop_protection: + description: + - Enable loop protection on the current port. + choices: ['enable', 'disable'] +''' + +EXAMPLES = ''' + +- name: CloudEngine stp test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Config stp mode" + community.network.ce_stp: + state: present + stp_mode: stp + provider: "{{ cli }}" + + - name: "Undo stp mode" + community.network.ce_stp: + state: absent + stp_mode: stp + provider: "{{ cli }}" + + - name: "Enable bpdu protection" + community.network.ce_stp: + state: present + bpdu_protection: enable + provider: "{{ cli }}" + + - name: "Disable bpdu protection" + community.network.ce_stp: + state: present + bpdu_protection: disable + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"bpdu_protection": "enable", + "state": "present"} +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: {"bpdu_protection": "disable"} +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: {"bpdu_protection": "enable"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["stp bpdu-protection"] +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import exec_command, load_config, ce_argument_spec + + +def get_config(module, flags): + + """Retrieves the current config from the device or cache""" + + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg=err) + config = str(out).strip() + if config.startswith("display"): + configs = config.split("\n") + if len(configs) > 1: + return "\n".join(configs[1:]) + else: + return "" + else: + return config + + +class Stp(object): + """ Manages stp/rstp/mstp configuration """ + + def __init__(self, **kwargs): + """ Stp module init """ + + # module + argument_spec = kwargs["argument_spec"] + self.spec = argument_spec + self.module = AnsibleModule(argument_spec=self.spec, supports_check_mode=True) + + # config + self.cur_cfg = dict() + self.stp_cfg = None + self.interface_stp_cfg = None + + # module args + self.state = self.module.params['state'] or None + self.stp_mode = self.module.params['stp_mode'] or None + self.stp_enable = self.module.params['stp_enable'] or None + self.stp_converge = self.module.params['stp_converge'] or None + self.interface = self.module.params['interface'] or None + self.edged_port = self.module.params['edged_port'] or None + self.bpdu_filter = self.module.params['bpdu_filter'] or None + self.cost = self.module.params['cost'] or None + self.bpdu_protection = self.module.params['bpdu_protection'] or None + self.tc_protection = self.module.params['tc_protection'] or None + self.tc_protection_interval = self.module.params['tc_protection_interval'] or None + self.tc_protection_threshold = self.module.params['tc_protection_threshold'] or None + self.root_protection = self.module.params['root_protection'] or None + self.loop_protection = self.module.params['loop_protection'] or None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def cli_load_config(self, commands): + """ Cli load configuration """ + + if not self.module.check_mode: + load_config(self.module, commands) + + def cli_get_stp_config(self): + """ Cli get stp configuration """ + + flags = [r"| section include #\s*\n\s*stp", r"| section exclude #\s*\n+\s*stp process \d+"] + self.stp_cfg = get_config(self.module, flags) + + def cli_get_interface_stp_config(self): + """ Cli get interface's stp configuration """ + + if self.interface: + regular = r"| ignore-case section include ^#\s+interface %s\s+" % self.interface.replace(" ", "") + flags = list() + flags.append(regular) + tmp_cfg = get_config(self.module, flags) + + if not tmp_cfg: + self.module.fail_json( + msg='Error: The interface %s is not exist.' % self.interface) + + if "undo portswitch" in tmp_cfg: + self.module.fail_json( + msg='Error: The interface %s is not switch mode.' % self.interface) + + self.interface_stp_cfg = tmp_cfg + + def check_params(self): + """ Check module params """ + + if self.cost: + if self.cost.isdigit(): + if int(self.cost) < 1 or int(self.cost) > 200000000: + self.module.fail_json( + msg='Error: The value of cost is out of [1 - 200000000].') + else: + self.module.fail_json( + msg='Error: The cost is not digit.') + + if self.tc_protection_interval: + if self.tc_protection_interval.isdigit(): + if int(self.tc_protection_interval) < 1 or int(self.tc_protection_interval) > 600: + self.module.fail_json( + msg='Error: The value of tc_protection_interval is out of [1 - 600].') + else: + self.module.fail_json( + msg='Error: The tc_protection_interval is not digit.') + + if self.tc_protection_threshold: + if self.tc_protection_threshold.isdigit(): + if int(self.tc_protection_threshold) < 1 or int(self.tc_protection_threshold) > 255: + self.module.fail_json( + msg='Error: The value of tc_protection_threshold is out of [1 - 255].') + else: + self.module.fail_json( + msg='Error: The tc_protection_threshold is not digit.') + + if self.root_protection or self.loop_protection or self.cost: + if not self.interface: + self.module.fail_json( + msg='Error: Please input interface.') + elif self.interface == "all": + self.module.fail_json( + msg='Error: Interface can not be all when config root_protection or loop_protection or cost.') + + if self.root_protection and self.root_protection == "enable": + if self.loop_protection and self.loop_protection == "enable": + self.module.fail_json( + msg='Error: Can not enable root_protection and loop_protection at the same interface.') + + if self.edged_port or self.bpdu_filter: + if not self.interface: + self.module.fail_json( + msg='Error: Please input interface.') + + def get_proposed(self): + """ Get module proposed """ + + self.proposed["state"] = self.state + + if self.stp_mode: + self.proposed["stp_mode"] = self.stp_mode + if self.stp_enable: + self.proposed["stp_enable"] = self.stp_enable + if self.stp_converge: + self.proposed["stp_converge"] = self.stp_converge + if self.interface: + self.proposed["interface"] = self.interface + if self.edged_port: + self.proposed["edged_port"] = self.edged_port + if self.bpdu_filter: + self.proposed["bpdu_filter"] = self.bpdu_filter + if self.cost: + self.proposed["cost"] = self.cost + if self.bpdu_protection: + self.proposed["bpdu_protection"] = self.bpdu_protection + if self.tc_protection: + self.proposed["tc_protection"] = self.tc_protection + if self.tc_protection_interval: + self.proposed["tc_protection_interval"] = self.tc_protection_interval + if self.tc_protection_threshold: + self.proposed["tc_protection_threshold"] = self.tc_protection_threshold + if self.root_protection: + self.proposed["root_protection"] = self.root_protection + if self.loop_protection: + self.proposed["loop_protection"] = self.loop_protection + + def get_existing(self): + """ Get existing configuration """ + + self.cli_get_stp_config() + if self.interface and self.interface != "all": + self.cli_get_interface_stp_config() + + if self.stp_mode: + if "stp mode stp" in self.stp_cfg: + self.cur_cfg["stp_mode"] = "stp" + self.existing["stp_mode"] = "stp" + elif "stp mode rstp" in self.stp_cfg: + self.cur_cfg["stp_mode"] = "rstp" + self.existing["stp_mode"] = "rstp" + else: + self.cur_cfg["stp_mode"] = "mstp" + self.existing["stp_mode"] = "mstp" + + if self.stp_enable: + if "stp disable" in self.stp_cfg: + self.cur_cfg["stp_enable"] = "disable" + self.existing["stp_enable"] = "disable" + else: + self.cur_cfg["stp_enable"] = "enable" + self.existing["stp_enable"] = "enable" + + if self.stp_converge: + if "stp converge fast" in self.stp_cfg: + self.cur_cfg["stp_converge"] = "fast" + self.existing["stp_converge"] = "fast" + else: + self.cur_cfg["stp_converge"] = "normal" + self.existing["stp_converge"] = "normal" + + if self.edged_port: + if self.interface == "all": + if "stp edged-port default" in self.stp_cfg: + self.cur_cfg["edged_port"] = "enable" + self.existing["edged_port"] = "enable" + else: + self.cur_cfg["edged_port"] = "disable" + self.existing["edged_port"] = "disable" + else: + if "stp edged-port enable" in self.interface_stp_cfg: + self.cur_cfg["edged_port"] = "enable" + self.existing["edged_port"] = "enable" + else: + self.cur_cfg["edged_port"] = "disable" + self.existing["edged_port"] = "disable" + + if self.bpdu_filter: + if self.interface == "all": + if "stp bpdu-filter default" in self.stp_cfg: + self.cur_cfg["bpdu_filter"] = "enable" + self.existing["bpdu_filter"] = "enable" + else: + self.cur_cfg["bpdu_filter"] = "disable" + self.existing["bpdu_filter"] = "disable" + else: + if "stp bpdu-filter enable" in self.interface_stp_cfg: + self.cur_cfg["bpdu_filter"] = "enable" + self.existing["bpdu_filter"] = "enable" + else: + self.cur_cfg["bpdu_filter"] = "disable" + self.existing["bpdu_filter"] = "disable" + + if self.bpdu_protection: + if "stp bpdu-protection" in self.stp_cfg: + self.cur_cfg["bpdu_protection"] = "enable" + self.existing["bpdu_protection"] = "enable" + else: + self.cur_cfg["bpdu_protection"] = "disable" + self.existing["bpdu_protection"] = "disable" + + if self.tc_protection: + pre_cfg = self.stp_cfg.split("\n") + if "stp tc-protection" in pre_cfg: + self.cur_cfg["tc_protection"] = "enable" + self.existing["tc_protection"] = "enable" + else: + self.cur_cfg["tc_protection"] = "disable" + self.existing["tc_protection"] = "disable" + + if self.tc_protection_interval: + if "stp tc-protection interval" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection interval (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection interval on the device.') + self.cur_cfg["tc_protection_interval"] = tmp_value[0] + self.existing["tc_protection_interval"] = tmp_value[0] + else: + self.cur_cfg["tc_protection_interval"] = "null" + self.existing["tc_protection_interval"] = "null" + + if self.tc_protection_threshold: + if "stp tc-protection threshold" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection threshold (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection threshold on the device.') + self.cur_cfg["tc_protection_threshold"] = tmp_value[0] + self.existing["tc_protection_threshold"] = tmp_value[0] + else: + self.cur_cfg["tc_protection_threshold"] = "1" + self.existing["tc_protection_threshold"] = "1" + + if self.cost: + tmp_value = re.findall(r'stp instance (.*) cost (.*)', self.interface_stp_cfg) + if not tmp_value: + self.cur_cfg["cost"] = "null" + self.existing["cost"] = "null" + else: + self.cur_cfg["cost"] = tmp_value[0][1] + self.existing["cost"] = tmp_value[0][1] + + # root_protection and loop_protection should get configuration at the same time + if self.root_protection or self.loop_protection: + if "stp root-protection" in self.interface_stp_cfg: + self.cur_cfg["root_protection"] = "enable" + self.existing["root_protection"] = "enable" + else: + self.cur_cfg["root_protection"] = "disable" + self.existing["root_protection"] = "disable" + + if "stp loop-protection" in self.interface_stp_cfg: + self.cur_cfg["loop_protection"] = "enable" + self.existing["loop_protection"] = "enable" + else: + self.cur_cfg["loop_protection"] = "disable" + self.existing["loop_protection"] = "disable" + + def get_end_state(self): + """ Get end state """ + + self.cli_get_stp_config() + if self.interface and self.interface != "all": + self.cli_get_interface_stp_config() + + if self.stp_mode: + if "stp mode stp" in self.stp_cfg: + self.end_state["stp_mode"] = "stp" + elif "stp mode rstp" in self.stp_cfg: + self.end_state["stp_mode"] = "rstp" + else: + self.end_state["stp_mode"] = "mstp" + + if self.stp_enable: + if "stp disable" in self.stp_cfg: + self.end_state["stp_enable"] = "disable" + else: + self.end_state["stp_enable"] = "enable" + + if self.stp_converge: + if "stp converge fast" in self.stp_cfg: + self.end_state["stp_converge"] = "fast" + else: + self.end_state["stp_converge"] = "normal" + + if self.edged_port: + if self.interface == "all": + if "stp edged-port default" in self.stp_cfg: + self.end_state["edged_port"] = "enable" + else: + self.end_state["edged_port"] = "disable" + else: + if "stp edged-port enable" in self.interface_stp_cfg: + self.end_state["edged_port"] = "enable" + else: + self.end_state["edged_port"] = "disable" + + if self.bpdu_filter: + if self.interface == "all": + if "stp bpdu-filter default" in self.stp_cfg: + self.end_state["bpdu_filter"] = "enable" + else: + self.end_state["bpdu_filter"] = "disable" + else: + if "stp bpdu-filter enable" in self.interface_stp_cfg: + self.end_state["bpdu_filter"] = "enable" + else: + self.end_state["bpdu_filter"] = "disable" + + if self.bpdu_protection: + if "stp bpdu-protection" in self.stp_cfg: + self.end_state["bpdu_protection"] = "enable" + else: + self.end_state["bpdu_protection"] = "disable" + + if self.tc_protection: + pre_cfg = self.stp_cfg.split("\n") + if "stp tc-protection" in pre_cfg: + self.end_state["tc_protection"] = "enable" + else: + self.end_state["tc_protection"] = "disable" + + if self.tc_protection_interval: + if "stp tc-protection interval" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection interval (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection interval on the device.') + self.end_state["tc_protection_interval"] = tmp_value[0] + else: + self.end_state["tc_protection_interval"] = "null" + + if self.tc_protection_threshold: + if "stp tc-protection threshold" in self.stp_cfg: + tmp_value = re.findall(r'stp tc-protection threshold (.*)', self.stp_cfg) + if not tmp_value: + self.module.fail_json( + msg='Error: Can not find tc-protection threshold on the device.') + self.end_state["tc_protection_threshold"] = tmp_value[0] + else: + self.end_state["tc_protection_threshold"] = "1" + + if self.cost: + tmp_value = re.findall(r'stp instance (.*) cost (.*)', self.interface_stp_cfg) + if not tmp_value: + self.end_state["cost"] = "null" + else: + self.end_state["cost"] = tmp_value[0][1] + + if self.root_protection or self.loop_protection: + if "stp root-protection" in self.interface_stp_cfg: + self.end_state["root_protection"] = "enable" + else: + self.end_state["root_protection"] = "disable" + + if "stp loop-protection" in self.interface_stp_cfg: + self.end_state["loop_protection"] = "enable" + else: + self.end_state["loop_protection"] = "disable" + + if self.existing == self.end_state: + self.changed = False + self.updates_cmd = list() + + def present_stp(self): + """ Present stp configuration """ + + cmds = list() + + # config stp global + if self.stp_mode: + if self.stp_mode != self.cur_cfg["stp_mode"]: + cmd = "stp mode %s" % self.stp_mode + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_enable: + if self.stp_enable != self.cur_cfg["stp_enable"]: + cmd = "stp %s" % self.stp_enable + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_converge: + if self.stp_converge != self.cur_cfg["stp_converge"]: + cmd = "stp converge %s" % self.stp_converge + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.interface == "all": + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_filter: + if self.interface == "all": + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_protection: + if self.bpdu_protection != self.cur_cfg["bpdu_protection"]: + if self.bpdu_protection == "enable": + cmd = "stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection: + if self.tc_protection != self.cur_cfg["tc_protection"]: + if self.tc_protection == "enable": + cmd = "stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_interval: + if self.tc_protection_interval != self.cur_cfg["tc_protection_interval"]: + cmd = "stp tc-protection interval %s" % self.tc_protection_interval + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_threshold: + if self.tc_protection_threshold != self.cur_cfg["tc_protection_threshold"]: + cmd = "stp tc-protection threshold %s" % self.tc_protection_threshold + cmds.append(cmd) + self.updates_cmd.append(cmd) + + # config interface stp + if self.interface and self.interface != "all": + tmp_changed = False + + cmd = "interface %s" % self.interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp edged-port" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.bpdu_filter: + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp bpdu-filter" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.root_protection: + if self.root_protection == "enable" and self.cur_cfg["loop_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable loop_protection, can not enable root_protection.') + if self.root_protection != self.cur_cfg["root_protection"]: + if self.root_protection == "enable": + cmd = "stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.loop_protection: + if self.loop_protection == "enable" and self.cur_cfg["root_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable root_protection, can not enable loop_protection.') + if self.loop_protection != self.cur_cfg["loop_protection"]: + if self.loop_protection == "enable": + cmd = "stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.cost: + if self.cost != self.cur_cfg["cost"]: + cmd = "stp cost %s" % self.cost + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if not tmp_changed: + cmd = "interface %s" % self.interface + self.updates_cmd.remove(cmd) + cmds.remove(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def absent_stp(self): + """ Absent stp configuration """ + + cmds = list() + + if self.stp_mode: + if self.stp_mode == self.cur_cfg["stp_mode"]: + if self.stp_mode != "mstp": + cmd = "undo stp mode" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.stp_enable: + if self.stp_enable != self.cur_cfg["stp_enable"]: + cmd = "stp %s" % self.stp_enable + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.stp_converge: + if self.stp_converge == self.cur_cfg["stp_converge"]: + cmd = "undo stp converge" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.edged_port: + if self.interface == "all": + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp edged-port default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_filter: + if self.interface == "all": + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-filter default" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.bpdu_protection: + if self.bpdu_protection != self.cur_cfg["bpdu_protection"]: + if self.bpdu_protection == "enable": + cmd = "stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp bpdu-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection: + if self.tc_protection != self.cur_cfg["tc_protection"]: + if self.tc_protection == "enable": + cmd = "stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + else: + cmd = "undo stp tc-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.tc_protection_interval: + if self.tc_protection_interval == self.cur_cfg["tc_protection_interval"]: + cmd = "undo stp tc-protection interval" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + if self.tc_protection_threshold: + if self.tc_protection_threshold == self.cur_cfg["tc_protection_threshold"]: + if self.tc_protection_threshold != "1": + cmd = "undo stp tc-protection threshold" + cmds.append(cmd) + self.updates_cmd.append(cmd) + self.changed = True + + # undo interface stp + if self.interface and self.interface != "all": + tmp_changed = False + + cmd = "interface %s" % self.interface + cmds.append(cmd) + self.updates_cmd.append(cmd) + + if self.edged_port: + if self.edged_port != self.cur_cfg["edged_port"]: + if self.edged_port == "enable": + cmd = "stp edged-port enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp edged-port" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.bpdu_filter: + if self.bpdu_filter != self.cur_cfg["bpdu_filter"]: + if self.bpdu_filter == "enable": + cmd = "stp bpdu-filter enable" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp bpdu-filter" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.root_protection: + if self.root_protection == "enable" and self.cur_cfg["loop_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable loop_protection, can not enable root_protection.') + if self.root_protection != self.cur_cfg["root_protection"]: + if self.root_protection == "enable": + cmd = "stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp root-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.loop_protection: + if self.loop_protection == "enable" and self.cur_cfg["root_protection"] == "enable": + self.module.fail_json( + msg='Error: The interface has enable root_protection, can not enable loop_protection.') + if self.loop_protection != self.cur_cfg["loop_protection"]: + if self.loop_protection == "enable": + cmd = "stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + else: + cmd = "undo stp loop-protection" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if self.cost: + if self.cost == self.cur_cfg["cost"]: + cmd = "undo stp cost" + cmds.append(cmd) + self.updates_cmd.append(cmd) + tmp_changed = True + + if not tmp_changed: + cmd = "interface %s" % self.interface + self.updates_cmd.remove(cmd) + cmds.remove(cmd) + + if cmds: + self.cli_load_config(cmds) + self.changed = True + + def work(self): + """ Work function """ + + self.check_params() + self.get_proposed() + self.get_existing() + + if self.state == "present": + self.present_stp() + else: + self.absent_stp() + + self.get_end_state() + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + self.results['updates'] = self.updates_cmd + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + stp_mode=dict(choices=['stp', 'rstp', 'mstp']), + stp_enable=dict(choices=['enable', 'disable']), + stp_converge=dict(choices=['fast', 'normal']), + bpdu_protection=dict(choices=['enable', 'disable']), + tc_protection=dict(choices=['enable', 'disable']), + tc_protection_interval=dict(type='str'), + tc_protection_threshold=dict(type='str'), + interface=dict(type='str'), + edged_port=dict(choices=['enable', 'disable']), + bpdu_filter=dict(choices=['enable', 'disable']), + cost=dict(type='str'), + root_protection=dict(choices=['enable', 'disable']), + loop_protection=dict(choices=['enable', 'disable']) + ) + + argument_spec.update(ce_argument_spec) + module = Stp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_switchport.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_switchport.py new file mode 100644 index 00000000..10dee874 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_switchport.py @@ -0,0 +1,997 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_switchport +short_description: Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. +description: + - Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - When C(state=absent), VLANs can be added/removed from trunk links and + the existing access VLAN can be 'unconfigured' to just having VLAN 1 on that interface. + - When working with trunks VLANs the keywords add/remove are always sent + in the C(port trunk allow-pass vlan) command. Use verbose mode to see commands sent. + - When C(state=unconfigured), the interface will result with having a default Layer 2 interface, i.e. vlan 1 in access mode. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Full name of the interface, i.e. 40GE1/0/22. + required: true + mode: + description: + - The link type of an interface. + choices: ['access','trunk', 'hybrid', 'dot1qtunnel'] + default_vlan: + description: + - If C(mode=access, or mode=dot1qtunnel), used as the access VLAN ID, in the range from 1 to 4094. + pvid_vlan: + description: + - If C(mode=trunk, or mode=hybrid), used as the trunk native VLAN ID, in the range from 1 to 4094. + trunk_vlans: + description: + - If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + untagged_vlans: + description: + - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + tagged_vlans: + description: + - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE + from the trunk, such as 2-10 or 2,5,10-15, etc. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present', 'absent', 'unconfigured'] +''' + +EXAMPLES = ''' +- name: Switchport module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + - name: Ensure 10GE1/0/22 is in its default switchport state + community.network.ce_switchport: + interface: 10GE1/0/22 + state: unconfigured + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 is configured for access vlan 20 + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: access + default_vlan: 20 + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 only has vlans 5-10 as trunk vlans + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + pvid_vlan: 10 + trunk_vlans: 5-10 + provider: '{{ cli }}' + + - name: Ensure 10GE1/0/22 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + pvid_vlan: 10 + trunk_vlans: 2-50 + provider: '{{ cli }}' + + - name: Ensure these VLANs are not being tagged on the trunk + community.network.ce_switchport: + interface: 10GE1/0/22 + mode: trunk + trunk_vlans: 51-4000 + state: absent + provider: '{{ cli }}' +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"default_vlan": "20", "interface": "10GE1/0/22", "mode": "access"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {"default_vlan": "10", "interface": "10GE1/0/22", + "mode": "access", "switchport": "enable"} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"default_vlan": "20", "interface": "10GE1/0/22", + "mode": "access", "switchport": "enable"} +updates: + description: command string sent to the device + returned: always + type: list + sample: ["10GE1/0/22", "port default vlan 20"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from xml.etree import ElementTree as ET +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_PORT_ATTR = """ + + + + + %s + + + + + + + + + + + +""" + +CE_NC_SET_PORT = """ + + + + %s + + %s + %s + %s + %s + + + + +""" + +CE_NC_SET_PORT_MODE = """ + + + + %s + + %s + + + + +""" + +CE_NC_SET_DEFAULT_PORT = """ + + + + + %s + + access + 1 + + + + + + + +""" + + +SWITCH_PORT_TYPE = ('ge', '10ge', '25ge', + '4x10ge', '40ge', '100ge', 'eth-trunk') + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +def is_portswitch_enalbed(iftype): + """"[undo] portswitch""" + + return bool(iftype in SWITCH_PORT_TYPE) + + +def vlan_bitmap_undo(bitmap): + """convert vlan bitmap to undo bitmap""" + + vlan_bit = ['F'] * 1024 + + if not bitmap or len(bitmap) == 0: + return ''.join(vlan_bit) + + bit_len = len(bitmap) + for num in range(bit_len): + undo = (~int(bitmap[num], 16)) & 0xF + vlan_bit[num] = hex(undo)[2] + + return ''.join(vlan_bit) + + +def is_vlan_bitmap_empty(bitmap): + """check vlan bitmap empty""" + + if not bitmap or len(bitmap) == 0: + return True + + bit_len = len(bitmap) + for num in range(bit_len): + if bitmap[num] != '0': + return False + + return True + + +class SwitchPort(object): + """ + Manages Layer 2 switchport interfaces. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # interface and vlan info + self.interface = self.module.params['interface'] + self.mode = self.module.params['mode'] + self.state = self.module.params['state'] + self.default_vlan = self.module.params['default_vlan'] + self.pvid_vlan = self.module.params['pvid_vlan'] + self.trunk_vlans = self.module.params['trunk_vlans'] + self.untagged_vlans = self.module.params['untagged_vlans'] + self.tagged_vlans = self.module.params['tagged_vlans'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + self.intf_info = dict() # interface vlan info + self.intf_type = None # loopback tunnel ... + + def init_module(self): + """ init module """ + + required_if = [('state', 'absent', ['mode']), ('state', 'present', ['mode'])] + mutually_exclusive = [['default_vlan', 'trunk_vlans'], + ['default_vlan', 'pvid_vlan'], + ['default_vlan', 'untagged_vlans'], + ['trunk_vlans', 'untagged_vlans'], + ['trunk_vlans', 'tagged_vlans'], + ['default_vlan', 'tagged_vlans']] + + self.module = AnsibleModule( + argument_spec=self.spec, required_if=required_if, supports_check_mode=True, mutually_exclusive=mutually_exclusive) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_interface_dict(self, ifname): + """ get one interface attributes dict.""" + + intf_info = dict() + conf_str = CE_NC_GET_PORT_ATTR % ifname + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return intf_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + tree = ET.fromstring(xml_str) + l2Enable = tree.find('ethernet/ethernetIfs/ethernetIf/l2Enable') + intf_info["l2Enable"] = l2Enable.text + port_type = tree.find('ethernet/ethernetIfs/ethernetIf/l2Attribute') + for pre in port_type: + intf_info[pre.tag] = pre.text + intf_info["ifName"] = ifname + if intf_info["trunkVlans"] is None: + intf_info["trunkVlans"] = "" + if intf_info["untagVlans"] is None: + intf_info["untagVlans"] = "" + return intf_info + + def is_l2switchport(self): + """Check layer2 switch port""" + + return bool(self.intf_info["l2Enable"] == "enable") + + def merge_access_vlan(self, ifname, default_vlan): + """Merge access interface vlan""" + + change = False + conf_str = "" + + self.updates_cmd.append("interface %s" % ifname) + if self.state == "present": + if self.intf_info["linkType"] == "access": + if default_vlan and self.intf_info["pvid"] != default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") + change = True + else: # not access + self.updates_cmd.append("port link-type access") + if default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") + else: + conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") + change = True + elif self.state == "absent": + if self.intf_info["linkType"] == "access": + if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": + self.updates_cmd.append( + "undo port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") + change = True + + if not change: + self.updates_cmd.pop() # remove interface + return + conf_str = "" + conf_str + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_ACCESS_PORT") + self.changed = True + + def merge_trunk_vlan(self, ifname, pvid_vlan, trunk_vlans): + """Merge trunk interface vlan""" + + change = False + xmlstr = "" + pvid = "" + trunk = "" + self.updates_cmd.append("interface %s" % ifname) + if trunk_vlans: + vlan_list = self.vlan_range_to_list(trunk_vlans) + vlan_map = self.vlan_list_to_bitmap(vlan_list) + if self.state == "present": + if self.intf_info["linkType"] == "trunk": + if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: + self.updates_cmd.append( + "port trunk pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + change = True + + if trunk_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["trunkVlans"], vlan_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + trunk = "%s:%s" % (add_vlans, add_vlans) + change = True + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + else: # not trunk + self.updates_cmd.append("port link-type trunk") + change = True + if pvid_vlan: + self.updates_cmd.append( + "port trunk pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + if trunk_vlans: + self.updates_cmd.append( + "port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + trunk = "%s:%s" % (vlan_map, vlan_map) + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + if not pvid_vlan and not trunk_vlans: + xmlstr += CE_NC_SET_PORT_MODE % (ifname, "trunk") + self.updates_cmd.append( + "undo port trunk allow-pass vlan 1") + elif self.state == "absent": + if self.intf_info["linkType"] == "trunk": + if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': + self.updates_cmd.append( + "undo port trunk pvid vlan %s" % pvid_vlan) + pvid = "1" + change = True + if trunk_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["trunkVlans"], vlan_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port trunk allow-pass %s" + % trunk_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + trunk = "%s:%s" % (undo_map, del_vlans) + change = True + if pvid or trunk: + xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") + if not pvid: + xmlstr = xmlstr.replace("", "") + if not trunk: + xmlstr = xmlstr.replace("", "") + + if not change: + self.updates_cmd.pop() + return + conf_str = "" + xmlstr + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_TRUNK_PORT") + self.changed = True + + def merge_hybrid_vlan(self, ifname, pvid_vlan, tagged_vlans, untagged_vlans): + """Merge hybrid interface vlan""" + + change = False + xmlstr = "" + pvid = "" + tagged = "" + untagged = "" + self.updates_cmd.append("interface %s" % ifname) + if tagged_vlans: + vlan_targed_list = self.vlan_range_to_list(tagged_vlans) + vlan_targed_map = self.vlan_list_to_bitmap(vlan_targed_list) + if untagged_vlans: + vlan_untarged_list = self.vlan_range_to_list(untagged_vlans) + vlan_untarged_map = self.vlan_list_to_bitmap(vlan_untarged_list) + if self.state == "present": + if self.intf_info["linkType"] == "hybrid": + if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: + self.updates_cmd.append( + "port hybrid pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + change = True + if tagged_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["trunkVlans"], vlan_targed_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + tagged = "%s:%s" % (add_vlans, add_vlans) + change = True + if untagged_vlans: + add_vlans = self.vlan_bitmap_add( + self.intf_info["untagVlans"], vlan_untarged_map) + if not is_vlan_bitmap_empty(add_vlans): + self.updates_cmd.append( + "port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + untagged = "%s:%s" % (add_vlans, add_vlans) + change = True + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + else: + self.updates_cmd.append("port link-type hybrid") + change = True + if pvid_vlan: + self.updates_cmd.append( + "port hybrid pvid vlan %s" % pvid_vlan) + pvid = pvid_vlan + if tagged_vlans: + self.updates_cmd.append( + "port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + tagged = "%s:%s" % (vlan_targed_map, vlan_targed_map) + if untagged_vlans: + self.updates_cmd.append( + "port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + untagged = "%s:%s" % (vlan_untarged_map, vlan_untarged_map) + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + if not pvid_vlan and not tagged_vlans and not untagged_vlans: + xmlstr += CE_NC_SET_PORT_MODE % (ifname, "hybrid") + self.updates_cmd.append( + "undo port hybrid untagged vlan 1") + elif self.state == "absent": + if self.intf_info["linkType"] == "hybrid": + if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': + self.updates_cmd.append( + "undo port hybrid pvid vlan %s" % pvid_vlan) + pvid = "1" + change = True + if tagged_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["trunkVlans"], vlan_targed_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port hybrid tagged vlan %s" + % tagged_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + tagged = "%s:%s" % (undo_map, del_vlans) + change = True + if untagged_vlans: + del_vlans = self.vlan_bitmap_del( + self.intf_info["untagVlans"], vlan_untarged_map) + if not is_vlan_bitmap_empty(del_vlans): + self.updates_cmd.append( + "undo port hybrid untagged vlan %s" + % untagged_vlans.replace(',', ' ').replace('-', ' to ')) + undo_map = vlan_bitmap_undo(del_vlans) + untagged = "%s:%s" % (undo_map, del_vlans) + change = True + if pvid or tagged or untagged: + xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) + if not pvid: + xmlstr = xmlstr.replace("", "") + if not tagged: + xmlstr = xmlstr.replace("", "") + if not untagged: + xmlstr = xmlstr.replace("", "") + + if not change: + self.updates_cmd.pop() + return + + conf_str = "" + xmlstr + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_HYBRID_PORT") + self.changed = True + + def merge_dot1qtunnel_vlan(self, ifname, default_vlan): + """Merge dot1qtunnel""" + + change = False + conf_str = "" + + self.updates_cmd.append("interface %s" % ifname) + if self.state == "present": + if self.intf_info["linkType"] == "dot1qtunnel": + if default_vlan and self.intf_info["pvid"] != default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") + change = True + else: + self.updates_cmd.append("port link-type dot1qtunnel") + if default_vlan: + self.updates_cmd.append( + "port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") + else: + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") + change = True + elif self.state == "absent": + if self.intf_info["linkType"] == "dot1qtunnel": + if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": + self.updates_cmd.append( + "undo port default vlan %s" % default_vlan) + conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") + change = True + if not change: + self.updates_cmd.pop() # remove interface + return + conf_str = "" + conf_str + "" + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "MERGE_DOT1QTUNNEL_PORT") + self.changed = True + + def default_switchport(self, ifname): + """Set interface default or unconfigured""" + + change = False + if self.intf_info["linkType"] != "access": + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("port link-type access") + self.updates_cmd.append("port default vlan 1") + change = True + else: + if self.intf_info["pvid"] != "1": + self.updates_cmd.append("interface %s" % ifname) + self.updates_cmd.append("port default vlan 1") + change = True + + if not change: + return + + conf_str = CE_NC_SET_DEFAULT_PORT % ifname + rcv_xml = set_nc_config(self.module, conf_str) + self.check_response(rcv_xml, "DEFAULT_INTF_VLAN") + self.changed = True + + def vlan_series(self, vlanid_s): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_s) + if peerlistlen != 2: + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + for num in range(peerlistlen): + if not vlanid_s[num].isdigit(): + self.module.fail_json( + msg='Error: Format of vlanid is invalid.') + if int(vlanid_s[0]) > int(vlanid_s[1]): + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + elif int(vlanid_s[0]) == int(vlanid_s[1]): + vlan_list.append(str(vlanid_s[0])) + return vlan_list + for num in range(int(vlanid_s[0]), int(vlanid_s[1])): + vlan_list.append(str(num)) + vlan_list.append(vlanid_s[1]) + + return vlan_list + + def vlan_region(self, vlanid_list): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_list) + for num in range(peerlistlen): + if vlanid_list[num].isdigit(): + vlan_list.append(vlanid_list[num]) + else: + vlan_s = self.vlan_series(vlanid_list[num].split('-')) + vlan_list.extend(vlan_s) + + return vlan_list + + def vlan_range_to_list(self, vlan_range): + """ convert vlan range to vlan list """ + + vlan_list = self.vlan_region(vlan_range.split(',')) + + return vlan_list + + def vlan_list_to_bitmap(self, vlanlist): + """ convert vlan list to vlan bitmap """ + + vlan_bit = ['0'] * 1024 + bit_int = [0] * 1024 + + vlan_list_len = len(vlanlist) + for num in range(vlan_list_len): + tagged_vlans = int(vlanlist[num]) + if tagged_vlans <= 0 or tagged_vlans > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + j = tagged_vlans // 4 + bit_int[j] |= 0x8 >> (tagged_vlans % 4) + vlan_bit[j] = hex(bit_int[j])[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def vlan_bitmap_add(self, oldmap, newmap): + """vlan add bitmap""" + + vlan_bit = ['0'] * 1024 + + if len(newmap) != 1024: + self.module.fail_json(msg='Error: New vlan bitmap is invalid.') + + if len(oldmap) != 1024 and len(oldmap) != 0: + self.module.fail_json(msg='Error: old vlan bitmap is invalid.') + + if len(oldmap) == 0: + return newmap + + for num in range(1024): + new_tmp = int(newmap[num], 16) + old_tmp = int(oldmap[num], 16) + add = (~(new_tmp & old_tmp)) & new_tmp + vlan_bit[num] = hex(add)[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def vlan_bitmap_del(self, oldmap, delmap): + """vlan del bitmap""" + + vlan_bit = ['0'] * 1024 + + if not oldmap or len(oldmap) == 0: + return ''.join(vlan_bit) + + if len(oldmap) != 1024 or len(delmap) != 1024: + self.module.fail_json(msg='Error: vlan bitmap is invalid.') + + for num in range(1024): + tmp = int(delmap[num], 16) & int(oldmap[num], 16) + vlan_bit[num] = hex(tmp)[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def check_params(self): + """Check all input params""" + + # interface type check + if self.interface: + self.intf_type = get_interface_type(self.interface) + if not self.intf_type: + self.module.fail_json( + msg='Error: Interface name of %s is error.' % self.interface) + + if not self.intf_type or not is_portswitch_enalbed(self.intf_type): + self.module.fail_json(msg='Error: Interface %s is error.') + + # check default_vlan + if self.default_vlan: + if not self.default_vlan.isdigit(): + self.module.fail_json(msg='Error: Access vlan id is invalid.') + if int(self.default_vlan) <= 0 or int(self.default_vlan) > 4094: + self.module.fail_json( + msg='Error: Access vlan id is not in the range from 1 to 4094.') + + # check pvid_vlan + if self.pvid_vlan: + if not self.pvid_vlan.isdigit(): + self.module.fail_json(msg='Error: Pvid vlan id is invalid.') + if int(self.pvid_vlan) <= 0 or int(self.pvid_vlan) > 4094: + self.module.fail_json( + msg='Error: Pvid vlan id is not in the range from 1 to 4094.') + + # get interface info + self.intf_info = self.get_interface_dict(self.interface) + if not self.intf_info: + self.module.fail_json(msg='Error: Interface does not exist.') + + if not self.is_l2switchport(): + self.module.fail_json( + msg='Error: Interface is not layer2 switch port.') + if self.state == "unconfigured": + if any([self.mode, self.default_vlan, self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When state is unconfigured, only interface name exists.') + else: + if self.mode == "access": + if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is access, only default_vlan can be supported.') + elif self.mode == "trunk": + if any([self.default_vlan, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is trunk, only pvid_vlan and trunk_vlans can exist.') + elif self.mode == "hybrid": + if any([self.default_vlan, self.trunk_vlans]): + self.module.fail_json( + msg='Error: When mode is hybrid, default_vlan and trunk_vlans cannot exist') + else: + if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): + self.module.fail_json( + msg='Error: When mode is dot1qtunnel, only default_vlan can be supported.') + + def get_proposed(self): + """get proposed info""" + + self.proposed['state'] = self.state + self.proposed['interface'] = self.interface + self.proposed['mode'] = self.mode + if self.mode: + if self.mode == "access": + self.proposed['access_pvid'] = self.default_vlan + elif self.mode == "trunk": + self.proposed['pvid_vlan'] = self.pvid_vlan + self.proposed['trunk_vlans'] = self.trunk_vlans + elif self.mode == "hybrid": + self.proposed['pvid_vlan'] = self.pvid_vlan + self.proposed['untagged_vlans'] = self.untagged_vlans + self.proposed['tagged_vlans'] = self.tagged_vlans + else: + self.proposed['dot1qtunnel_pvid'] = self.default_vlan + + def get_existing(self): + """get existing info""" + + if self.intf_info: + self.existing["interface"] = self.intf_info["ifName"] + self.existing["switchport"] = self.intf_info["l2Enable"] + self.existing["mode"] = self.intf_info["linkType"] + if self.intf_info["linkType"] == "access": + self.existing['access_pvid'] = self.intf_info["pvid"] + elif self.intf_info["linkType"] == "trunk": + self.existing['trunk_pvid'] = self.intf_info["pvid"] + self.existing['trunk_vlans'] = self.intf_info["trunkVlans"] + elif self.intf_info["linkType"] == "hybrid": + self.existing['hybrid_pvid'] = self.intf_info["pvid"] + self.existing['hybrid_untagged_vlans'] = self.intf_info["untagVlans"] + self.existing['hybrid_tagged_vlans'] = self.intf_info["trunkVlans"] + else: + self.existing['dot1qtunnel_pvid'] = self.intf_info["pvid"] + + def get_end_state(self): + """get end state info""" + + end_info = self.get_interface_dict(self.interface) + if end_info: + self.end_state["interface"] = end_info["ifName"] + self.end_state["switchport"] = end_info["l2Enable"] + self.end_state["mode"] = end_info["linkType"] + if end_info["linkType"] == "access": + self.end_state['access_pvid'] = end_info["pvid"] + elif end_info["linkType"] == "trunk": + self.end_state['trunk_pvid'] = end_info["pvid"] + self.end_state['trunk_vlans'] = end_info["trunkVlans"] + elif end_info["linkType"] == "hybrid": + self.end_state['hybrid_pvid'] = end_info["pvid"] + self.end_state['hybrid_untagged_vlans'] = end_info["untagVlans"] + self.end_state['hybrid_tagged_vlans'] = end_info["trunkVlans"] + else: + self.end_state['dot1qtunnel_pvid'] = end_info["pvid"] + if self.end_state == self.existing: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if not self.intf_info: + self.module.fail_json(msg='Error: interface does not exist.') + self.get_existing() + self.get_proposed() + + # present or absent + if self.state == "present" or self.state == "absent": + if self.mode == "access": + self.merge_access_vlan(self.interface, self.default_vlan) + elif self.mode == "trunk": + self.merge_trunk_vlan( + self.interface, self.pvid_vlan, self.trunk_vlans) + elif self.mode == "hybrid": + self.merge_hybrid_vlan(self.interface, self.pvid_vlan, self.tagged_vlans, self.untagged_vlans) + else: + self.merge_dot1qtunnel_vlan(self.interface, self.default_vlan) + + # unconfigured + else: + self.default_switchport(self.interface) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + interface=dict(required=True, type='str'), + mode=dict(choices=['access', 'trunk', 'dot1qtunnel', 'hybrid'], required=False), + default_vlan=dict(type='str', required=False), + pvid_vlan=dict(type='str', required=False), + trunk_vlans=dict(type='str', required=False), + untagged_vlans=dict(type='str', required=False), + tagged_vlans=dict(type='str', required=False), + state=dict(choices=['absent', 'present', 'unconfigured'], + default='present') + ) + + argument_spec.update(ce_argument_spec) + switchport = SwitchPort(argument_spec) + switchport.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vlan.py new file mode 100644 index 00000000..61029410 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vlan.py @@ -0,0 +1,687 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vlan +short_description: Manages VLAN resources and attributes on Huawei CloudEngine switches. +description: + - Manages VLAN configurations on Huawei CloudEngine switches. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vlan_id: + description: + - Single VLAN ID, in the range from 1 to 4094. + vlan_range: + description: + - Range of VLANs such as C(2-10) or C(2,5,10-15), etc. + name: + description: + - Name of VLAN, minimum of 1 character, maximum of 31 characters. + description: + description: + - Specify VLAN description, minimum of 1 character, maximum of 80 characters. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Vlan module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Ensure a range of VLANs are not present on the switch + community.network.ce_vlan: + vlan_range: "2-10,20,50,55-60,100-150" + state: absent + provider: "{{ cli }}" + + - name: Ensure VLAN 50 exists with the name WEB + community.network.ce_vlan: + vlan_id: 50 + name: WEB + state: absent + provider: "{{ cli }}" + + - name: Ensure VLAN is NOT on the device + community.network.ce_vlan: + vlan_id: 50 + state: absent + provider: "{{ cli }}" + +''' + +RETURN = ''' +proposed_vlans_list: + description: list of VLANs being proposed + returned: always + type: list + sample: ["100"] +existing_vlans_list: + description: list of existing VLANs on the switch prior to making changes + returned: always + type: list + sample: ["1", "2", "3", "4", "5", "20"] +end_state_vlans_list: + description: list of VLANs after the module is executed + returned: always + type: list + sample: ["1", "2", "3", "4", "5", "20", "100"] +proposed: + description: k/v pairs of parameters passed into module (does not include + vlan_id or vlan_range) + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "vlan for app" } +existing: + description: k/v pairs of existing vlan or null when using vlan_range + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "" } +end_state: + description: k/v pairs of the VLAN after executing module or null + when using vlan_range + returned: always + type: dict + sample: {"vlan_id":"20", "name": "VLAN_APP", "description": "vlan for app" } +updates: + description: command string sent to the device + returned: always + type: list + sample: ["vlan 20", "name VLAN20"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, execute_nc_action, ce_argument_spec + +CE_NC_CREATE_VLAN = """ + + + + + %s + %s + %s + + + + + + +""" + +CE_NC_DELETE_VLAN = """ + + + + + %s + + + + +""" + +CE_NC_MERGE_VLAN_DES = """ + + + + + %s + %s + + + + + + +""" + +CE_NC_MERGE_VLAN_NAME = """ + + + + + %s + %s + + + + + + +""" + + +CE_NC_MERGE_VLAN = """ + + + + + %s + %s + %s + + + + + + +""" + +CE_NC_GET_VLAN = """ + + + + + %s + + + + + + +""" + +CE_NC_GET_VLANS = """ + + + + + + + + + + +""" + +CE_NC_CREATE_VLAN_BATCH = """ + + + + %s:%s + + + +""" + +CE_NC_DELETE_VLAN_BATCH = """ + + + + %s:%s + + + +""" + + +class Vlan(object): + """ + Manages VLAN resources and attributes + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # vlan config info + self.vlan_id = self.module.params['vlan_id'] + self.vlan_range = self.module.params['vlan_range'] + self.name = self.module.params['name'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.vlan_exist = False + self.vlan_attr_exist = None + self.vlans_list_exist = list() + self.vlans_list_change = list() + self.updates_cmd = list() + self.results = dict() + self.vlan_attr_end = dict() + + def init_module(self): + """ + init ansible NetworkModule. + """ + + required_one_of = [["vlan_id", "vlan_range"]] + mutually_exclusive = [["vlan_id", "vlan_range"]] + + self.module = AnsibleModule( + argument_spec=self.spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def config_vlan(self, vlan_id, name='', description=''): + """Create vlan.""" + + if name is None: + name = '' + if description is None: + description = '' + + conf_str = CE_NC_CREATE_VLAN % (vlan_id, name, description) + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "CREATE_VLAN") + self.changed = True + + def merge_vlan(self, vlan_id, name, description): + """Merge vlan.""" + + conf_str = None + + if not name and description: + conf_str = CE_NC_MERGE_VLAN_DES % (vlan_id, description) + if not description and name: + conf_str = CE_NC_MERGE_VLAN_NAME % (vlan_id, name) + if description and name: + conf_str = CE_NC_MERGE_VLAN % (vlan_id, name, description) + + if not conf_str: + return + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "MERGE_VLAN") + self.changed = True + + def create_vlan_batch(self, vlan_list): + """Create vlan batch.""" + + if not vlan_list: + return + + vlan_bitmap = self.vlan_list_to_bitmap(vlan_list) + xmlstr = CE_NC_CREATE_VLAN_BATCH % (vlan_bitmap, vlan_bitmap) + + recv_xml = execute_nc_action(self.module, xmlstr) + self.check_response(recv_xml, "CREATE_VLAN_BATCH") + self.updates_cmd.append('vlan batch %s' % ( + self.vlan_range.replace(',', ' ').replace('-', ' to '))) + self.changed = True + + def delete_vlan_batch(self, vlan_list): + """Delete vlan batch.""" + + if not vlan_list: + return + + vlan_bitmap = self.vlan_list_to_bitmap(vlan_list) + xmlstr = CE_NC_DELETE_VLAN_BATCH % (vlan_bitmap, vlan_bitmap) + + recv_xml = execute_nc_action(self.module, xmlstr) + self.check_response(recv_xml, "DELETE_VLAN_BATCH") + self.updates_cmd.append('undo vlan batch %s' % ( + self.vlan_range.replace(',', ' ').replace('-', ' to '))) + self.changed = True + + def undo_config_vlan(self, vlanid): + """Delete vlan.""" + + conf_str = CE_NC_DELETE_VLAN % vlanid + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "DELETE_VLAN") + self.changed = True + self.updates_cmd.append('undo vlan %s' % self.vlan_id) + + def get_vlan_attr(self, vlan_id): + """ get vlan attributes.""" + + conf_str = CE_NC_GET_VLAN % vlan_id + xml_str = get_nc_config(self.module, conf_str) + attr = dict() + + if "" in xml_str: + return attr + else: + re_find_id = re.findall(r'.*(.*).*\s*', xml_str) + re_find_name = re.findall(r'.*(.*).*\s*', xml_str) + re_find_desc = re.findall(r'.*(.*).*\s*', xml_str) + + if re_find_id: + if re_find_name: + attr = dict(vlan_id=re_find_id[0], name=re_find_name[0], + description=re_find_desc[0]) + else: + attr = dict(vlan_id=re_find_id[0], name=None, + description=re_find_desc[0]) + return attr + + def get_vlans_name(self): + """ get all vlan vid and its name list, + sample: [ ("20", "VLAN_NAME_20"), ("30", "VLAN_NAME_30") ]""" + + conf_str = CE_NC_GET_VLANS + xml_str = get_nc_config(self.module, conf_str) + vlan_list = list() + + if "" in xml_str: + return vlan_list + else: + vlan_list = re.findall( + r'.*(.*).*\s*(.*).*', xml_str) + return vlan_list + + def get_vlans_list(self): + """ get all vlan vid list, sample: [ "20", "30", "31" ]""" + + conf_str = CE_NC_GET_VLANS + xml_str = get_nc_config(self.module, conf_str) + vlan_list = list() + + if "" in xml_str: + return vlan_list + else: + vlan_list = re.findall( + r'.*(.*).*', xml_str) + return vlan_list + + def vlan_series(self, vlanid_s): + """ convert vlan range to list """ + + vlan_list = [] + peerlistlen = len(vlanid_s) + if peerlistlen != 2: + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + for num in range(peerlistlen): + if not vlanid_s[num].isdigit(): + self.module.fail_json( + msg='Error: Format of vlanid is invalid.') + if int(vlanid_s[0]) > int(vlanid_s[1]): + self.module.fail_json(msg='Error: Format of vlanid is invalid.') + elif int(vlanid_s[0]) == int(vlanid_s[1]): + vlan_list.append(str(vlanid_s[0])) + return vlan_list + for num in range(int(vlanid_s[0]), int(vlanid_s[1])): + vlan_list.append(str(num)) + vlan_list.append(vlanid_s[1]) + + return vlan_list + + def vlan_region(self, vlanid_list): + """ convert vlan range to vlan list """ + + vlan_list = [] + peerlistlen = len(vlanid_list) + for num in range(peerlistlen): + if vlanid_list[num].isdigit(): + vlan_list.append(vlanid_list[num]) + else: + vlan_s = self.vlan_series(vlanid_list[num].split('-')) + vlan_list.extend(vlan_s) + + return vlan_list + + def vlan_range_to_list(self, vlan_range): + """ convert vlan range to vlan list """ + + vlan_list = self.vlan_region(vlan_range.split(',')) + + return vlan_list + + def vlan_list_to_bitmap(self, vlanlist): + """ convert vlan list to vlan bitmap """ + + vlan_bit = ['0'] * 1024 + bit_int = [0] * 1024 + + vlan_list_len = len(vlanlist) + for num in range(vlan_list_len): + tagged_vlans = int(vlanlist[num]) + if tagged_vlans <= 0 or tagged_vlans > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + j = tagged_vlans // 4 + bit_int[j] |= 0x8 >> (tagged_vlans % 4) + vlan_bit[j] = hex(bit_int[j])[2] + + vlan_xml = ''.join(vlan_bit) + + return vlan_xml + + def check_params(self): + """Check all input params""" + + if not self.vlan_id and self.description: + self.module.fail_json( + msg='Error: Vlan description could be set only at one vlan.') + + if not self.vlan_id and self.name: + self.module.fail_json( + msg='Error: Vlan name could be set only at one vlan.') + + # check vlan id + if self.vlan_id: + if not self.vlan_id.isdigit(): + self.module.fail_json( + msg='Error: Vlan id is not digit.') + if int(self.vlan_id) <= 0 or int(self.vlan_id) > 4094: + self.module.fail_json( + msg='Error: Vlan id is not in the range from 1 to 4094.') + + # check vlan description + if self.description: + if len(self.description) > 81 or len(self.description.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: vlan description is not in the range from 1 to 80.') + + # check vlan name + if self.name: + if len(self.name) > 31 or len(self.name.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: Vlan name is not in the range from 1 to 31.') + + def get_proposed(self): + """ + get proposed config. + """ + + if self.vlans_list_change: + if self.state == 'present': + proposed_vlans_tmp = list(self.vlans_list_change) + proposed_vlans_tmp.extend(self.vlans_list_exist) + self.results['proposed_vlans_list'] = list( + set(proposed_vlans_tmp)) + else: + self.results['proposed_vlans_list'] = list( + set(self.vlans_list_exist) - set(self.vlans_list_change)) + self.results['proposed_vlans_list'].sort() + else: + self.results['proposed_vlans_list'] = self.vlans_list_exist + + if self.vlan_id: + if self.state == "present": + self.results['proposed'] = dict( + vlan_id=self.vlan_id, + name=self.name, + description=self.description + ) + else: + self.results['proposed'] = None + else: + self.results['proposed'] = None + + def get_existing(self): + """ + get existing config. + """ + + self.results['existing_vlans_list'] = self.vlans_list_exist + + if self.vlan_id: + if self.vlan_attr_exist: + self.results['existing'] = dict( + vlan_id=self.vlan_attr_exist['vlan_id'], + name=self.vlan_attr_exist['name'], + description=self.vlan_attr_exist['description'] + ) + else: + self.results['existing'] = None + else: + self.results['existing'] = None + + def get_end_state(self): + """ + get end state config. + """ + + self.results['end_state_vlans_list'] = self.get_vlans_list() + + if self.vlan_id: + if self.vlan_attr_end: + self.results['end_state'] = dict( + vlan_id=self.vlan_attr_end['vlan_id'], + name=self.vlan_attr_end['name'], + description=self.vlan_attr_end['description'] + ) + else: + self.results['end_state'] = None + + else: + self.results['end_state'] = None + + def work(self): + """ + worker. + """ + + # check param + self.check_params() + + # get all vlan info + self.vlans_list_exist = self.get_vlans_list() + + # get vlan attributes + if self.vlan_id: + self.vlans_list_change.append(self.vlan_id) + self.vlan_attr_exist = self.get_vlan_attr(self.vlan_id) + if self.vlan_attr_exist: + self.vlan_exist = True + + if self.vlan_range: + new_vlans_tmp = self.vlan_range_to_list(self.vlan_range) + if self.state == 'present': + self.vlans_list_change = list( + set(new_vlans_tmp) - set(self.vlans_list_exist)) + else: + self.vlans_list_change = [ + val for val in new_vlans_tmp if val in self.vlans_list_exist] + + if self.state == 'present': + if self.vlan_id: + if not self.vlan_exist: + # create a new vlan + self.config_vlan(self.vlan_id, self.name, self.description) + elif self.description and self.description != self.vlan_attr_exist['description']: + # merge vlan description + self.merge_vlan(self.vlan_id, self.name, self.description) + elif self.name and self.name != self.vlan_attr_exist['name']: + # merge vlan name + self.merge_vlan(self.vlan_id, self.name, self.description) + + # update command for results + if self.changed: + self.updates_cmd.append('vlan %s' % self.vlan_id) + if self.name: + self.updates_cmd.append('name %s' % self.name) + if self.description: + self.updates_cmd.append( + 'description %s' % self.description) + elif self.vlan_range and self.vlans_list_change: + self.create_vlan_batch(self.vlans_list_change) + else: # absent + if self.vlan_id: + if self.vlan_exist: + # delete the vlan + self.undo_config_vlan(self.vlan_id) + elif self.vlan_range and self.vlans_list_change: + self.delete_vlan_batch(self.vlans_list_change) + + # result + if self.vlan_id: + self.vlan_attr_end = self.get_vlan_attr(self.vlan_id) + + self.get_existing() + self.get_proposed() + self.get_end_state() + + self.results['changed'] = self.changed + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ module main """ + + argument_spec = dict( + vlan_id=dict(required=False), + vlan_range=dict(required=False, type='str'), + name=dict(required=False, type='str'), + description=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + + argument_spec.update(ce_argument_spec) + vlancfg = Vlan(argument_spec) + vlancfg.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf.py new file mode 100644 index 00000000..f3293c9a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf +short_description: Manages VPN instance on HUAWEI CloudEngine switches. +description: + - Manages VPN instance of HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If I(state=absent), the route will be removed, regardless of the non-required options. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance, the length of vrf name is 1 - 31, i.e. "test", but can not be C(_public_). + required: true + description: + description: + - Description of the vrf, the string length is 1 - 242 . + state: + description: + - Manage the state of the resource. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Vrf module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config a vpn install named vpna, description is test + community.network.ce_vrf: + vrf: vpna + description: test + state: present + provider: "{{ cli }}" + - name: Delete a vpn install named vpna + community.network.ce_vrf: + vrf: vpna + state: absent + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"vrf": "vpna", + "description": "test", + "state": "present"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: {"vrf": "vpna", + "description": "test", + "present": "present"} +updates: + description: command list sent to the device + returned: always + type: list + sample: ["ip vpn-instance vpna", + "description test"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VRF = """ + + + + + + + + + + + + +""" + +CE_NC_CREATE_VRF = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_VRF = """ + + + + + %s + %s + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +class Vrf(object): + """Manage vpn instance""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.description = self.module.params['description'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + if self.state == "present": + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.description: + self.updates_cmd.append('description %s' % (self.description)) + else: + self.updates_cmd.append('undo ip vpn-instance %s' % (self.vrf)) + + def get_vrf(self): + """ check if vrf is need to change""" + + getxmlstr = CE_NC_GET_VRF + xml_str = get_nc_config(self.module, getxmlstr) + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + vpn_instances = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance") + if vpn_instances: + for vpn_instance in vpn_instances: + if vpn_instance.find('vrfName').text == self.vrf: + if vpn_instance.find('vrfDescription').text == self.description: + if self.state == "present": + return False + else: + return True + else: + return True + return self.state == "present" + else: + return self.state == "present" + + def check_params(self): + """Check all input params""" + + # vrf and description check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if len(self.vrf) < 1 or len(self.vrf) > 31: + self.module.fail_json( + msg='Error: The vrf name length must between 1 and 242.') + if self.description: + if len(self.description) < 1 or len(self.description) > 242: + self.module.fail_json( + msg='Error: The vrf description length must between 1 and 242.') + + def operate_vrf(self): + """config/delete vrf""" + if not self.changed: + return + if self.state == "present": + if self.description is None: + configxmlstr = CE_NC_CREATE_VRF % (self.vrf, '') + else: + configxmlstr = CE_NC_CREATE_VRF % (self.vrf, self.description) + else: + configxmlstr = CE_NC_DELETE_VRF % (self.vrf, self.description) + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF") + + def get_proposed(self): + """get_proposed""" + + if self.state == 'present': + self.proposed['vrf'] = self.vrf + if self.description: + self.proposed['description'] = self.description + + else: + self.proposed = dict() + self.proposed['state'] = self.state + + def get_existing(self): + """get_existing""" + + change = self.get_vrf() + if change: + if self.state == 'present': + self.existing = dict() + else: + self.existing['vrf'] = self.vrf + if self.description: + self.existing['description'] = self.description + self.changed = True + else: + if self.state == 'absent': + self.existing = dict() + else: + self.existing['vrf'] = self.vrf + if self.description: + self.existing['description'] = self.description + self.changed = False + + def get_end_state(self): + """get_end_state""" + + change = self.get_vrf() + if not change: + if self.state == 'present': + self.end_state['vrf'] = self.vrf + if self.description: + self.end_state['description'] = self.description + else: + self.end_state = dict() + else: + if self.state == 'present': + self.end_state = dict() + else: + self.end_state['vrf'] = self.vrf + if self.description: + self.end_state['description'] = self.description + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_vrf() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + description=dict(required=False, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = Vrf(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_af.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_af.py new file mode 100644 index 00000000..05a205e6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_af.py @@ -0,0 +1,848 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf_af +short_description: Manages VPN instance address family on HUAWEI CloudEngine switches. +description: + - Manages VPN instance address family of HUAWEI CloudEngine switches. +author: Yang yang (@QijunPan) +notes: + - If I(state=absent), the vrf will be removed, regardless of the non-required parameters. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance. + required: true + vrf_aftype: + description: + - VPN instance address family. + choices: ['v4','v6'] + default: v4 + route_distinguisher: + description: + - VPN instance route distinguisher,the RD used to distinguish same route prefix from different vpn. + The RD must be setted before setting vpn_target_value. + vpn_target_state: + description: + - Manage the state of the vpn target. + choices: ['present','absent'] + vpn_target_type: + description: + - VPN instance vpn target type. + choices: ['export_extcommunity', 'import_extcommunity'] + vpn_target_value: + description: + - VPN instance target value. Such as X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295> + or number<0-65535>.number<0-65535>:number<0-65535> or number<65536-4294967295>:number<0-65535> + but not support 0:0 and 0.0:0. + evpn: + description: + - Is extend vpn or normal vpn. + type: bool + default: 'no' + state: + description: + - Manage the state of the af. + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: Vrf af module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Config vpna, set address family is ipv4 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + state: present + provider: "{{ cli }}" + - name: Config vpna, delete address family is ipv4 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + state: absent + provider: "{{ cli }}" + - name: Config vpna, set address family is ipv4,rd=1:1,set vpn_target_type=export_extcommunity,vpn_target_value=2:2 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + route_distinguisher: 1:1 + vpn_target_type: export_extcommunity + vpn_target_value: 2:2 + vpn_target_state: present + state: present + provider: "{{ cli }}" + - name: Config vpna, set address family is ipv4,rd=1:1,delete vpn_target_type=export_extcommunity,vpn_target_value=2:2 + community.network.ce_vrf_af: + vrf: vpna + vrf_aftype: v4 + route_distinguisher: 1:1 + vpn_target_type: export_extcommunity + vpn_target_value: 2:2 + vpn_target_state: absent + state: present + provider: "{{ cli }}" +''' +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"vrf": "vpna", + "vrf_aftype": "v4", + "state": "present", + "vpn_targe_state":"absent", + "evpn": "none", + "vpn_target_type": "none", + "vpn_target_value": "none"} +existing: + description: k/v pairs of existing switchport + returned: always + type: dict + sample: { + "route_distinguisher": [ + "1:1", + "2:2" + ], + "vpn_target_type": [], + "vpn_target_value": [], + "vrf": "vpna", + "vrf_aftype": [ + "ipv4uni", + "ipv6uni" + ] + } +end_state: + description: k/v pairs of switchport after module execution + returned: always + type: dict + sample: { + "route_distinguisher": [ + "1:1", + "2:2" + ], + "vpn_target_type": [ + "import_extcommunity", + "3:3" + ], + "vpn_target_value": [], + "vrf": "vpna", + "vrf_aftype": [ + "ipv4uni", + "ipv6uni" + ] + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "ip vpn-instance vpna", + "vpn-target 3:3 import_extcommunity" + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_VRF = """ + + + + + + + + + + + + +""" + +CE_NC_GET_VRF_AF = """ + + + + + + %s + + + + %s + + + + + + + +""" + +CE_NC_DELETE_VRF_AF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_CREATE_VRF_AF = """ + + + + + %s + + + %s + %s%s + + + + + +""" +CE_NC_CREATE_VRF_TARGET = """ + + + %s + %s + + +""" + +CE_NC_DELETE_VRF_TARGET = """ + + + %s + %s + + +""" + +CE_NC_GET_VRF_TARGET = """ + + + + + + +""" + +CE_NC_CREATE_EXTEND_VRF_TARGET = """ + + + %s + %s + evpn + + +""" + +CE_NC_DELETE_EXTEND_VRF_TARGET = """ + + + %s + %s + evpn + + +""" + +CE_NC_GET_EXTEND_VRF_TARGET = """ + + + + + + + +""" + + +def build_config_xml(xmlstr): + """build_config_xml""" + + return ' ' + xmlstr + ' ' + + +def is_valid_value(vrf_targe_value): + """check if the vrf target value is valid""" + + each_num = None + if len(vrf_targe_value) > 21 or len(vrf_targe_value) < 3: + return False + if vrf_targe_value.find(':') == -1: + return False + elif vrf_targe_value == '0:0': + return False + elif vrf_targe_value == '0.0:0': + return False + else: + value_list = vrf_targe_value.split(':') + if value_list[0].find('.') != -1: + if not value_list[1].isdigit(): + return False + if int(value_list[1]) > 65535: + return False + value = value_list[0].split('.') + if len(value) == 4: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + elif len(value) == 2: + for each_num in value: + if not each_num.isdigit(): + return False + if int(each_num) > 65535: + return False + return True + else: + return False + elif not value_list[0].isdigit(): + return False + elif not value_list[1].isdigit(): + return False + elif int(value_list[0]) < 65536 and int(value_list[1]) < 4294967296: + return True + elif int(value_list[0]) > 65535 and int(value_list[0]) < 4294967296: + return bool(int(value_list[1]) < 65536) + else: + return False + + +class VrfAf(object): + """manage the vrf address family and export/import target""" + + def __init__(self, argument_spec, ): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.vrf_aftype = self.module.params['vrf_aftype'] + if self.vrf_aftype == 'v4': + self.vrf_aftype = 'ipv4uni' + else: + self.vrf_aftype = 'ipv6uni' + self.route_distinguisher = self.module.params['route_distinguisher'] + self.evpn = self.module.params['evpn'] + self.vpn_target_type = self.module.params['vpn_target_type'] + self.vpn_target_value = self.module.params['vpn_target_value'] + self.vpn_target_state = self.module.params['vpn_target_state'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + self.vpn_target_changed = False + self.vrf_af_type_changed = False + self.vrf_rd_changed = False + self.vrf_af_info = dict() + + def init_module(self): + """init_module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def is_vrf_af_exist(self): + """is vrf address family exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + return True + else: + continue + return False + + def get_exist_rd(self): + """get exist route distinguisher """ + + if not self.vrf_af_info: + return None + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if vrf_af_ele["vrfRD"] is None: + return None + else: + return vrf_af_ele["vrfRD"] + else: + continue + return None + + def is_vrf_rd_exist(self): + """is vrf route distinguisher exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if vrf_af_ele["vrfRD"] is None: + return False + if self.route_distinguisher is not None: + return bool(vrf_af_ele["vrfRD"] == self.route_distinguisher) + else: + return True + else: + continue + return False + + def is_vrf_rt_exist(self): + """is vpn target exist""" + + if not self.vrf_af_info: + return False + + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + if vrf_af_ele["afType"] == self.vrf_aftype: + if self.evpn is False: + if not vrf_af_ele.get("vpnTargets"): + return False + for vpn_target in vrf_af_ele.get("vpnTargets"): + if vpn_target["vrfRTType"] == self.vpn_target_type \ + and vpn_target["vrfRTValue"] == self.vpn_target_value: + return True + else: + continue + else: + if not vrf_af_ele.get("evpnTargets"): + return False + for evpn_target in vrf_af_ele.get("evpnTargets"): + if evpn_target["vrfRTType"] == self.vpn_target_type \ + and evpn_target["vrfRTValue"] == self.vpn_target_value: + return True + else: + continue + else: + continue + return False + + def set_update_cmd(self): + """ set update command""" + if not self.changed: + return + if self.vpn_target_type: + if self.vpn_target_type == "export_extcommunity": + vpn_target_type = "export-extcommunity" + else: + vpn_target_type = "import-extcommunity" + if self.state == "present": + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.vrf_aftype == 'ipv4uni': + self.updates_cmd.append('ipv4-family') + elif self.vrf_aftype == 'ipv6uni': + self.updates_cmd.append('ipv6-family') + if self.route_distinguisher: + if not self.is_vrf_rd_exist(): + self.updates_cmd.append( + 'route-distinguisher %s' % self.route_distinguisher) + else: + if self.get_exist_rd() is not None: + self.updates_cmd.append( + 'undo route-distinguisher %s' % self.get_exist_rd()) + if self.vpn_target_state == "present": + if not self.is_vrf_rt_exist(): + if self.evpn is False: + self.updates_cmd.append( + 'vpn-target %s %s' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append( + 'vpn-target %s %s evpn' % (self.vpn_target_value, vpn_target_type)) + elif self.vpn_target_state == "absent": + if self.is_vrf_rt_exist(): + if self.evpn is False: + self.updates_cmd.append( + 'undo vpn-target %s %s' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append( + 'undo vpn-target %s %s evpn' % (self.vpn_target_value, vpn_target_type)) + else: + self.updates_cmd.append('ip vpn-instance %s' % (self.vrf)) + if self.vrf_aftype == 'ipv4uni': + self.updates_cmd.append('undo ipv4-family') + elif self.vrf_aftype == 'ipv6uni': + self.updates_cmd.append('undo ipv6-family') + + def get_vrf(self): + """ check if vrf is need to change""" + + getxmlstr = CE_NC_GET_VRF + xmlstr_new_1 = (self.vrf.lower()) + + xml_str = get_nc_config(self.module, getxmlstr) + re_find_1 = re.findall( + r'.*(.*).*', xml_str.lower()) + + if re_find_1 is None: + return False + + return xmlstr_new_1 in re_find_1 + + def get_vrf_af(self): + """ check if vrf is need to change""" + + self.vrf_af_info["vpnInstAF"] = list() + if self.evpn is True: + getxmlstr = CE_NC_GET_VRF_AF % ( + self.vrf, CE_NC_GET_EXTEND_VRF_TARGET) + else: + getxmlstr = CE_NC_GET_VRF_AF % (self.vrf, CE_NC_GET_VRF_TARGET) + + xml_str = get_nc_config(self.module, getxmlstr) + + if 'data/' in xml_str: + return self.state == 'present' + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + + # get the vpn address family and RD text + vrf_addr_types = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance/vpnInstAFs/vpnInstAF") + if vrf_addr_types: + for vrf_addr_type in vrf_addr_types: + vrf_af_info = dict() + for vrf_addr_type_ele in vrf_addr_type: + if vrf_addr_type_ele.tag in ["vrfName", "afType", "vrfRD"]: + vrf_af_info[vrf_addr_type_ele.tag] = vrf_addr_type_ele.text + if vrf_addr_type_ele.tag == 'vpnTargets': + vrf_af_info["vpnTargets"] = list() + for rtargets in vrf_addr_type_ele: + rt_dict = dict() + for rtarget in rtargets: + if rtarget.tag in ["vrfRTValue", "vrfRTType"]: + rt_dict[rtarget.tag] = rtarget.text + vrf_af_info["vpnTargets"].append(rt_dict) + if vrf_addr_type_ele.tag == 'exVpnTargets': + vrf_af_info["evpnTargets"] = list() + for rtargets in vrf_addr_type_ele: + rt_dict = dict() + for rtarget in rtargets: + if rtarget.tag in ["vrfRTValue", "vrfRTType"]: + rt_dict[rtarget.tag] = rtarget.text + vrf_af_info["evpnTargets"].append(rt_dict) + self.vrf_af_info["vpnInstAF"].append(vrf_af_info) + + def check_params(self): + """Check all input params""" + + # vrf and description check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if not self.get_vrf(): + self.module.fail_json( + msg='Error: The vrf name do not exist.') + if self.state == 'present': + if self.route_distinguisher: + if not is_valid_value(self.route_distinguisher): + self.module.fail_json(msg='Error:The vrf route distinguisher length must between 3 ~ 21,' + 'i.e. X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295>' + 'or number<0-65535>.number<0-65535>:number<0-65535>' + 'or number<65536-4294967295>:number<0-65535>' + ' but not be 0:0 or 0.0:0.') + if not self.vpn_target_state: + if self.vpn_target_value or self.vpn_target_type: + self.module.fail_json( + msg='Error: The vpn target state should be exist.') + if self.vpn_target_state: + if not self.vpn_target_value or not self.vpn_target_type: + self.module.fail_json( + msg='Error: The vpn target value and type should be exist.') + if self.vpn_target_value: + if not is_valid_value(self.vpn_target_value): + self.module.fail_json(msg='Error:The vrf target value length must between 3 ~ 21,' + 'i.e. X.X.X.X:number<0-65535> or number<0-65535>:number<0-4294967295>' + 'or number<0-65535>.number<0-65535>:number<0-65535>' + 'or number<65536-4294967295>:number<0-65535>' + ' but not be 0:0 or 0.0:0.') + + def operate_vrf_af(self): + """config/delete vrf""" + + vrf_target_operate = '' + if self.route_distinguisher is None: + route_d = '' + else: + route_d = self.route_distinguisher + + if self.state == 'present': + if self.vrf_aftype: + if self.is_vrf_af_exist(): + self.vrf_af_type_changed = False + else: + self.vrf_af_type_changed = True + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + else: + self.vrf_af_type_changed = bool(self.is_vrf_af_exist()) + + if self.vpn_target_state == 'present': + if self.evpn is False and not self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_CREATE_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + if self.evpn is True and not self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_CREATE_EXTEND_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + elif self.vpn_target_state == 'absent': + if self.evpn is False and self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_DELETE_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + if self.evpn is True and self.is_vrf_rt_exist(): + vrf_target_operate = CE_NC_DELETE_EXTEND_VRF_TARGET % ( + self.vpn_target_type, self.vpn_target_value) + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vpn_target_changed = True + else: + if self.route_distinguisher: + if not self.is_vrf_rd_exist(): + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vrf_rd_changed = True + else: + self.vrf_rd_changed = False + else: + if self.is_vrf_rd_exist(): + configxmlstr = CE_NC_CREATE_VRF_AF % ( + self.vrf, self.vrf_aftype, route_d, vrf_target_operate) + self.vrf_rd_changed = True + else: + self.vrf_rd_changed = False + if not self.vrf_rd_changed and not self.vrf_af_type_changed and not self.vpn_target_changed: + self.changed = False + else: + self.changed = True + else: + if self.is_vrf_af_exist(): + configxmlstr = CE_NC_DELETE_VRF_AF % ( + self.vrf, self.vrf_aftype) + self.changed = True + else: + self.changed = False + + if not self.changed: + return + + conf_str = build_config_xml(configxmlstr) + + recv_xml = set_nc_config(self.module, conf_str) + self.check_response(recv_xml, "OPERATE_VRF_AF") + + def get_proposed(self): + """get_proposed""" + + if self.state == 'present': + self.proposed['vrf'] = self.vrf + if self.vrf_aftype is None: + self.proposed['vrf_aftype'] = 'ipv4uni' + else: + self.proposed['vrf_aftype'] = self.vrf_aftype + if self.route_distinguisher is not None: + self.proposed['route_distinguisher'] = self.route_distinguisher + else: + self.proposed['route_distinguisher'] = list() + if self.vpn_target_state == 'present': + self.proposed['evpn'] = self.evpn + self.proposed['vpn_target_type'] = self.vpn_target_type + self.proposed['vpn_target_value'] = self.vpn_target_value + else: + self.proposed['vpn_target_type'] = list() + self.proposed['vpn_target_value'] = list() + else: + self.proposed = dict() + self.proposed['state'] = self.state + self.proposed['vrf'] = self.vrf + self.proposed['vrf_aftype'] = list() + self.proposed['route_distinguisher'] = list() + self.proposed['vpn_target_value'] = list() + self.proposed['vpn_target_type'] = list() + + def get_existing(self): + """get_existing""" + + self.get_vrf_af() + self.existing['vrf'] = self.vrf + self.existing['vrf_aftype'] = list() + self.existing['route_distinguisher'] = list() + self.existing['vpn_target_value'] = list() + self.existing['vpn_target_type'] = list() + self.existing['evpn_target_value'] = list() + self.existing['evpn_target_type'] = list() + if self.vrf_af_info["vpnInstAF"] is None: + return + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + self.existing['vrf_aftype'].append(vrf_af_ele["afType"]) + self.existing['route_distinguisher'].append( + vrf_af_ele["vrfRD"]) + if vrf_af_ele.get("vpnTargets"): + for vpn_target in vrf_af_ele.get("vpnTargets"): + self.existing['vpn_target_type'].append( + vpn_target["vrfRTType"]) + self.existing['vpn_target_value'].append( + vpn_target["vrfRTValue"]) + if vrf_af_ele.get("evpnTargets"): + for evpn_target in vrf_af_ele.get("evpnTargets"): + self.existing['evpn_target_type'].append( + evpn_target["vrfRTType"]) + self.existing['evpn_target_value'].append( + evpn_target["vrfRTValue"]) + + def get_end_state(self): + """get_end_state""" + + self.get_vrf_af() + self.end_state['vrf'] = self.vrf + self.end_state['vrf_aftype'] = list() + self.end_state['route_distinguisher'] = list() + self.end_state['vpn_target_value'] = list() + self.end_state['vpn_target_type'] = list() + self.end_state['evpn_target_value'] = list() + self.end_state['evpn_target_type'] = list() + if self.vrf_af_info["vpnInstAF"] is None: + return + for vrf_af_ele in self.vrf_af_info["vpnInstAF"]: + self.end_state['vrf_aftype'].append(vrf_af_ele["afType"]) + self.end_state['route_distinguisher'].append(vrf_af_ele["vrfRD"]) + if vrf_af_ele.get("vpnTargets"): + for vpn_target in vrf_af_ele.get("vpnTargets"): + self.end_state['vpn_target_type'].append( + vpn_target["vrfRTType"]) + self.end_state['vpn_target_value'].append( + vpn_target["vrfRTValue"]) + if vrf_af_ele.get("evpnTargets"): + for evpn_target in vrf_af_ele.get("evpnTargets"): + self.end_state['evpn_target_type'].append( + evpn_target["vrfRTType"]) + self.end_state['evpn_target_value'].append( + evpn_target["vrfRTValue"]) + + def work(self): + """worker""" + + self.check_params() + self.get_existing() + self.get_proposed() + self.operate_vrf_af() + self.set_update_cmd() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + vrf_aftype=dict(choices=['v4', 'v6'], + default='v4', required=False), + route_distinguisher=dict(required=False, type='str'), + evpn=dict(type='bool', default=False), + vpn_target_type=dict( + choices=['export_extcommunity', 'import_extcommunity'], required=False), + vpn_target_value=dict(required=False, type='str'), + vpn_target_state=dict(choices=['absent', 'present'], required=False), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + interface = VrfAf(argument_spec) + interface.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_interface.py new file mode 100644 index 00000000..10a207ce --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrf_interface.py @@ -0,0 +1,517 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrf_interface +short_description: Manages interface specific VPN configuration on HUAWEI CloudEngine switches. +description: + - Manages interface specific VPN configuration of HUAWEI CloudEngine switches. +author: Zhijin Zhou (@QijunPan) +notes: + - Ensure that a VPN instance has been created and the IPv4 address family has been enabled for the VPN instance. + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + vrf: + description: + - VPN instance, the length of vrf name is 1 ~ 31, i.e. "test", but can not be C(_public_). + required: true + vpn_interface: + description: + - An interface that can binding VPN instance, i.e. 40GE1/0/22, Vlanif10. + Must be fully qualified interface name. + Interface types, such as 10GE, 40GE, 100GE, LoopBack, MEth, Tunnel, Vlanif.... + required: true + state: + description: + - Manage the state of the resource. + required: false + choices: ['present','absent'] + default: present +''' + +EXAMPLES = ''' +- name: VRF interface test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: "Configure a VPN instance for the interface" + community.network.ce_vrf_interface: + vpn_interface: 40GE1/0/2 + vrf: test + state: present + provider: "{{ cli }}" + + - name: "Disable the association between a VPN instance and an interface" + community.network.ce_vrf_interface: + vpn_interface: 40GE1/0/2 + vrf: test + state: absent + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: { + "state": "present", + "vpn_interface": "40GE2/0/17", + "vrf": "jss" + } +existing: + description: k/v pairs of existing attributes on the interface + returned: verbose mode + type: dict + sample: { + "vpn_interface": "40GE2/0/17", + "vrf": null + } +end_state: + description: k/v pairs of end attributes on the interface + returned: verbose mode + type: dict + sample: { + "vpn_interface": "40GE2/0/17", + "vrf": "jss" + } +updates: + description: command list sent to the device + returned: always + type: list + sample: [ + "ip binding vpn-instance jss", + ] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, get_nc_config, set_nc_config + +CE_NC_GET_VRF = """ + + + + + + %s + + + + + +""" + +CE_NC_GET_VRF_INTERFACE = """ + + + + + + + + + + + + + + + + +""" + +CE_NC_MERGE_VRF_INTERFACE = """ + + + + + + %s + + + %s + + + + + + + +""" + +CE_NC_GET_INTF = """ + + + + + %s + + + + + +""" + +CE_NC_DEL_INTF_VPN = """ + + + + + + %s + + + %s + + + + + + + +""" + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class VrfInterface(object): + """Manage vpn instance""" + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # vpn instance info + self.vrf = self.module.params['vrf'] + self.vpn_interface = self.module.params['vpn_interface'] + self.vpn_interface = self.vpn_interface.upper().replace(' ', '') + self.state = self.module.params['state'] + self.intf_info = dict() + self.intf_info['isL2SwitchPort'] = None + self.intf_info['vrfName'] = None + self.conf_exist = False + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init_module""" + + required_one_of = [("vrf", "vpn_interface")] + self.module = AnsibleModule( + argument_spec=self.spec, required_one_of=required_one_of, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_update_cmd(self): + """ get updated command""" + + if self.conf_exist: + return + + if self.state == 'absent': + self.updates_cmd.append( + "undo ip binding vpn-instance %s" % self.vrf) + return + + if self.vrf != self.intf_info['vrfName']: + self.updates_cmd.append("ip binding vpn-instance %s" % self.vrf) + + return + + def check_params(self): + """Check all input params""" + + if not self.is_vrf_exist(): + self.module.fail_json( + msg='Error: The VPN instance is not existed.') + + if self.state == 'absent': + if self.vrf != self.intf_info['vrfName']: + self.module.fail_json( + msg='Error: The VPN instance is not bound to the interface.') + + if self.intf_info['isL2SwitchPort'] == 'true': + self.module.fail_json( + msg='Error: L2Switch Port can not binding a VPN instance.') + + # interface type check + if self.vpn_interface: + intf_type = get_interface_type(self.vpn_interface) + if not intf_type: + self.module.fail_json( + msg='Error: interface name of %s' + ' is error.' % self.vpn_interface) + + # vrf check + if self.vrf == '_public_': + self.module.fail_json( + msg='Error: The vrf name _public_ is reserved.') + if len(self.vrf) < 1 or len(self.vrf) > 31: + self.module.fail_json( + msg='Error: The vrf name length must be between 1 and 31.') + + def get_interface_vpn_name(self, vpninfo, vpn_name): + """ get vpn instance name""" + + l3vpn_if = vpninfo.findall("l3vpnIf") + for l3vpn_ifinfo in l3vpn_if: + for ele in l3vpn_ifinfo: + if ele.tag in ['ifName']: + if ele.text.lower() == self.vpn_interface.lower(): + self.intf_info['vrfName'] = vpn_name + + def get_interface_vpn(self): + """ get the VPN instance associated with the interface""" + + xml_str = CE_NC_GET_VRF_INTERFACE + con_obj = get_nc_config(self.module, xml_str) + if "" in con_obj: + return + + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get global vrf interface info + root = ElementTree.fromstring(xml_str) + vpns = root.findall( + "l3vpn/l3vpncomm/l3vpnInstances/l3vpnInstance") + if vpns: + for vpnele in vpns: + vpn_name = None + for vpninfo in vpnele: + if vpninfo.tag == 'vrfName': + vpn_name = vpninfo.text + if vpninfo.tag == 'l3vpnIfs': + self.get_interface_vpn_name(vpninfo, vpn_name) + + return + + def is_vrf_exist(self): + """ judge whether the VPN instance is existed""" + + conf_str = CE_NC_GET_VRF % self.vrf + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return False + + return True + + def get_intf_conf_info(self): + """ get related configuration of the interface""" + + conf_str = CE_NC_GET_INTF % self.vpn_interface + con_obj = get_nc_config(self.module, conf_str) + if "" in con_obj: + return + + # get interface base info + xml_str = con_obj.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + interface = root.find("ifm/interfaces/interface") + if interface: + for eles in interface: + if eles.tag in ["isL2SwitchPort"]: + self.intf_info[eles.tag] = eles.text + + self.get_interface_vpn() + return + + def get_existing(self): + """get existing config""" + + self.existing = dict(vrf=self.intf_info['vrfName'], + vpn_interface=self.vpn_interface) + + def get_proposed(self): + """get_proposed""" + + self.proposed = dict(vrf=self.vrf, + vpn_interface=self.vpn_interface, + state=self.state) + + def get_end_state(self): + """get_end_state""" + + self.intf_info['vrfName'] = None + self.get_intf_conf_info() + + self.end_state = dict(vrf=self.intf_info['vrfName'], + vpn_interface=self.vpn_interface) + + def show_result(self): + """ show result""" + + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + def judge_if_config_exist(self): + """ judge whether configuration has existed""" + + if self.state == 'absent': + return False + + delta = set(self.proposed.items()).difference( + self.existing.items()) + delta = dict(delta) + if len(delta) == 1 and delta['state']: + return True + + return False + + def config_interface_vrf(self): + """ configure VPN instance of the interface""" + + if not self.conf_exist and self.state == 'present': + + xml_str = CE_NC_MERGE_VRF_INTERFACE % ( + self.vrf, self.vpn_interface) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "VRF_INTERFACE_CONFIG") + self.changed = True + elif self.state == 'absent': + xml_str = CE_NC_DEL_INTF_VPN % (self.vrf, self.vpn_interface) + ret_xml = set_nc_config(self.module, xml_str) + self.check_response(ret_xml, "DEL_VRF_INTERFACE_CONFIG") + self.changed = True + + def work(self): + """execute task""" + + self.get_intf_conf_info() + self.check_params() + self.get_existing() + self.get_proposed() + self.conf_exist = self.judge_if_config_exist() + + self.config_interface_vrf() + + self.get_update_cmd() + self.get_end_state() + self.show_result() + + +def main(): + """main""" + + argument_spec = dict( + vrf=dict(required=True, type='str'), + vpn_interface=dict(required=True, type='str'), + state=dict(choices=['absent', 'present'], + default='present', required=False), + ) + argument_spec.update(ce_argument_spec) + vrf_intf = VrfInterface(argument_spec) + vrf_intf.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrrp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrrp.py new file mode 100644 index 00000000..9d9fc349 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vrrp.py @@ -0,0 +1,1327 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vrrp +short_description: Manages VRRP interfaces on HUAWEI CloudEngine devices. +description: + - Manages VRRP interface attributes on HUAWEI CloudEngine devices. +author: + - Li Yanfeng (@numone213) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + interface: + description: + - Name of an interface. The value is a string of 1 to 63 characters. + vrid: + description: + - VRRP backup group ID. + The value is an integer ranging from 1 to 255. + default: present + virtual_ip : + description: + - Virtual IP address. The value is a string of 0 to 255 characters. + vrrp_type: + description: + - Type of a VRRP backup group. + type: str + choices: ['normal', 'member', 'admin'] + admin_ignore_if_down: + description: + - mVRRP ignores an interface Down event. + type: bool + default: 'false' + admin_vrid: + description: + - Tracked mVRRP ID. The value is an integer ranging from 1 to 255. + admin_interface: + description: + - Tracked mVRRP interface name. The value is a string of 1 to 63 characters. + admin_flowdown: + description: + - Disable the flowdown function for service VRRP. + type: bool + default: 'false' + priority: + description: + - Configured VRRP priority. + The value ranges from 1 to 254. The default value is 100. A larger value indicates a higher priority. + version: + description: + - VRRP version. The default version is v2. + type: str + choices: ['v2','v3'] + advertise_interval: + description: + - Configured interval between sending advertisements, in milliseconds. + Only the master router sends VRRP advertisements. The default value is 1000 milliseconds. + preempt_timer_delay: + description: + - Preemption delay. + The value is an integer ranging from 0 to 3600. The default value is 0. + gratuitous_arp_interval: + description: + - Interval at which gratuitous ARP packets are sent, in seconds. + The value ranges from 30 to 1200.The default value is 300. + recover_delay: + description: + - Delay in recovering after an interface goes Up. + The delay is used for interface flapping suppression. + The value is an integer ranging from 0 to 3600. + The default value is 0 seconds. + holding_multiplier: + description: + - The configured holdMultiplier.The value is an integer ranging from 3 to 10. The default value is 3. + auth_mode: + description: + - Authentication type used for VRRP packet exchanges between virtual routers. + The values are noAuthentication, simpleTextPassword, md5Authentication. + The default value is noAuthentication. + type: str + choices: ['simple','md5','none'] + is_plain: + description: + - Select the display mode of an authentication key. + By default, an authentication key is displayed in ciphertext. + type: bool + default: 'false' + auth_key: + description: + - This object is set based on the authentication type. + When noAuthentication is specified, the value is empty. + When simpleTextPassword or md5Authentication is specified, the value is a string of 1 to 8 characters + in plaintext and displayed as a blank text for security. + fast_resume: + description: + - mVRRP's fast resume mode. + type: str + choices: ['enable','disable'] + state: + description: + - Specify desired state of the resource. + type: str + default: present + choices: ['present','absent'] +''' + +EXAMPLES = ''' +- name: Vrrp module test + hosts: cloudengine + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + tasks: + - name: Set vrrp version + community.network.ce_vrrp: + version: v3 + provider: "{{ cli }}" + - name: Set vrrp gratuitous-arp interval + community.network.ce_vrrp: + gratuitous_arp_interval: 40 + mlag_id: 4 + provider: "{{ cli }}" + - name: Set vrrp recover-delay + community.network.ce_vrrp: + recover_delay: 10 + provider: "{{ cli }}" + - name: Set vrrp vrid virtual-ip + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + virtual_ip: 10.14.2.7 + provider: "{{ cli }}" + - name: Set vrrp vrid admin + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + vrrp_type: admin + provider: "{{ cli }}" + - name: Set vrrp vrid fast_resume + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + fast_resume: enable + provider: "{{ cli }}" + - name: Set vrrp vrid holding-multiplier + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + holding_multiplier: 4 + provider: "{{ cli }}" + - name: Set vrrp vrid preempt timer delay + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + preempt_timer_delay: 10 + provider: "{{ cli }}" + - name: Set vrrp vrid admin-vrrp + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + admin_interface: 40GE2/0/9 + admin_vrid: 2 + vrrp_type: member + provider: "{{ cli }}" + - name: Set vrrp vrid authentication-mode + community.network.ce_vrrp: + interface: 40GE2/0/8 + vrid: 1 + is_plain: true + auth_mode: simple + auth_key: aaa + provider: "{{ cli }}" +''' + +RETURN = ''' +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: { + "auth_key": "aaa", + "auth_mode": "simple", + "interface": "40GE2/0/8", + "is_plain": true, + "state": "present", + "vrid": "1" + } +existing: + description: k/v pairs of existing aaa server + returned: always + type: dict + sample: { + "auth_mode": "none", + "interface": "40GE2/0/8", + "is_plain": "false", + "vrid": "1", + "vrrp_type": "normal" + } +end_state: + description: k/v pairs of aaa params after module execution + returned: always + type: dict + sample: { + "auth_mode": "simple", + "interface": "40GE2/0/8", + "is_plain": "true", + "vrid": "1", + "vrrp_type": "normal" + } +updates: + description: command sent to the device + returned: always + type: list + sample: { "interface 40GE2/0/8", + "vrrp vrid 1 authentication-mode simple plain aaa"} +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + + +CE_NC_GET_VRRP_GROUP_INFO = """ + + + + + %s + %s + + + + +""" + +CE_NC_SET_VRRP_GROUP_INFO_HEAD = """ + + + + + %s + %s +""" +CE_NC_SET_VRRP_GROUP_INFO_TAIL = """ + + + + +""" +CE_NC_GET_VRRP_GLOBAL_INFO = """ + + + + + + + + + + +""" + +CE_NC_SET_VRRP_GLOBAL_HEAD = """ + + + +""" +CE_NC_SET_VRRP_GLOBAL_TAIL = """ + + + +""" + +CE_NC_GET_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + + + + + + + +""" +CE_NC_CREATE_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + %s + + + + + + +""" +CE_NC_DELETE_VRRP_VIRTUAL_IP_INFO = """ + + + + + %s + %s + + + %s + + + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('NULL'): + iftype = 'null' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + else: + return None + + return iftype.lower() + + +class Vrrp(object): + """ + Manages Manages vrrp information. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.interface = self.module.params['interface'] + self.vrid = self.module.params['vrid'] + self.virtual_ip = self.module.params['virtual_ip'] + self.vrrp_type = self.module.params['vrrp_type'] + self.admin_ignore_if_down = 'false' if self.module.params['admin_ignore_if_down'] is False else 'true' + self.admin_vrid = self.module.params['admin_vrid'] + self.admin_interface = self.module.params['admin_interface'] + self.admin_flowdown = 'false' if self.module.params['admin_flowdown'] is False else 'true' + self.priority = self.module.params['priority'] + self.version = self.module.params['version'] + self.advertise_interval = self.module.params['advertise_interval'] + self.preempt_timer_delay = self.module.params['preempt_timer_delay'] + self.gratuitous_arp_interval = self.module.params[ + 'gratuitous_arp_interval'] + self.recover_delay = self.module.params['recover_delay'] + self.holding_multiplier = self.module.params['holding_multiplier'] + self.auth_mode = self.module.params['auth_mode'] + self.is_plain = 'false' if self.module.params['is_plain'] is False else 'true' + self.auth_key = self.module.params['auth_key'] + self.fast_resume = self.module.params['fast_resume'] + self.state = self.module.params['state'] + + # vrrp info + self.vrrp_global_info = None + self.virtual_ip_info = None + self.vrrp_group_info = None + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def get_virtual_ip_info(self): + """ get vrrp virtual ip info.""" + virtual_ip_info = dict() + conf_str = CE_NC_GET_VRRP_VIRTUAL_IP_INFO % (self.vrid, self.interface) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return virtual_ip_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + virtual_ip_info["vrrpVirtualIpInfos"] = list() + root = ElementTree.fromstring(xml_str) + vrrp_virtual_ip_infos = root.findall( + "vrrp/vrrpGroups/vrrpGroup/virtualIps/virtualIp") + if vrrp_virtual_ip_infos: + for vrrp_virtual_ip_info in vrrp_virtual_ip_infos: + virtual_ip_dict = dict() + for ele in vrrp_virtual_ip_info: + if ele.tag in ["virtualIpAddress"]: + virtual_ip_dict[ele.tag] = ele.text + virtual_ip_info["vrrpVirtualIpInfos"].append( + virtual_ip_dict) + return virtual_ip_info + + def get_vrrp_global_info(self): + """ get vrrp global info.""" + + vrrp_global_info = dict() + conf_str = CE_NC_GET_VRRP_GLOBAL_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vrrp_global_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "vrrp/vrrpGlobalCfg") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["gratuitousArpTimeOut", "gratuitousArpFlag", "recoverDelay", "version"]: + vrrp_global_info[site.tag] = site.text + return vrrp_global_info + + def get_vrrp_group_info(self): + """ get vrrp group info.""" + + vrrp_group_info = dict() + conf_str = CE_NC_GET_VRRP_GROUP_INFO % (self.interface, self.vrid) + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vrrp_group_info + else: + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + root = ElementTree.fromstring(xml_str) + global_info = root.findall( + "vrrp/vrrpGroups/vrrpGroup") + + if global_info: + for tmp in global_info: + for site in tmp: + if site.tag in ["ifName", "vrrpId", "priority", "advertiseInterval", "preemptMode", "delayTime", + "authenticationMode", "authenticationKey", "vrrpType", "adminVrrpId", + "adminIfName", "adminIgnoreIfDown", "isPlain", "unflowdown", "fastResume", + "holdMultiplier"]: + vrrp_group_info[site.tag] = site.text + return vrrp_group_info + + def check_params(self): + """Check all input params""" + + # interface check + if self.interface: + intf_type = get_interface_type(self.interface) + if not intf_type: + self.module.fail_json( + msg='Error: Interface name of %s ' + 'is error.' % self.interface) + + # vrid check + if self.vrid: + if not self.vrid.isdigit(): + self.module.fail_json( + msg='Error: The value of vrid is an integer.') + if int(self.vrid) < 1 or int(self.vrid) > 255: + self.module.fail_json( + msg='Error: The value of vrid ranges from 1 to 255.') + + # virtual_ip check + if self.virtual_ip: + if not is_valid_address(self.virtual_ip): + self.module.fail_json( + msg='Error: The %s is not a valid ip address.' % self.virtual_ip) + + # admin_vrid check + if self.admin_vrid: + if not self.admin_vrid.isdigit(): + self.module.fail_json( + msg='Error: The value of admin_vrid is an integer.') + if int(self.admin_vrid) < 1 or int(self.admin_vrid) > 255: + self.module.fail_json( + msg='Error: The value of admin_vrid ranges from 1 to 255.') + + # admin_interface check + if self.admin_interface: + intf_type = get_interface_type(self.admin_interface) + if not intf_type: + self.module.fail_json( + msg='Error: Admin interface name of %s ' + 'is error.' % self.admin_interface) + + # priority check + if self.priority: + if not self.priority.isdigit(): + self.module.fail_json( + msg='Error: The value of priority is an integer.') + if int(self.priority) < 1 or int(self.priority) > 254: + self.module.fail_json( + msg='Error: The value of priority ranges from 1 to 254. The default value is 100.') + + # advertise_interval check + if self.advertise_interval: + if not self.advertise_interval.isdigit(): + self.module.fail_json( + msg='Error: The value of advertise_interval is an integer.') + if int(self.advertise_interval) < 1000 or int(self.advertise_interval) > 255000: + self.module.fail_json( + msg='Error: The value of advertise_interval ranges from 1000 to 255000 milliseconds. The default value is 1000 milliseconds.') + if int(self.advertise_interval) % 1000 != 0: + self.module.fail_json( + msg='Error: The advertisement interval value of VRRP must be a multiple of 1000 milliseconds.') + # preempt_timer_delay check + if self.preempt_timer_delay: + if not self.preempt_timer_delay.isdigit(): + self.module.fail_json( + msg='Error: The value of preempt_timer_delay is an integer.') + if int(self.preempt_timer_delay) < 1 or int(self.preempt_timer_delay) > 3600: + self.module.fail_json( + msg='Error: The value of preempt_timer_delay ranges from 1 to 3600. The default value is 0.') + + # holding_multiplier check + if self.holding_multiplier: + if not self.holding_multiplier.isdigit(): + self.module.fail_json( + msg='Error: The value of holding_multiplier is an integer.') + if int(self.holding_multiplier) < 3 or int(self.holding_multiplier) > 10: + self.module.fail_json( + msg='Error: The value of holding_multiplier ranges from 3 to 10. The default value is 3.') + + # auth_key check + if self.auth_key: + if len(self.auth_key) > 16 \ + or len(self.auth_key.replace(' ', '')) < 1: + self.module.fail_json( + msg='Error: The length of auth_key is not in the range from 1 to 16.') + + def is_virtual_ip_change(self): + """whether virtual ip change""" + + if not self.virtual_ip_info: + return True + + for info in self.virtual_ip_info["vrrpVirtualIpInfos"]: + if info["virtualIpAddress"] == self.virtual_ip: + return False + return True + + def is_virtual_ip_exist(self): + """whether virtual ip info exist""" + + if not self.virtual_ip_info: + return False + + for info in self.virtual_ip_info["vrrpVirtualIpInfos"]: + if info["virtualIpAddress"] == self.virtual_ip: + return True + return False + + def is_vrrp_global_info_change(self): + """whether vrrp global attribute info change""" + + if not self.vrrp_global_info: + return True + + if self.gratuitous_arp_interval: + if self.vrrp_global_info["gratuitousArpFlag"] == "false": + self.module.fail_json(msg="Error: gratuitousArpFlag is false.") + if self.vrrp_global_info["gratuitousArpTimeOut"] != self.gratuitous_arp_interval: + return True + if self.recover_delay: + if self.vrrp_global_info["recoverDelay"] != self.recover_delay: + return True + if self.version: + if self.vrrp_global_info["version"] != self.version: + return True + return False + + def is_vrrp_global_info_exist(self): + """whether vrrp global attribute info exist""" + + if self.gratuitous_arp_interval or self.recover_delay or self.version: + if self.gratuitous_arp_interval: + if self.vrrp_global_info["gratuitousArpFlag"] == "false": + self.module.fail_json( + msg="Error: gratuitousArpFlag is false.") + if self.vrrp_global_info["gratuitousArpTimeOut"] != self.gratuitous_arp_interval: + return False + if self.recover_delay: + if self.vrrp_global_info["recoverDelay"] != self.recover_delay: + return False + if self.version: + if self.vrrp_global_info["version"] != self.version: + return False + return True + + return False + + def is_vrrp_group_info_change(self): + """whether vrrp group attribute info change""" + if self.vrrp_type: + if self.vrrp_group_info["vrrpType"] != self.vrrp_type: + return True + if self.admin_ignore_if_down: + if self.vrrp_group_info["adminIgnoreIfDown"] != self.admin_ignore_if_down: + return True + if self.admin_vrid: + if self.vrrp_group_info["adminVrrpId"] != self.admin_vrid: + return True + if self.admin_interface: + if self.vrrp_group_info["adminIfName"] != self.admin_interface: + return True + if self.admin_flowdown: + if self.vrrp_group_info["unflowdown"] != self.admin_flowdown: + return True + if self.priority: + if self.vrrp_group_info["priority"] != self.priority: + return True + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + if self.vrrp_group_info["fastResume"] != fast_resume: + return True + if self.advertise_interval: + if self.vrrp_group_info["advertiseInterval"] != self.advertise_interval: + return True + if self.preempt_timer_delay: + if self.vrrp_group_info["delayTime"] != self.preempt_timer_delay: + return True + if self.holding_multiplier: + if self.vrrp_group_info["holdMultiplier"] != self.holding_multiplier: + return True + if self.auth_mode: + if self.vrrp_group_info["authenticationMode"] != self.auth_mode: + return True + if self.auth_key: + return True + if self.is_plain: + if self.vrrp_group_info["isPlain"] != self.is_plain: + return True + + return False + + def is_vrrp_group_info_exist(self): + """whether vrrp group attribute info exist""" + + if self.vrrp_type: + if self.vrrp_group_info["vrrpType"] != self.vrrp_type: + return False + if self.admin_ignore_if_down: + if self.vrrp_group_info["adminIgnoreIfDown"] != self.admin_ignore_if_down: + return False + if self.admin_vrid: + if self.vrrp_group_info["adminVrrpId"] != self.admin_vrid: + return False + if self.admin_interface: + if self.vrrp_group_info["adminIfName"] != self.admin_interface: + return False + if self.admin_flowdown: + if self.vrrp_group_info["unflowdown"] != self.admin_flowdown: + return False + if self.priority: + if self.vrrp_group_info["priority"] != self.priority: + return False + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + if self.vrrp_group_info["fastResume"] != fast_resume: + return False + if self.advertise_interval: + if self.vrrp_group_info["advertiseInterval"] != self.advertise_interval: + return False + if self.preempt_timer_delay: + if self.vrrp_group_info["delayTime"] != self.preempt_timer_delay: + return False + if self.holding_multiplier: + if self.vrrp_group_info["holdMultiplier"] != self.holding_multiplier: + return False + if self.auth_mode: + if self.vrrp_group_info["authenticationMode"] != self.auth_mode: + return False + if self.is_plain: + if self.vrrp_group_info["isPlain"] != self.is_plain: + return False + return True + + def create_virtual_ip(self): + """create virtual ip info""" + + if self.is_virtual_ip_change(): + conf_str = CE_NC_CREATE_VRRP_VIRTUAL_IP_INFO % ( + self.vrid, self.interface, self.virtual_ip) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: create virtual ip info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append( + "vrrp vrid %s virtual-ip %s" % (self.vrid, self.virtual_ip)) + self.changed = True + + def delete_virtual_ip(self): + """delete virtual ip info""" + + if self.is_virtual_ip_exist(): + conf_str = CE_NC_DELETE_VRRP_VIRTUAL_IP_INFO % ( + self.vrid, self.interface, self.virtual_ip) + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: delete virtual ip info failed.') + + self.updates_cmd.append("interface %s" % self.interface) + self.updates_cmd.append( + "undo vrrp vrid %s virtual-ip %s " % (self.vrid, self.virtual_ip)) + self.changed = True + + def set_vrrp_global(self): + """set vrrp global attribute info""" + + if self.is_vrrp_global_info_change(): + conf_str = CE_NC_SET_VRRP_GLOBAL_HEAD + if self.gratuitous_arp_interval: + conf_str += "%s" % self.gratuitous_arp_interval + if self.recover_delay: + conf_str += "%s" % self.recover_delay + if self.version: + conf_str += "%s" % self.version + conf_str += CE_NC_SET_VRRP_GLOBAL_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + + if self.gratuitous_arp_interval: + self.updates_cmd.append( + "vrrp gratuitous-arp interval %s" % self.gratuitous_arp_interval) + + if self.recover_delay: + self.updates_cmd.append( + "vrrp recover-delay %s" % self.recover_delay) + + if self.version: + version = "3" + if self.version == "v2": + version = "2" + self.updates_cmd.append("vrrp version %s" % version) + self.changed = True + + def delete_vrrp_global(self): + """delete vrrp global attribute info""" + + if self.is_vrrp_global_info_exist(): + conf_str = CE_NC_SET_VRRP_GLOBAL_HEAD + if self.gratuitous_arp_interval: + if self.gratuitous_arp_interval == "120": + self.module.fail_json( + msg='Error: The default value of gratuitous_arp_interval is 120.') + gratuitous_arp_interval = "120" + conf_str += "%s" % gratuitous_arp_interval + if self.recover_delay: + if self.recover_delay == "0": + self.module.fail_json( + msg='Error: The default value of recover_delay is 0.') + recover_delay = "0" + conf_str += "%s" % recover_delay + if self.version: + if self.version == "v2": + self.module.fail_json( + msg='Error: The default value of version is v2.') + version = "v2" + conf_str += "%s" % version + conf_str += CE_NC_SET_VRRP_GLOBAL_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + if self.gratuitous_arp_interval: + self.updates_cmd.append("undo vrrp gratuitous-arp interval") + + if self.recover_delay: + self.updates_cmd.append("undo vrrp recover-delay") + + if self.version == "v3": + self.updates_cmd.append("undo vrrp version") + self.changed = True + + def set_vrrp_group(self): + """set vrrp group attribute info""" + + if self.is_vrrp_group_info_change(): + conf_str = CE_NC_SET_VRRP_GROUP_INFO_HEAD % ( + self.interface, self.vrid) + if self.vrrp_type: + conf_str += "%s" % self.vrrp_type + if self.admin_vrid: + conf_str += "%s" % self.admin_vrid + if self.admin_interface: + conf_str += "%s" % self.admin_interface + if self.admin_flowdown: + conf_str += "%s" % self.admin_flowdown + if self.priority: + conf_str += "%s" % self.priority + if self.vrrp_type == "admin": + if self.admin_ignore_if_down: + conf_str += "%s" % self.admin_ignore_if_down + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + conf_str += "%s" % fast_resume + if self.advertise_interval: + conf_str += "%s" % self.advertise_interval + if self.preempt_timer_delay: + conf_str += "%s" % self.preempt_timer_delay + if self.holding_multiplier: + conf_str += "%s" % self.holding_multiplier + if self.auth_mode: + conf_str += "%s" % self.auth_mode + if self.auth_key: + conf_str += "%s" % self.auth_key + if self.auth_mode == "simple": + conf_str += "%s" % self.is_plain + + conf_str += CE_NC_SET_VRRP_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp group attribute info failed.') + if self.interface and self.vrid: + self.updates_cmd.append("interface %s" % self.interface) + if self.vrrp_type == "admin": + if self.admin_ignore_if_down == "true": + self.updates_cmd.append( + "vrrp vrid %s admin ignore-if-down" % self.vrid) + else: + self.updates_cmd.append( + "vrrp vrid %s admin" % self.vrid) + + if self.priority: + self.updates_cmd.append( + "vrrp vrid %s priority %s" % (self.vrid, self.priority)) + + if self.fast_resume == "enable": + self.updates_cmd.append( + "vrrp vrid %s fast-resume" % self.vrid) + if self.fast_resume == "disable": + self.updates_cmd.append( + "undo vrrp vrid %s fast-resume" % self.vrid) + + if self.advertise_interval: + advertise_interval = int(self.advertise_interval) / 1000 + self.updates_cmd.append("vrrp vrid %s timer advertise %s" % ( + self.vrid, int(advertise_interval))) + + if self.preempt_timer_delay: + self.updates_cmd.append("vrrp vrid %s preempt timer delay %s" % (self.vrid, + self.preempt_timer_delay)) + + if self.holding_multiplier: + self.updates_cmd.append( + "vrrp vrid %s holding-multiplier %s" % (self.vrid, self.holding_multiplier)) + + if self.admin_vrid and self.admin_interface: + if self.admin_flowdown == "true": + self.updates_cmd.append("vrrp vrid %s track admin-vrrp interface %s vrid %s unflowdown" % + (self.vrid, self.admin_interface, self.admin_vrid)) + else: + self.updates_cmd.append("vrrp vrid %s track admin-vrrp interface %s vrid %s" % + (self.vrid, self.admin_interface, self.admin_vrid)) + + if self.auth_mode and self.auth_key: + if self.auth_mode == "simple": + if self.is_plain == "true": + self.updates_cmd.append("vrrp vrid %s authentication-mode simple plain %s" % + (self.vrid, self.auth_key)) + else: + self.updates_cmd.append("vrrp vrid %s authentication-mode simple cipher %s" % + (self.vrid, self.auth_key)) + if self.auth_mode == "md5": + self.updates_cmd.append( + "vrrp vrid %s authentication-mode md5 %s" % (self.vrid, self.auth_key)) + self.changed = True + + def delete_vrrp_group(self): + """delete vrrp group attribute info""" + + if self.is_vrrp_group_info_exist(): + conf_str = CE_NC_SET_VRRP_GROUP_INFO_HEAD % ( + self.interface, self.vrid) + if self.vrrp_type: + vrrp_type = self.vrrp_type + if self.vrrp_type == "admin": + vrrp_type = "normal" + if self.vrrp_type == "member" and self.admin_vrid and self.admin_interface: + vrrp_type = "normal" + conf_str += "%s" % vrrp_type + if self.priority: + if self.priority == "100": + self.module.fail_json( + msg='Error: The default value of priority is 100.') + priority = "100" + conf_str += "%s" % priority + + if self.fast_resume: + fast_resume = "false" + if self.fast_resume == "enable": + fast_resume = "true" + conf_str += "%s" % fast_resume + if self.advertise_interval: + if self.advertise_interval == "1000": + self.module.fail_json( + msg='Error: The default value of advertise_interval is 1000.') + advertise_interval = "1000" + conf_str += "%s" % advertise_interval + if self.preempt_timer_delay: + if self.preempt_timer_delay == "0": + self.module.fail_json( + msg='Error: The default value of preempt_timer_delay is 0.') + preempt_timer_delay = "0" + conf_str += "%s" % preempt_timer_delay + if self.holding_multiplier: + if self.holding_multiplier == "0": + self.module.fail_json( + msg='Error: The default value of holding_multiplier is 3.') + holding_multiplier = "3" + conf_str += "%s" % holding_multiplier + if self.auth_mode: + auth_mode = self.auth_mode + if self.auth_mode == "md5" or self.auth_mode == "simple": + auth_mode = "none" + conf_str += "%s" % auth_mode + + conf_str += CE_NC_SET_VRRP_GROUP_INFO_TAIL + recv_xml = set_nc_config(self.module, conf_str) + if "" not in recv_xml: + self.module.fail_json( + msg='Error: set vrrp global attribute info failed.') + if self.interface and self.vrid: + self.updates_cmd.append("interface %s" % self.interface) + if self.vrrp_type == "admin": + self.updates_cmd.append( + "undo vrrp vrid %s admin" % self.vrid) + + if self.priority: + self.updates_cmd.append( + "undo vrrp vrid %s priority" % self.vrid) + + if self.fast_resume: + self.updates_cmd.append( + "undo vrrp vrid %s fast-resume" % self.vrid) + + if self.advertise_interval: + self.updates_cmd.append( + "undo vrrp vrid %s timer advertise" % self.vrid) + + if self.preempt_timer_delay: + self.updates_cmd.append( + "undo vrrp vrid %s preempt timer delay" % self.vrid) + + if self.holding_multiplier: + self.updates_cmd.append( + "undo vrrp vrid %s holding-multiplier" % self.vrid) + + if self.admin_vrid and self.admin_interface: + self.updates_cmd.append( + "undo vrrp vrid %s track admin-vrrp" % self.vrid) + + if self.auth_mode: + self.updates_cmd.append( + "undo vrrp vrid %s authentication-mode" % self.vrid) + self.changed = True + + def get_proposed(self): + """get proposed info""" + + if self.interface: + self.proposed["interface"] = self.interface + if self.vrid: + self.proposed["vrid"] = self.vrid + if self.virtual_ip: + self.proposed["virtual_ip"] = self.virtual_ip + if self.vrrp_type: + self.proposed["vrrp_type"] = self.vrrp_type + if self.admin_vrid: + self.proposed["admin_vrid"] = self.admin_vrid + if self.admin_interface: + self.proposed["admin_interface"] = self.admin_interface + if self.admin_flowdown: + self.proposed["unflowdown"] = self.admin_flowdown + if self.admin_ignore_if_down: + self.proposed["admin_ignore_if_down"] = self.admin_ignore_if_down + if self.priority: + self.proposed["priority"] = self.priority + if self.version: + self.proposed["version"] = self.version + if self.advertise_interval: + self.proposed["advertise_interval"] = self.advertise_interval + if self.preempt_timer_delay: + self.proposed["preempt_timer_delay"] = self.preempt_timer_delay + if self.gratuitous_arp_interval: + self.proposed[ + "gratuitous_arp_interval"] = self.gratuitous_arp_interval + if self.recover_delay: + self.proposed["recover_delay"] = self.recover_delay + if self.holding_multiplier: + self.proposed["holding_multiplier"] = self.holding_multiplier + if self.auth_mode: + self.proposed["auth_mode"] = self.auth_mode + if self.is_plain: + self.proposed["is_plain"] = self.is_plain + if self.auth_key: + self.proposed["auth_key"] = self.auth_key + if self.fast_resume: + self.proposed["fast_resume"] = self.fast_resume + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.gratuitous_arp_interval: + self.existing["gratuitous_arp_interval"] = self.vrrp_global_info[ + "gratuitousArpTimeOut"] + if self.version: + self.existing["version"] = self.vrrp_global_info["version"] + if self.recover_delay: + self.existing["recover_delay"] = self.vrrp_global_info[ + "recoverDelay"] + + if self.virtual_ip: + if self.virtual_ip_info: + self.existing["interface"] = self.interface + self.existing["vrid"] = self.vrid + self.existing["virtual_ip_info"] = self.virtual_ip_info[ + "vrrpVirtualIpInfos"] + + if self.vrrp_group_info: + self.existing["interface"] = self.vrrp_group_info["ifName"] + self.existing["vrid"] = self.vrrp_group_info["vrrpId"] + self.existing["vrrp_type"] = self.vrrp_group_info["vrrpType"] + if self.vrrp_type == "admin": + self.existing["admin_ignore_if_down"] = self.vrrp_group_info[ + "adminIgnoreIfDown"] + if self.admin_vrid and self.admin_interface: + self.existing["admin_vrid"] = self.vrrp_group_info[ + "adminVrrpId"] + self.existing["admin_interface"] = self.vrrp_group_info[ + "adminIfName"] + self.existing["admin_flowdown"] = self.vrrp_group_info[ + "unflowdown"] + if self.priority: + self.existing["priority"] = self.vrrp_group_info["priority"] + if self.advertise_interval: + self.existing["advertise_interval"] = self.vrrp_group_info[ + "advertiseInterval"] + if self.preempt_timer_delay: + self.existing["preempt_timer_delay"] = self.vrrp_group_info[ + "delayTime"] + if self.holding_multiplier: + self.existing["holding_multiplier"] = self.vrrp_group_info[ + "holdMultiplier"] + if self.fast_resume: + fast_resume_exist = "disable" + fast_resume = self.vrrp_group_info["fastResume"] + if fast_resume == "true": + fast_resume_exist = "enable" + self.existing["fast_resume"] = fast_resume_exist + if self.auth_mode: + self.existing["auth_mode"] = self.vrrp_group_info[ + "authenticationMode"] + self.existing["is_plain"] = self.vrrp_group_info["isPlain"] + + def get_end_state(self): + """get end state info""" + + if self.gratuitous_arp_interval or self.version or self.recover_delay: + self.vrrp_global_info = self.get_vrrp_global_info() + if self.interface and self.vrid: + if self.virtual_ip: + self.virtual_ip_info = self.get_virtual_ip_info() + if self.virtual_ip_info: + self.vrrp_group_info = self.get_vrrp_group_info() + + if self.gratuitous_arp_interval: + self.end_state["gratuitous_arp_interval"] = self.vrrp_global_info[ + "gratuitousArpTimeOut"] + if self.version: + self.end_state["version"] = self.vrrp_global_info["version"] + if self.recover_delay: + self.end_state["recover_delay"] = self.vrrp_global_info[ + "recoverDelay"] + + if self.virtual_ip: + if self.virtual_ip_info: + self.end_state["interface"] = self.interface + self.end_state["vrid"] = self.vrid + self.end_state["virtual_ip_info"] = self.virtual_ip_info[ + "vrrpVirtualIpInfos"] + + if self.vrrp_group_info: + self.end_state["interface"] = self.vrrp_group_info["ifName"] + self.end_state["vrid"] = self.vrrp_group_info["vrrpId"] + self.end_state["vrrp_type"] = self.vrrp_group_info["vrrpType"] + if self.vrrp_type == "admin": + self.end_state["admin_ignore_if_down"] = self.vrrp_group_info[ + "adminIgnoreIfDown"] + if self.admin_vrid and self.admin_interface: + self.end_state["admin_vrid"] = self.vrrp_group_info[ + "adminVrrpId"] + self.end_state["admin_interface"] = self.vrrp_group_info[ + "adminIfName"] + self.end_state["admin_flowdown"] = self.vrrp_group_info[ + "unflowdown"] + if self.priority: + self.end_state["priority"] = self.vrrp_group_info["priority"] + if self.advertise_interval: + self.end_state["advertise_interval"] = self.vrrp_group_info[ + "advertiseInterval"] + if self.preempt_timer_delay: + self.end_state["preempt_timer_delay"] = self.vrrp_group_info[ + "delayTime"] + if self.holding_multiplier: + self.end_state["holding_multiplier"] = self.vrrp_group_info[ + "holdMultiplier"] + if self.fast_resume: + fast_resume_end = "disable" + fast_resume = self.vrrp_group_info["fastResume"] + if fast_resume == "true": + fast_resume_end = "enable" + self.end_state["fast_resume"] = fast_resume_end + if self.auth_mode: + self.end_state["auth_mode"] = self.vrrp_group_info[ + "authenticationMode"] + self.end_state["is_plain"] = self.vrrp_group_info["isPlain"] + if self.existing == self.end_state: + self.changed = False + + def work(self): + """worker""" + + self.check_params() + if self.gratuitous_arp_interval or self.version or self.recover_delay: + self.vrrp_global_info = self.get_vrrp_global_info() + if self.interface and self.vrid: + self.virtual_ip_info = self.get_virtual_ip_info() + if self.virtual_ip_info: + self.vrrp_group_info = self.get_vrrp_group_info() + self.get_proposed() + self.get_existing() + + if self.gratuitous_arp_interval or self.version or self.recover_delay: + if self.state == "present": + self.set_vrrp_global() + else: + self.delete_vrrp_global() + else: + if not self.interface or not self.vrid: + self.module.fail_json( + msg='Error: interface, vrid must be config at the same time.') + + if self.interface and self.vrid: + if self.virtual_ip: + if self.state == "present": + self.create_virtual_ip() + else: + self.delete_virtual_ip() + else: + if not self.vrrp_group_info: + self.module.fail_json( + msg='Error: The VRRP group does not exist.') + if self.admin_ignore_if_down == "true": + if self.vrrp_type != "admin": + self.module.fail_json( + msg='Error: vrrpType must be admin when admin_ignore_if_down is true.') + if self.admin_interface or self.admin_vrid: + if self.vrrp_type != "member": + self.module.fail_json( + msg='Error: it binds a VRRP group to an mVRRP group, vrrp_type must be "member".') + if not self.vrrp_type or not self.interface or not self.vrid: + self.module.fail_json( + msg='Error: admin_interface admin_vrid vrrp_type interface vrid must ' + 'be config at the same time.') + if self.auth_mode == "md5" and self.is_plain == "true": + self.module.fail_json( + msg='Error: is_plain can not be True when auth_mode is md5.') + + if self.state == "present": + self.set_vrrp_group() + else: + self.delete_vrrp_group() + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """ Module main """ + + argument_spec = dict( + interface=dict(type='str'), + vrid=dict(type='str'), + virtual_ip=dict(type='str'), + vrrp_type=dict(type='str', choices=['normal', 'member', 'admin']), + admin_ignore_if_down=dict(type='bool', default=False), + admin_vrid=dict(type='str'), + admin_interface=dict(type='str'), + admin_flowdown=dict(type='bool', default=False), + priority=dict(type='str'), + version=dict(type='str', choices=['v2', 'v3']), + advertise_interval=dict(type='str'), + preempt_timer_delay=dict(type='str'), + gratuitous_arp_interval=dict(type='str'), + recover_delay=dict(type='str'), + holding_multiplier=dict(type='str'), + auth_mode=dict(type='str', choices=['simple', 'md5', 'none']), + is_plain=dict(type='bool', default=False), + auth_key=dict(type='str', no_log=True), + fast_resume=dict(type='str', choices=['enable', 'disable']), + state=dict(type='str', default='present', + choices=['present', 'absent']) + ) + + argument_spec.update(ce_argument_spec) + module = Vrrp(argument_spec=argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_arp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_arp.py new file mode 100644 index 00000000..bcb1659b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_arp.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_arp +short_description: Manages ARP attributes of VXLAN on HUAWEI CloudEngine devices. +description: + - Manages ARP attributes of VXLAN on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + evn_bgp: + description: + - Enables EVN BGP. + choices: ['enable', 'disable'] + evn_source_ip: + description: + - Specifies the source address of an EVN BGP peer. + The value is in dotted decimal notation. + evn_peer_ip: + description: + - Specifies the IP address of an EVN BGP peer. + The value is in dotted decimal notation. + evn_server: + description: + - Configures the local device as the router reflector (RR) on the EVN network. + choices: ['enable', 'disable'] + evn_reflect_client: + description: + - Configures the local device as the route reflector (RR) and its peer as the client. + choices: ['enable', 'disable'] + vbdif_name: + description: + - Full name of VBDIF interface, i.e. Vbdif100. + arp_collect_host: + description: + - Enables EVN BGP or BGP EVPN to collect host information. + choices: ['enable', 'disable'] + host_collect_protocol: + description: + - Enables EVN BGP or BGP EVPN to advertise host information. + choices: ['bgp','none'] + bridge_domain_id: + description: + - Specifies a BD(bridge domain) ID. + The value is an integer ranging from 1 to 16777215. + arp_suppress: + description: + - Enables ARP broadcast suppression in a BD. + choices: ['enable', 'disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan arp module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configure EVN BGP on Layer 2 and Layer 3 VXLAN gateways to establish EVN BGP peer relationships. + community.network.ce_vxlan_arp: + evn_bgp: enable + evn_source_ip: 6.6.6.6 + evn_peer_ip: 7.7.7.7 + provider: "{{ cli }}" + - name: Configure a Layer 3 VXLAN gateway as a BGP RR. + community.network.ce_vxlan_arp: + evn_bgp: enable + evn_server: enable + provider: "{{ cli }}" + - name: Enable EVN BGP on a Layer 3 VXLAN gateway to collect host information. + community.network.ce_vxlan_arp: + vbdif_name: Vbdif100 + arp_collect_host: enable + provider: "{{ cli }}" + - name: Enable Layer 2 and Layer 3 VXLAN gateways to use EVN BGP to advertise host information. + community.network.ce_vxlan_arp: + host_collect_protocol: bgp + provider: "{{ cli }}" + - name: Enable ARP broadcast suppression on a Layer 2 VXLAN gateway. + community.network.ce_vxlan_arp: + bridge_domain_id: 100 + arp_suppress: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"evn_bgp": "enable", "evn_source_ip": "6.6.6.6", "evn_peer_ip":"7.7.7.7", state: "present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"evn_bgp": "disable", "evn_source_ip": null, "evn_peer_ip": []} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"evn_bgp": "enable", "evn_source_ip": "6.6.6.6", "evn_peer_ip": ["7.7.7.7"]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["evn bgp", + "source-address 6.6.6.6", + "peer 7.7.7.7"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_v4addr(addr): + """check is ipv4 addr is valid""" + + if addr.count('.') == 3: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def get_evn_peers(config): + """get evn peer ip list""" + + get = re.findall(r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return list(set(get)) + + +def get_evn_srouce(config): + """get evn peer ip list""" + + get = re.findall( + r"source-address ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return get[0] + + +def get_evn_reflect_client(config): + """get evn reflect client list""" + + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s*reflect-client", config) + if not get: + return None + else: + return list(get) + + +class VxlanArp(object): + """ + Manages arp attributes of VXLAN. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.evn_bgp = self.module.params['evn_bgp'] + self.evn_source_ip = self.module.params['evn_source_ip'] + self.evn_peer_ip = self.module.params['evn_peer_ip'] + self.evn_server = self.module.params['evn_server'] + self.evn_reflect_client = self.module.params['evn_reflect_client'] + self.vbdif_name = self.module.params['vbdif_name'] + self.arp_collect_host = self.module.params['arp_collect_host'] + self.host_collect_protocol = self.module.params[ + 'host_collect_protocol'] + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.arp_suppress = self.module.params['arp_suppress'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" # current config + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + required_together = [("vbdif_name", "arp_collect_host"), ("bridge_domain_id", "arp_suppress")] + self.module = AnsibleModule(argument_spec=self.spec, + required_together=required_together, + supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = r"| ignore-case section include evn bgp|host collect protocol bgp" + if self.vbdif_name: + exp += r"|^#\s+interface %s\s+" % self.vbdif_name.lower().capitalize().replace(" ", "") + + if self.bridge_domain_id: + exp += r"|^#\s+bridge-domain %s\s+" % self.bridge_domain_id + + flags.append(exp) + cfg_str = self.get_config(flags) + config = cfg_str.split("\n") + + exist_config = "" + for cfg in config: + if not cfg.startswith("display"): + exist_config += cfg + return exist_config + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_bridge_domain(self): + """manage bridge domain configuration""" + + if not self.bridge_domain_id: + return + + # bridge-domain bd-id + # [undo] arp broadcast-suppress enable + + cmd = "bridge-domain %s" % self.bridge_domain_id + if not is_config_exist(self.config, cmd): + self.module.fail_json(msg="Error: Bridge domain %s is not exist." % self.bridge_domain_id) + + cmd = "arp broadcast-suppress enable" + exist = is_config_exist(self.config, cmd) + if self.arp_suppress == "enable" and not exist: + self.cli_add_command("bridge-domain %s" % self.bridge_domain_id) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.arp_suppress == "disable" and exist: + self.cli_add_command("bridge-domain %s" % self.bridge_domain_id) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_evn_bgp(self): + """enables EVN BGP and configure evn bgp command""" + + evn_bgp_view = False + evn_bgp_enable = False + + cmd = "evn bgp" + exist = is_config_exist(self.config, cmd) + if self.evn_bgp == "enable" or exist: + evn_bgp_enable = True + + # [undo] evn bgp + if self.evn_bgp: + if self.evn_bgp == "enable" and not exist: + self.cli_add_command(cmd) + evn_bgp_view = True + elif self.evn_bgp == "disable" and exist: + self.cli_add_command(cmd, undo=True) + return + + # [undo] source-address ip-address + if evn_bgp_enable and self.evn_source_ip: + cmd = "source-address %s" % self.evn_source_ip + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] peer ip-address + # [undo] peer ipv4-address reflect-client + if evn_bgp_enable and self.evn_peer_ip: + cmd = "peer %s" % self.evn_peer_ip + exist = is_config_exist(self.config, cmd) + if self.state == "present": + if not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + if self.evn_reflect_client == "enable": + self.cli_add_command( + "peer %s reflect-client" % self.evn_peer_ip) + else: + if self.evn_reflect_client: + cmd = "peer %s reflect-client" % self.evn_peer_ip + exist = is_config_exist(self.config, cmd) + if self.evn_reflect_client == "enable" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.evn_reflect_client == "disable" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + else: + if exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] server enable + if evn_bgp_enable and self.evn_server: + cmd = "server enable" + exist = is_config_exist(self.config, cmd) + if self.evn_server == "enable" and not exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd) + elif self.evn_server == "disable" and exist: + if not evn_bgp_view: + self.cli_add_command("evn bgp") + evn_bgp_view = True + self.cli_add_command(cmd, undo=True) + + if evn_bgp_view: + self.cli_add_command("quit") + + def config_vbdif(self): + """configure command at the VBDIF interface view""" + + # interface vbdif bd-id + # [undo] arp collect host enable + + cmd = "interface %s" % self.vbdif_name.lower().capitalize() + exist = is_config_exist(self.config, cmd) + + if not exist: + self.module.fail_json( + msg="Error: Interface %s does not exist." % self.vbdif_name) + + cmd = "arp collect host enable" + exist = is_config_exist(self.config, cmd) + if self.arp_collect_host == "enable" and not exist: + self.cli_add_command("interface %s" % + self.vbdif_name.lower().capitalize()) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.arp_collect_host == "disable" and exist: + self.cli_add_command("interface %s" % + self.vbdif_name.lower().capitalize()) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_host_collect_protocal(self): + """Enable EVN BGP or BGP EVPN to advertise host information""" + + # [undo] host collect protocol bgp + cmd = "host collect protocol bgp" + exist = is_config_exist(self.config, cmd) + + if self.state == "present": + if self.host_collect_protocol == "bgp" and not exist: + self.cli_add_command(cmd) + elif self.host_collect_protocol == "none" and exist: + self.cli_add_command(cmd, undo=True) + else: + if self.host_collect_protocol == "bgp" and exist: + self.cli_add_command(cmd, undo=True) + + def is_valid_vbdif(self, ifname): + """check is interface vbdif is valid""" + + if not ifname.upper().startswith('VBDIF'): + return False + bdid = self.vbdif_name.replace(" ", "").upper().replace("VBDIF", "") + if not bdid.isdigit(): + return False + if int(bdid) < 1 or int(bdid) > 16777215: + return False + + return True + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: Bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: Bridge domain id is not in the range from 1 to 16777215.") + + # evn_source_ip check + if self.evn_source_ip: + if not is_valid_v4addr(self.evn_source_ip): + self.module.fail_json(msg="Error: evn_source_ip is invalid.") + + # evn_peer_ip check + if self.evn_peer_ip: + if not is_valid_v4addr(self.evn_peer_ip): + self.module.fail_json(msg="Error: evn_peer_ip is invalid.") + + # vbdif_name check + if self.vbdif_name: + self.vbdif_name = self.vbdif_name.replace( + " ", "").lower().capitalize() + if not self.is_valid_vbdif(self.vbdif_name): + self.module.fail_json(msg="Error: vbdif_name is invalid.") + + # evn_reflect_client and evn_peer_ip must set at the same time + if self.evn_reflect_client and not self.evn_peer_ip: + self.module.fail_json( + msg="Error: evn_reflect_client and evn_peer_ip must set at the same time.") + + # evn_server and evn_reflect_client can not set at the same time + if self.evn_server == "enable" and self.evn_reflect_client == "enable": + self.module.fail_json( + msg="Error: evn_server and evn_reflect_client can not set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.evn_bgp: + self.proposed["evn_bgp"] = self.evn_bgp + if self.evn_source_ip: + self.proposed["evn_source_ip"] = self.evn_source_ip + if self.evn_peer_ip: + self.proposed["evn_peer_ip"] = self.evn_peer_ip + if self.evn_server: + self.proposed["evn_server"] = self.evn_server + if self.evn_reflect_client: + self.proposed["evn_reflect_client"] = self.evn_reflect_client + if self.arp_collect_host: + self.proposed["arp_collect_host"] = self.arp_collect_host + if self.host_collect_protocol: + self.proposed["host_collect_protocol"] = self.host_collect_protocol + if self.arp_suppress: + self.proposed["arp_suppress"] = self.arp_suppress + if self.vbdif_name: + self.proposed["vbdif_name"] = self.evn_peer_ip + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + evn_bgp_exist = is_config_exist(self.config, "evn bgp") + if evn_bgp_exist: + self.existing["evn_bgp"] = "enable" + else: + self.existing["evn_bgp"] = "disable" + + if evn_bgp_exist: + if is_config_exist(self.config, "server enable"): + self.existing["evn_server"] = "enable" + else: + self.existing["evn_server"] = "disable" + + self.existing["evn_source_ip"] = get_evn_srouce(self.config) + self.existing["evn_peer_ip"] = get_evn_peers(self.config) + self.existing["evn_reflect_client"] = get_evn_reflect_client( + self.config) + + if is_config_exist(self.config, "arp collect host enable"): + self.existing["host_collect_protocol"] = "enable" + else: + self.existing["host_collect_protocol"] = "disable" + + if is_config_exist(self.config, "host collect protocol bgp"): + self.existing["host_collect_protocol"] = "bgp" + else: + self.existing["host_collect_protocol"] = None + + if is_config_exist(self.config, "arp broadcast-suppress enable"): + self.existing["arp_suppress"] = "enable" + else: + self.existing["arp_suppress"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + evn_bgp_exist = is_config_exist(config, "evn bgp") + if evn_bgp_exist: + self.end_state["evn_bgp"] = "enable" + else: + self.end_state["evn_bgp"] = "disable" + + if evn_bgp_exist: + if is_config_exist(config, "server enable"): + self.end_state["evn_server"] = "enable" + else: + self.end_state["evn_server"] = "disable" + + self.end_state["evn_source_ip"] = get_evn_srouce(config) + self.end_state["evn_peer_ip"] = get_evn_peers(config) + self.end_state[ + "evn_reflect_client"] = get_evn_reflect_client(config) + + if is_config_exist(config, "arp collect host enable"): + self.end_state["host_collect_protocol"] = "enable" + else: + self.end_state["host_collect_protocol"] = "disable" + + if is_config_exist(config, "host collect protocol bgp"): + self.end_state["host_collect_protocol"] = "bgp" + else: + self.end_state["host_collect_protocol"] = None + + if is_config_exist(config, "arp broadcast-suppress enable"): + self.end_state["arp_suppress"] = "enable" + else: + self.end_state["arp_suppress"] = "disable" + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.evn_bgp or self.evn_server or self.evn_peer_ip or self.evn_source_ip: + self.config_evn_bgp() + + if self.vbdif_name and self.arp_collect_host: + self.config_vbdif() + + if self.host_collect_protocol: + self.config_host_collect_protocal() + + if self.bridge_domain_id and self.arp_suppress: + self.config_bridge_domain() + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + evn_bgp=dict(required=False, type='str', + choices=['enable', 'disable']), + evn_source_ip=dict(required=False, type='str'), + evn_peer_ip=dict(required=False, type='str'), + evn_server=dict(required=False, type='str', + choices=['enable', 'disable']), + evn_reflect_client=dict( + required=False, type='str', choices=['enable', 'disable']), + vbdif_name=dict(required=False, type='str'), + arp_collect_host=dict(required=False, type='str', + choices=['enable', 'disable']), + host_collect_protocol=dict( + required=False, type='str', choices=['bgp', 'none']), + bridge_domain_id=dict(required=False, type='str'), + arp_suppress=dict(required=False, type='str', + choices=['enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanArp(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_gateway.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_gateway.py new file mode 100644 index 00000000..8d8ffd8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_gateway.py @@ -0,0 +1,936 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_gateway +short_description: Manages gateway for the VXLAN network on HUAWEI CloudEngine devices. +description: + - Configuring Centralized All-Active Gateways or Distributed Gateway for + the VXLAN Network on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Ensure All-Active Gateways or Distributed Gateway for the VXLAN Network can not configure at the same time. + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + dfs_id: + description: + - Specifies the ID of a DFS group. + The value must be 1. + dfs_source_ip: + description: + - Specifies the IPv4 address bound to a DFS group. + The value is in dotted decimal notation. + dfs_source_vpn: + description: + - Specifies the name of a VPN instance bound to a DFS group. + The value is a string of 1 to 31 case-sensitive characters without spaces. + If the character string is quoted by double quotation marks, the character string can contain spaces. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + dfs_udp_port: + description: + - Specifies the UDP port number of the DFS group. + The value is an integer that ranges from 1025 to 65535. + dfs_all_active: + description: + - Creates all-active gateways. + choices: ['enable', 'disable'] + dfs_peer_ip: + description: + - Configure the IP address of an all-active gateway peer. + The value is in dotted decimal notation. + dfs_peer_vpn: + description: + - Specifies the name of the VPN instance that is associated with all-active gateway peer. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vpn_instance: + description: + - Specifies the name of a VPN instance. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vpn_vni: + description: + - Specifies a VNI ID. + Binds a VXLAN network identifier (VNI) to a virtual private network (VPN) instance. + The value is an integer ranging from 1 to 16000000. + vbdif_name: + description: + - Full name of VBDIF interface, i.e. Vbdif100. + vbdif_bind_vpn: + description: + - Specifies the name of the VPN instance that is associated with the interface. + The value is a string of 1 to 31 case-sensitive characters, spaces not supported. + When double quotation marks are used around the string, spaces are allowed in the string. + The value C(_public_) is reserved and cannot be used as the VPN instance name. + vbdif_mac: + description: + - Specifies a MAC address for a VBDIF interface. + The value is in the format of H-H-H. Each H is a 4-digit hexadecimal number, such as C(00e0) or C(fc01). + If an H contains less than four digits, 0s are added ahead. For example, C(e0) is equal to C(00e0). + A MAC address cannot be all 0s or 1s or a multicast MAC address. + arp_distribute_gateway: + description: + - Enable the distributed gateway function on VBDIF interface. + choices: ['enable','disable'] + arp_direct_route: + description: + - Enable VLINK direct route on VBDIF interface. + choices: ['enable','disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan gateway module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Configuring Centralized All-Active Gateways for the VXLAN Network + community.network.ce_vxlan_gateway: + dfs_id: 1 + dfs_source_ip: 6.6.6.6 + dfs_all_active: enable + dfs_peer_ip: 7.7.7.7 + provider: "{{ cli }}" + - name: Bind the VPN instance to a Layer 3 gateway, enable distributed gateway, and configure host route advertisement. + community.network.ce_vxlan_gateway: + vbdif_name: Vbdif100 + vbdif_bind_vpn: vpn1 + arp_distribute_gateway: enable + arp_direct_route: enable + provider: "{{ cli }}" + - name: Assign a VNI to a VPN instance. + community.network.ce_vxlan_gateway: + vpn_instance: vpn1 + vpn_vni: 100 + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "dfs_source_ip": "6.6.6.6", "dfs_all_active":"enable", "dfs_peer_ip": "7.7.7.7"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "dfs_source_ip": null, "evn_peer_ip": [], "dfs_all_active": "disable"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"dfs_id": "1", "evn_source_ip": "6.6.6.6", "evn_source_vpn": null, + "evn_peers": [{"ip": "7.7.7.7", "vpn": ""}], "dfs_all_active": "enable"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["dfs-group 1", + "source ip 6.6.6.6", + "active-active-gateway", + "peer 7.7.7.7"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def is_valid_v4addr(addr): + """check is ipv4 addr""" + + if not addr: + return False + + if addr.count('.') == 3: + addr_list = addr.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +def mac_format(mac): + """convert mac format to xxxx-xxxx-xxxx""" + + if not mac: + return None + + if mac.count("-") != 2: + return None + + addrs = mac.split("-") + for i in range(3): + if not addrs[i] or not addrs[i].isalnum(): + return None + if len(addrs[i]) < 1 or len(addrs[i]) > 4: + return None + try: + addrs[i] = int(addrs[i], 16) + except ValueError: + return None + + try: + return "%04x-%04x-%04x" % (addrs[0], addrs[1], addrs[2]) + except ValueError: + return None + except TypeError: + return None + + +def get_dfs_source_ip(config): + """get dfs source ip address""" + + get = re.findall(r"source ip ([0-9]+.[0-9]+.[0-9]+.[0-9]+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_source_vpn(config): + """get dfs source ip vpn instance name""" + + get = re.findall( + r"source ip [0-9]+.[0-9]+.[0-9]+.[0-9]+ vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_udp_port(config): + """get dfs udp port""" + + get = re.findall(r"udp port (\d+)", config) + if not get: + return None + else: + return get[0] + + +def get_dfs_peers(config): + """get evn peer ip list""" + + get = re.findall( + r"peer ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\s?(vpn-instance)?\s?(\S*)", config) + if not get: + return None + else: + peers = list() + for item in get: + peers.append(dict(ip=item[0], vpn=item[2])) + return peers + + +def get_ip_vpn(config): + """get ip vpn instance""" + + get = re.findall(r"ip vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_ip_vpn_vni(config): + """get ip vpn vxlan vni""" + + get = re.findall(r"vxlan vni (\d+)", config) + if not get: + return None + else: + return get[0] + + +def get_vbdif_vpn(config): + """get ip vpn name of interface vbdif""" + + get = re.findall(r"ip binding vpn-instance (\S+)", config) + if not get: + return None + else: + return get[0] + + +def get_vbdif_mac(config): + """get mac address of interface vbdif""" + + get = re.findall( + r" mac-address ([0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4})", config) + if not get: + return None + else: + return get[0] + + +class VxlanGateway(object): + """ + Manages Gateway for the VXLAN Network. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.dfs_id = self.module.params['dfs_id'] + self.dfs_source_ip = self.module.params['dfs_source_ip'] + self.dfs_source_vpn = self.module.params['dfs_source_vpn'] + self.dfs_udp_port = self.module.params['dfs_udp_port'] + self.dfs_all_active = self.module.params['dfs_all_active'] + self.dfs_peer_ip = self.module.params['dfs_peer_ip'] + self.dfs_peer_vpn = self.module.params['dfs_peer_vpn'] + self.vpn_instance = self.module.params['vpn_instance'] + self.vpn_vni = self.module.params['vpn_vni'] + self.vbdif_name = self.module.params['vbdif_name'] + self.vbdif_mac = self.module.params['vbdif_mac'] + self.vbdif_bind_vpn = self.module.params['vbdif_bind_vpn'] + self.arp_distribute_gateway = self.module.params['arp_distribute_gateway'] + self.arp_direct_route = self.module.params['arp_direct_route'] + self.state = self.module.params['state'] + + # host info + self.host = self.module.params['host'] + self.username = self.module.params['username'] + self.port = self.module.params['port'] + + # state + self.config = "" # current config + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = r" | ignore-case section include ^#\s+dfs-group" + if self.vpn_instance: + exp += r"|^#\s+ip vpn-instance %s" % self.vpn_instance + if self.vbdif_name: + exp += r"|^#\s+interface %s" % self.vbdif_name + flags.append(exp) + return self.get_config(flags) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def config_dfs_group(self): + """manage Dynamic Fabric Service (DFS) group configuration""" + + if not self.dfs_id: + return + + dfs_view = False + view_cmd = "dfs-group %s" % self.dfs_id + exist = is_config_exist(self.config, view_cmd) + if self.state == "present" and not exist: + self.cli_add_command(view_cmd) + dfs_view = True + + # undo dfs-group dfs-group-id + if self.state == "absent" and exist: + if not self.dfs_source_ip and not self.dfs_udp_port and not self.dfs_all_active and not self.dfs_peer_ip: + self.cli_add_command(view_cmd, undo=True) + return + + # [undo] source ip ip-address [ vpn-instance vpn-instance-name ] + if self.dfs_source_ip: + cmd = "source ip %s" % self.dfs_source_ip + if self.dfs_source_vpn: + cmd += " vpn-instance %s" % self.dfs_source_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd) + if self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] udp port port-number + if self.dfs_udp_port: + cmd = "udp port %s" % self.dfs_udp_port + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] active-active-gateway + # [undo]peer[ vpn-instance vpn-instance-name ] + aa_cmd = "active-active-gateway" + aa_exist = is_config_exist(self.config, aa_cmd) + aa_view = False + if self.dfs_all_active == "disable": + if aa_exist: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_source_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd, undo=True) + elif self.dfs_all_active == "enable": + if not aa_exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + aa_view = True + + if self.dfs_peer_ip: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_peer_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + if not aa_view: + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + if not aa_view: + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + else: # not input dfs_all_active + if aa_exist and self.dfs_peer_ip: + cmd = "peer %s" % self.dfs_peer_ip + if self.dfs_peer_vpn: + cmd += " vpn-instance %s" % self.dfs_peer_vpn + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + if not dfs_view: + self.cli_add_command(view_cmd) + dfs_view = True + self.cli_add_command(aa_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + else: + pass + elif not aa_exist and self.dfs_peer_ip and self.state == "present": + self.module.fail_json( + msg="Error: All-active gateways is not enable.") + else: + pass + + if dfs_view: + self.cli_add_command("quit") + + def config_ip_vpn(self): + """configure command at the ip vpn view""" + + if not self.vpn_instance or not self.vpn_vni: + return + + # ip vpn-instance vpn-instance-name + view_cmd = "ip vpn-instance %s" % self.vpn_instance + exist = is_config_exist(self.config, view_cmd) + if not exist: + self.module.fail_json( + msg="Error: ip vpn instance %s is not exist." % self.vpn_instance) + + # [undo] vxlan vni vni-id + cmd = "vxlan vni %s" % self.vpn_vni + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + self.cli_add_command(view_cmd) + self.cli_add_command(cmd) + self.cli_add_command("quit") + elif self.state == "absent" and exist: + self.cli_add_command(view_cmd) + self.cli_add_command(cmd, undo=True) + self.cli_add_command("quit") + + def config_vbdif(self): + """configure command at the VBDIF interface view""" + + if not self.vbdif_name: + return + + vbdif_cmd = "interface %s" % self.vbdif_name.lower().capitalize() + exist = is_config_exist(self.config, vbdif_cmd) + + if not exist: + self.module.fail_json( + msg="Error: Interface %s is not exist." % self.vbdif_name) + + # interface vbdif bd-id + # [undo] ip binding vpn-instance vpn-instance-name + vbdif_view = False + if self.vbdif_bind_vpn: + cmd = "ip binding vpn-instance %s" % self.vbdif_bind_vpn + exist = is_config_exist(self.config, cmd) + + if self.state == "present" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] arp distribute-gateway enable + if self.arp_distribute_gateway: + cmd = "arp distribute-gateway enable" + exist = is_config_exist(self.config, cmd) + if self.arp_distribute_gateway == "enable" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.arp_distribute_gateway == "disable" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # [undo] arp direct-route enable + if self.arp_direct_route: + cmd = "arp direct-route enable" + exist = is_config_exist(self.config, cmd) + if self.arp_direct_route == "enable" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.arp_direct_route == "disable" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd, undo=True) + + # mac-address mac-address + # undo mac-address + if self.vbdif_mac: + cmd = "mac-address %s" % self.vbdif_mac + exist = is_config_exist(self.config, cmd) + if self.state == "present" and not exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command(cmd) + elif self.state == "absent" and exist: + if not vbdif_view: + self.cli_add_command(vbdif_cmd) + vbdif_view = True + self.cli_add_command("undo mac-address") + + # quit + if vbdif_view: + self.cli_add_command("quit") + + def is_valid_vbdif(self, ifname): + """check is interface vbdif""" + + if not ifname.upper().startswith('VBDIF'): + return False + bdid = self.vbdif_name.replace(" ", "").upper().replace("VBDIF", "") + if not bdid.isdigit(): + return False + if int(bdid) < 1 or int(bdid) > 16777215: + return False + + return True + + def is_valid_ip_vpn(self, vpname): + """check ip vpn""" + + if not vpname: + return False + + if vpname == "_public_": + self.module.fail_json( + msg="Error: The value C(_public_) is reserved and cannot be used as the VPN instance name.") + + if len(vpname) < 1 or len(vpname) > 31: + self.module.fail_json( + msg="Error: IP vpn name length is not in the range from 1 to 31.") + + return True + + def check_params(self): + """Check all input params""" + + # dfs id check + if self.dfs_id: + if not self.dfs_id.isdigit(): + self.module.fail_json(msg="Error: DFS id is not digit.") + if int(self.dfs_id) != 1: + self.module.fail_json(msg="Error: DFS is not 1.") + + # dfs_source_ip check + if self.dfs_source_ip: + if not is_valid_v4addr(self.dfs_source_ip): + self.module.fail_json(msg="Error: dfs_source_ip is invalid.") + # dfs_source_vpn check + if self.dfs_source_vpn and not self.is_valid_ip_vpn(self.dfs_source_vpn): + self.module.fail_json(msg="Error: dfs_source_vpn is invalid.") + + # dfs_source_vpn and dfs_source_ip must set at the same time + if self.dfs_source_vpn and not self.dfs_source_ip: + self.module.fail_json( + msg="Error: dfs_source_vpn and dfs_source_ip must set at the same time.") + + # dfs_udp_port check + if self.dfs_udp_port: + if not self.dfs_udp_port.isdigit(): + self.module.fail_json( + msg="Error: dfs_udp_port id is not digit.") + if int(self.dfs_udp_port) < 1025 or int(self.dfs_udp_port) > 65535: + self.module.fail_json( + msg="dfs_udp_port is not ranges from 1025 to 65535.") + + # dfs_peer_ip check + if self.dfs_peer_ip: + if not is_valid_v4addr(self.dfs_peer_ip): + self.module.fail_json(msg="Error: dfs_peer_ip is invalid.") + # dfs_peer_vpn check + if self.dfs_peer_vpn and not self.is_valid_ip_vpn(self.dfs_peer_vpn): + self.module.fail_json(msg="Error: dfs_peer_vpn is invalid.") + + # dfs_peer_vpn and dfs_peer_ip must set at the same time + if self.dfs_peer_vpn and not self.dfs_peer_ip: + self.module.fail_json( + msg="Error: dfs_peer_vpn and dfs_peer_ip must set at the same time.") + + # vpn_instance check + if self.vpn_instance and not self.is_valid_ip_vpn(self.vpn_instance): + self.module.fail_json(msg="Error: vpn_instance is invalid.") + + # vpn_vni check + if self.vpn_vni: + if not self.vpn_vni.isdigit(): + self.module.fail_json(msg="Error: vpn_vni id is not digit.") + if int(self.vpn_vni) < 1 or int(self.vpn_vni) > 16000000: + self.module.fail_json( + msg="vpn_vni is not ranges from 1 to 16000000.") + + # vpn_instance and vpn_vni must set at the same time + if bool(self.vpn_instance) != bool(self.vpn_vni): + self.module.fail_json( + msg="Error: vpn_instance and vpn_vni must set at the same time.") + + # vbdif_name check + if self.vbdif_name: + self.vbdif_name = self.vbdif_name.replace(" ", "").lower().capitalize() + if not self.is_valid_vbdif(self.vbdif_name): + self.module.fail_json(msg="Error: vbdif_name is invalid.") + + # vbdif_mac check + if self.vbdif_mac: + mac = mac_format(self.vbdif_mac) + if not mac: + self.module.fail_json(msg="Error: vbdif_mac is invalid.") + self.vbdif_mac = mac + + # vbdif_bind_vpn check + if self.vbdif_bind_vpn and not self.is_valid_ip_vpn(self.vbdif_bind_vpn): + self.module.fail_json(msg="Error: vbdif_bind_vpn is invalid.") + + # All-Active Gateways or Distributed Gateway config can not set at the + # same time. + if self.dfs_id: + if self.vpn_vni or self.arp_distribute_gateway == "enable": + self.module.fail_json(msg="Error: All-Active Gateways or Distributed Gateway config " + "can not set at the same time.") + + def get_proposed(self): + """get proposed info""" + + if self.dfs_id: + self.proposed["dfs_id"] = self.dfs_id + self.proposed["dfs_source_ip"] = self.dfs_source_ip + self.proposed["dfs_source_vpn"] = self.dfs_source_vpn + self.proposed["dfs_udp_port"] = self.dfs_udp_port + self.proposed["dfs_all_active"] = self.dfs_all_active + self.proposed["dfs_peer_ip"] = self.dfs_peer_ip + self.proposed["dfs_peer_vpn"] = self.dfs_peer_vpn + + if self.vpn_instance: + self.proposed["vpn_instance"] = self.vpn_instance + self.proposed["vpn_vni"] = self.vpn_vni + + if self.vbdif_name: + self.proposed["vbdif_name"] = self.vbdif_name + self.proposed["vbdif_mac"] = self.vbdif_mac + self.proposed["vbdif_bind_vpn"] = self.vbdif_bind_vpn + self.proposed[ + "arp_distribute_gateway"] = self.arp_distribute_gateway + self.proposed["arp_direct_route"] = self.arp_direct_route + + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if not self.config: + return + + if is_config_exist(self.config, "dfs-group 1"): + self.existing["dfs_id"] = "1" + self.existing["dfs_source_ip"] = get_dfs_source_ip(self.config) + self.existing["dfs_source_vpn"] = get_dfs_source_vpn(self.config) + self.existing["dfs_udp_port"] = get_dfs_udp_port(self.config) + if is_config_exist(self.config, "active-active-gateway"): + self.existing["dfs_all_active"] = "enable" + self.existing["dfs_peers"] = get_dfs_peers(self.config) + else: + self.existing["dfs_all_active"] = "disable" + + if self.vpn_instance: + self.existing["vpn_instance"] = get_ip_vpn(self.config) + self.existing["vpn_vni"] = get_ip_vpn_vni(self.config) + + if self.vbdif_name: + self.existing["vbdif_name"] = self.vbdif_name + self.existing["vbdif_mac"] = get_vbdif_mac(self.config) + self.existing["vbdif_bind_vpn"] = get_vbdif_vpn(self.config) + if is_config_exist(self.config, "arp distribute-gateway enable"): + self.existing["arp_distribute_gateway"] = "enable" + else: + self.existing["arp_distribute_gateway"] = "disable" + if is_config_exist(self.config, "arp direct-route enable"): + self.existing["arp_direct_route"] = "enable" + else: + self.existing["arp_direct_route"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + if not config: + return + + if is_config_exist(config, "dfs-group 1"): + self.end_state["dfs_id"] = "1" + self.end_state["dfs_source_ip"] = get_dfs_source_ip(config) + self.end_state["dfs_source_vpn"] = get_dfs_source_vpn(config) + self.end_state["dfs_udp_port"] = get_dfs_udp_port(config) + if is_config_exist(config, "active-active-gateway"): + self.end_state["dfs_all_active"] = "enable" + self.end_state["dfs_peers"] = get_dfs_peers(config) + else: + self.end_state["dfs_all_active"] = "disable" + + if self.vpn_instance: + self.end_state["vpn_instance"] = get_ip_vpn(config) + self.end_state["vpn_vni"] = get_ip_vpn_vni(config) + + if self.vbdif_name: + self.end_state["vbdif_name"] = self.vbdif_name + self.end_state["vbdif_mac"] = get_vbdif_mac(config) + self.end_state["vbdif_bind_vpn"] = get_vbdif_vpn(config) + if is_config_exist(config, "arp distribute-gateway enable"): + self.end_state["arp_distribute_gateway"] = "enable" + else: + self.end_state["arp_distribute_gateway"] = "disable" + if is_config_exist(config, "arp direct-route enable"): + self.end_state["arp_direct_route"] = "enable" + else: + self.end_state["arp_direct_route"] = "disable" + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.get_existing() + self.get_proposed() + + # deal present or absent + if self.dfs_id: + self.config_dfs_group() + + if self.vpn_instance: + self.config_ip_vpn() + + if self.vbdif_name: + self.config_vbdif() + + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + dfs_id=dict(required=False, type='str'), + dfs_source_ip=dict(required=False, type='str'), + dfs_source_vpn=dict(required=False, type='str'), + dfs_udp_port=dict(required=False, type='str'), + dfs_all_active=dict(required=False, type='str', + choices=['enable', 'disable']), + dfs_peer_ip=dict(required=False, type='str'), + dfs_peer_vpn=dict(required=False, type='str'), + vpn_instance=dict(required=False, type='str'), + vpn_vni=dict(required=False, type='str'), + vbdif_name=dict(required=False, type='str'), + vbdif_mac=dict(required=False, type='str'), + vbdif_bind_vpn=dict(required=False, type='str'), + arp_distribute_gateway=dict( + required=False, type='str', choices=['enable', 'disable']), + arp_direct_route=dict(required=False, type='str', + choices=['enable', 'disable']), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanGateway(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_global.py new file mode 100644 index 00000000..e474c923 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_global.py @@ -0,0 +1,539 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_global +short_description: Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. +description: + - Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices. +author: QijunPan (@QijunPan) +notes: + - Recommended connection is C(network_cli). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. + The value is an integer ranging from 1 to 16777215. + tunnel_mode_vxlan: + description: + - Set the tunnel mode to VXLAN when configuring the VXLAN feature. + choices: ['enable', 'disable'] + nvo3_prevent_loops: + description: + - Loop prevention of VXLAN traffic in non-enhanced mode. + When the device works in non-enhanced mode, + inter-card forwarding of VXLAN traffic may result in loops. + choices: ['enable', 'disable'] + nvo3_acl_extend: + description: + - Enabling or disabling the VXLAN ACL extension function. + choices: ['enable', 'disable'] + nvo3_gw_enhanced: + description: + - Configuring the Layer 3 VXLAN Gateway to Work in Non-loopback Mode. + choices: ['l2', 'l3'] + nvo3_service_extend: + description: + - Enabling or disabling the VXLAN service extension function. + choices: ['enable', 'disable'] + nvo3_eth_trunk_hash: + description: + - Eth-Trunk from load balancing VXLAN packets in optimized mode. + choices: ['enable','disable'] + nvo3_ecmp_hash: + description: + - Load balancing of VXLAN packets through ECMP in optimized mode. + choices: ['enable', 'disable'] + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan global module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create bridge domain and set tunnel mode to VXLAN + community.network.ce_vxlan_global: + bridge_domain_id: 100 + nvo3_acl_extend: enable + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "nvo3_acl_extend": "enable", state="present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"bridge_domain": {"80", "90"}, "nvo3_acl_extend": "disable"} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"bridge_domain_id": {"80", "90", "100"}, "nvo3_acl_extend": "enable"} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["bridge-domain 100", + "ip tunnel mode vxlan"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import load_config +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec +from ansible.module_utils.connection import exec_command + + +def is_config_exist(cmp_cfg, test_cfg): + """is configuration exist?""" + + if not cmp_cfg or not test_cfg: + return False + + return bool(test_cfg in cmp_cfg) + + +def get_nvo3_gw_enhanced(cmp_cfg): + """get the Layer 3 VXLAN Gateway to Work in Non-loopback Mode """ + + get = re.findall( + r"assign forward nvo3-gateway enhanced (l[2|3])", cmp_cfg) + if not get: + return None + else: + return get[0] + + +class VxlanGlobal(object): + """ + Manages global attributes of VXLAN and bridge domain. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.tunnel_mode_vxlan = self.module.params['tunnel_mode_vxlan'] + self.nvo3_prevent_loops = self.module.params['nvo3_prevent_loops'] + self.nvo3_acl_extend = self.module.params['nvo3_acl_extend'] + self.nvo3_gw_enhanced = self.module.params['nvo3_gw_enhanced'] + self.nvo3_service_extend = self.module.params['nvo3_service_extend'] + self.nvo3_eth_trunk_hash = self.module.params['nvo3_eth_trunk_hash'] + self.nvo3_ecmp_hash = self.module.params['nvo3_ecmp_hash'] + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.state = self.module.params['state'] + + # state + self.config = "" # current config + self.bd_info = list() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def init_module(self): + """init module""" + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def cli_load_config(self, commands): + """load config by cli""" + + if not self.module.check_mode: + load_config(self.module, commands) + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache + """ + flags = [] if flags is None else flags + + cmd = 'display current-configuration ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + rc, out, err = exec_command(self.module, cmd) + if rc != 0: + self.module.fail_json(msg=err) + cfg = str(out).strip() + + return cfg + + def get_current_config(self): + """get current configuration""" + + flags = list() + exp = " include-default | include vxlan|assign | exclude undo" + flags.append(exp) + return self.get_config(flags) + + def cli_add_command(self, command, undo=False): + """add command to self.update_cmd and self.commands""" + + if undo and command.lower() not in ["quit", "return"]: + cmd = "undo " + command + else: + cmd = command + + self.commands.append(cmd) # set to device + if command.lower() not in ["quit", "return"]: + self.updates_cmd.append(cmd) # show updates result + + def get_bd_list(self): + """get bridge domain list""" + flags = list() + bd_info = list() + exp = " include-default | include bridge-domain | exclude undo" + flags.append(exp) + bd_str = self.get_config(flags) + if not bd_str: + return bd_info + bd_num = re.findall(r'bridge-domain\s*([0-9]+)', bd_str) + bd_info.extend(bd_num) + return bd_info + + def config_bridge_domain(self): + """manage bridge domain""" + + if not self.bridge_domain_id: + return + + cmd = "bridge-domain %s" % self.bridge_domain_id + exist = self.bridge_domain_id in self.bd_info + if self.state == "present": + if not exist: + self.cli_add_command(cmd) + self.cli_add_command("quit") + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def config_tunnel_mode(self): + """config tunnel mode vxlan""" + + # ip tunnel mode vxlan + if self.tunnel_mode_vxlan: + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(self.config, cmd) + if self.tunnel_mode_vxlan == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def config_assign_forward(self): + """config assign forward command""" + + # [undo] assign forward nvo3-gateway enhanced {l2|l3) + if self.nvo3_gw_enhanced: + cmd = "assign forward nvo3-gateway enhanced %s" % self.nvo3_gw_enhanced + exist = is_config_exist(self.config, cmd) + if self.state == "present": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 f-linecard compatibility enable + if self.nvo3_prevent_loops: + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_prevent_loops == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 acl extend enable + if self.nvo3_acl_extend: + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_acl_extend == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 service extend enable + if self.nvo3_service_extend: + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_service_extend == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # assign forward nvo3 eth-trunk hash {enable|disable} + if self.nvo3_eth_trunk_hash: + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_eth_trunk_hash == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + # [undo] assign forward nvo3 ecmp hash enable + if self.nvo3_ecmp_hash: + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(self.config, cmd) + if self.nvo3_ecmp_hash == "enable": + if not exist: + self.cli_add_command(cmd) + else: + if exist: + self.cli_add_command(cmd, undo=True) + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: bridge domain id is not in the range from 1 to 16777215.") + + def get_proposed(self): + """get proposed info""" + + if self.tunnel_mode_vxlan: + self.proposed["tunnel_mode_vxlan"] = self.tunnel_mode_vxlan + if self.nvo3_prevent_loops: + self.proposed["nvo3_prevent_loops"] = self.nvo3_prevent_loops + if self.nvo3_acl_extend: + self.proposed["nvo3_acl_extend"] = self.nvo3_acl_extend + if self.nvo3_gw_enhanced: + self.proposed["nvo3_gw_enhanced"] = self.nvo3_gw_enhanced + if self.nvo3_service_extend: + self.proposed["nvo3_service_extend"] = self.nvo3_service_extend + if self.nvo3_eth_trunk_hash: + self.proposed["nvo3_eth_trunk_hash"] = self.nvo3_eth_trunk_hash + if self.nvo3_ecmp_hash: + self.proposed["nvo3_ecmp_hash"] = self.nvo3_ecmp_hash + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + self.existing["bridge_domain"] = self.bd_info + + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["tunnel_mode_vxlan"] = "enable" + else: + self.existing["tunnel_mode_vxlan"] = "disable" + + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_prevent_loops"] = "enable" + else: + self.existing["nvo3_prevent_loops"] = "disable" + + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_acl_extend"] = "enable" + else: + self.existing["nvo3_acl_extend"] = "disable" + + self.existing["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced( + self.config) + + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_service_extend"] = "enable" + else: + self.existing["nvo3_service_extend"] = "disable" + + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_eth_trunk_hash"] = "enable" + else: + self.existing["nvo3_eth_trunk_hash"] = "disable" + + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(self.config, cmd) + if exist: + self.existing["nvo3_ecmp_hash"] = "enable" + else: + self.existing["nvo3_ecmp_hash"] = "disable" + + def get_end_state(self): + """get end state info""" + + config = self.get_current_config() + + self.end_state["bridge_domain"] = self.get_bd_list() + + cmd = "ip tunnel mode vxlan" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["tunnel_mode_vxlan"] = "enable" + else: + self.end_state["tunnel_mode_vxlan"] = "disable" + + cmd = "assign forward nvo3 f-linecard compatibility enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_prevent_loops"] = "enable" + else: + self.end_state["nvo3_prevent_loops"] = "disable" + + cmd = "assign forward nvo3 acl extend enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_acl_extend"] = "enable" + else: + self.end_state["nvo3_acl_extend"] = "disable" + + self.end_state["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced(config) + + cmd = "assign forward nvo3 service extend enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_service_extend"] = "enable" + else: + self.end_state["nvo3_service_extend"] = "disable" + + cmd = "assign forward nvo3 eth-trunk hash enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_eth_trunk_hash"] = "enable" + else: + self.end_state["nvo3_eth_trunk_hash"] = "disable" + + cmd = "assign forward nvo3 ecmp hash enable" + exist = is_config_exist(config, cmd) + if exist: + self.end_state["nvo3_ecmp_hash"] = "enable" + else: + self.end_state["nvo3_ecmp_hash"] = "disable" + if self.existing == self.end_state: + self.changed = True + + def work(self): + """worker""" + + self.check_params() + self.config = self.get_current_config() + self.bd_info = self.get_bd_list() + self.get_existing() + self.get_proposed() + + # deal present or absent + self.config_bridge_domain() + self.config_tunnel_mode() + self.config_assign_forward() + if self.commands: + self.cli_load_config(self.commands) + self.changed = True + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + tunnel_mode_vxlan=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_prevent_loops=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_acl_extend=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_gw_enhanced=dict(required=False, type='str', + choices=['l2', 'l3']), + nvo3_service_extend=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_eth_trunk_hash=dict(required=False, type='str', + choices=['enable', 'disable']), + nvo3_ecmp_hash=dict(required=False, type='str', + choices=['enable', 'disable']), + bridge_domain_id=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanGlobal(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_tunnel.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_tunnel.py new file mode 100644 index 00000000..d644b51d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_tunnel.py @@ -0,0 +1,940 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_tunnel +short_description: Manages VXLAN tunnel configuration on HUAWEI CloudEngine devices. +description: + - This module offers the ability to set the VNI and mapped to the BD, + and configure an ingress replication list on HUAWEI CloudEngine devices. +author: + - Li Yanfeng (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. The value is an integer ranging from 1 to 16777215. + vni_id: + description: + - Specifies a VXLAN network identifier (VNI) ID. The value is an integer ranging from 1 to 16000000. + nve_name: + description: + - Specifies the number of an NVE interface. The value ranges from 1 to 2. + nve_mode: + description: + - Specifies the working mode of an NVE interface. + choices: ['mode-l2','mode-l3'] + peer_list_ip: + description: + - Specifies the IP address of a remote VXLAN tunnel endpoints (VTEP). + The value is in dotted decimal notation. + protocol_type: + description: + - The operation type of routing protocol. + choices: ['bgp','null'] + source_ip: + description: + - Specifies an IP address for a source VTEP. The value is in dotted decimal notation. + state: + description: + - Manage the state of the resource. + default: present + choices: ['present','absent'] +''' +EXAMPLES = ''' +- name: Vxlan tunnel module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Make sure nve_name is exist, ensure vni_id and protocol_type is configured on Nve1 interface. + community.network.ce_vxlan_tunnel: + nve_name: Nve1 + vni_id: 100 + protocol_type: bgp + state: present + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l2", "source_ip": "0.0.0.0"} +existing: + description: + - k/v pairs of existing rollback + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l3", "source_ip": "0.0.0.0"} + +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface Nve1", + "mode l3"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {nve_interface_name": "Nve1", nve_mode": "mode-l3", "source_ip": "0.0.0.0"} +''' +import re +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_VNI_BD_INFO = """ + + + + + + + + + + +""" + +CE_NC_GET_NVE_INFO = """ + + + + + %s + + + + +""" + +CE_NC_MERGE_VNI_BD_ID = """ + + + + + %s + %s + + + + +""" + +CE_NC_DELETE_VNI_BD_ID = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_NVE_MODE = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL = """ + + + + + %s + %s + + + + +""" + +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" + +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_MERGE_VNI_PEER_ADDRESS_IP_MERGE = """ + + + %s + + +""" + +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE = """ + + + %s + + +""" + +CE_NC_DELETE_PEER_ADDRESS_IP_HEAD = """ + + + + + %s + + + %s +""" +CE_NC_DELETE_PEER_ADDRESS_IP_END = """ + + + + + + +""" +CE_NC_MERGE_VNI_PROTOCOL = """ + + + + + %s + + + %s + %s + + + + + + +""" + +CE_NC_DELETE_VNI_PROTOCOL = """ + + + + + %s + + + %s + %s + + + + + + +""" + + +def is_valid_address(address): + """check ip-address is valid""" + + if address.find('.') != -1: + addr_list = address.split('.') + if len(addr_list) != 4: + return False + for each_num in addr_list: + if not each_num.isdigit(): + return False + if int(each_num) > 255: + return False + return True + + return False + + +class VxlanTunnel(object): + """ + Manages vxlan tunnel configuration. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.init_module() + + # module input info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.vni_id = self.module.params['vni_id'] + self.nve_name = self.module.params['nve_name'] + self.nve_mode = self.module.params['nve_mode'] + self.peer_list_ip = self.module.params['peer_list_ip'] + self.protocol_type = self.module.params['protocol_type'] + self.source_ip = self.module.params['source_ip'] + self.state = self.module.params['state'] + + # state + self.changed = False + self.updates_cmd = list() + self.results = dict() + self.existing = dict() + self.proposed = dict() + self.end_state = dict() + + # configuration nve info + self.vni2bd_info = None + self.nve_info = None + + def init_module(self): + """ init module """ + + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_vni2bd_dict(self): + """ get vni2bd attributes dict.""" + + vni2bd_info = dict() + # get vni bd info + conf_str = CE_NC_GET_VNI_BD_INFO + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return vni2bd_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + # get vni to bridge domain id info + root = ElementTree.fromstring(xml_str) + vni2bd_info["vni2BdInfos"] = list() + vni2bds = root.findall("nvo3/nvo3Vni2Bds/nvo3Vni2Bd") + + if vni2bds: + for vni2bd in vni2bds: + vni_dict = dict() + for ele in vni2bd: + if ele.tag in ["vniId", "bdId"]: + vni_dict[ele.tag] = ele.text + vni2bd_info["vni2BdInfos"].append(vni_dict) + + return vni2bd_info + + def check_nve_interface(self, nve_name): + """is nve interface exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name: + return True + return False + + def get_nve_dict(self, nve_name): + """ get nve interface attributes dict.""" + + nve_info = dict() + # get nve info + conf_str = CE_NC_GET_NVE_INFO % nve_name + xml_str = get_nc_config(self.module, conf_str) + if "" in xml_str: + return nve_info + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get nve info + root = ElementTree.fromstring(xml_str) + + nvo3 = root.find("nvo3/nvo3Nves/nvo3Nve") + if nvo3: + for nve in nvo3: + if nve.tag in ["srcAddr", "ifName", "nveType"]: + nve_info[nve.tag] = nve.text + + # get nve vni info + nve_info["vni_peer_protocols"] = list() + + vni_members = root.findall( + "nvo3/nvo3Nves/nvo3Nve/vniMembers/vniMember") + if vni_members: + for member in vni_members: + vni_dict = dict() + for ele in member: + if ele.tag in ["vniId", "protocol"]: + vni_dict[ele.tag] = ele.text + nve_info["vni_peer_protocols"].append(vni_dict) + + # get vni peer address ip info + nve_info["vni_peer_ips"] = list() + + re_find = re.findall(r'(.*?)\s*' + r'(.*?)\s*' + r'(.*?)', xml_str) + + if re_find: + for vni_peers in re_find: + vni_info = dict() + vni_peer = re.findall(r'(.*?)', vni_peers[2]) + if vni_peer: + vni_info["vniId"] = vni_peers[0] + vni_peer_list = list() + for peer in vni_peer: + vni_peer_list.append(peer) + vni_info["peerAddr"] = vni_peer_list + nve_info["vni_peer_ips"].append(vni_info) + + return nve_info + + def check_nve_name(self): + """Gets Nve interface name""" + + if self.nve_name is None: + return False + if self.nve_name in ["Nve1", "Nve2"]: + return True + return False + + def is_vni_bd_exist(self, vni_id, bd_id): + """is vni to bridge-domain-id exist""" + + if not self.vni2bd_info: + return False + + for vni2bd in self.vni2bd_info["vni2BdInfos"]: + if vni2bd["vniId"] == vni_id and vni2bd["bdId"] == bd_id: + return True + return False + + def is_vni_bd_change(self, vni_id, bd_id): + """is vni to bridge-domain-id change""" + + if not self.vni2bd_info: + return True + + for vni2bd in self.vni2bd_info["vni2BdInfos"]: + if vni2bd["vniId"] == vni_id and vni2bd["bdId"] == bd_id: + return False + return True + + def is_nve_mode_exist(self, nve_name, mode): + """is nve interface mode exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name and self.nve_info["nveType"] == mode: + return True + return False + + def is_nve_mode_change(self, nve_name, mode): + """is nve interface mode change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name and self.nve_info["nveType"] == mode: + return False + return True + + def is_nve_source_ip_exist(self, nve_name, source_ip): + """is vni to bridge-domain-id exist""" + + if not self.nve_info: + return False + + if self.nve_info["ifName"] == nve_name and self.nve_info["srcAddr"] == source_ip: + return True + return False + + def is_nve_source_ip_change(self, nve_name, source_ip): + """is vni to bridge-domain-id change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name and self.nve_info["srcAddr"] == source_ip: + return False + return True + + def is_vni_protocol_exist(self, nve_name, vni_id, protocol_type): + """is vni protocol exist""" + + if not self.nve_info: + return False + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_protocols"]: + if member["vniId"] == vni_id and member["protocol"] == protocol_type: + return True + return False + + def is_vni_protocol_change(self, nve_name, vni_id, protocol_type): + """is vni protocol change""" + + if not self.nve_info: + return True + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_protocols"]: + if member["vniId"] == vni_id and member["protocol"] == protocol_type: + return False + return True + + def is_vni_peer_list_exist(self, nve_name, vni_id, peer_ip): + """is vni peer list exist""" + + if not self.nve_info: + return False + if self.nve_info["ifName"] == nve_name: + for member in self.nve_info["vni_peer_ips"]: + if member["vniId"] == vni_id and peer_ip in member["peerAddr"]: + return True + return False + + def is_vni_peer_list_change(self, nve_name, vni_id, peer_ip_list): + """is vni peer list change""" + + if not self.nve_info: + return True + + if self.nve_info["ifName"] == nve_name: + if not self.nve_info["vni_peer_ips"]: + return True + + nve_peer_info = list() + for nve_peer in self.nve_info["vni_peer_ips"]: + if nve_peer["vniId"] == vni_id: + nve_peer_info.append(nve_peer) + + if not nve_peer_info: + return True + + nve_peer_list = nve_peer_info[0]["peerAddr"] + for peer in peer_ip_list: + if peer not in nve_peer_list: + return True + + return False + + def config_merge_vni2bd(self, bd_id, vni_id): + """config vni to bd id""" + + if self.is_vni_bd_change(vni_id, bd_id): + cfg_xml = CE_NC_MERGE_VNI_BD_ID % (vni_id, bd_id) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_BD") + self.updates_cmd.append("bridge-domain %s" % bd_id) + self.updates_cmd.append("vxlan vni %s" % vni_id) + self.changed = True + + def config_merge_mode(self, nve_name, mode): + """config nve mode""" + + if self.is_nve_mode_change(nve_name, mode): + cfg_xml = CE_NC_MERGE_NVE_MODE % (nve_name, mode) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_MODE") + self.updates_cmd.append("interface %s" % nve_name) + if mode == "mode-l3": + self.updates_cmd.append("mode l3") + else: + self.updates_cmd.append("undo mode l3") + self.changed = True + + def config_merge_source_ip(self, nve_name, source_ip): + """config nve source ip""" + + if self.is_nve_source_ip_change(nve_name, source_ip): + cfg_xml = CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL % ( + nve_name, source_ip) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_SOURCE_IP") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("source %s" % source_ip) + self.changed = True + + def config_merge_vni_peer_ip(self, nve_name, vni_id, peer_ip_list): + """config vni peer ip""" + + if self.is_vni_peer_list_change(nve_name, vni_id, peer_ip_list): + cfg_xml = CE_NC_MERGE_VNI_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_MERGE_VNI_PEER_ADDRESS_IP_MERGE % peer_ip + cfg_xml += CE_NC_MERGE_VNI_PEER_ADDRESS_IP_END + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_PEER_IP") + self.updates_cmd.append("interface %s" % nve_name) + + for peer_ip in peer_ip_list: + cmd_output = "vni %s head-end peer-list %s" % (vni_id, peer_ip) + self.updates_cmd.append(cmd_output) + self.changed = True + + def config_merge_vni_protocol_type(self, nve_name, vni_id, protocol_type): + """config vni protocol type""" + + if self.is_vni_protocol_change(nve_name, vni_id, protocol_type): + cfg_xml = CE_NC_MERGE_VNI_PROTOCOL % ( + nve_name, vni_id, protocol_type) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "MERGE_VNI_PEER_PROTOCOL") + self.updates_cmd.append("interface %s" % nve_name) + + if protocol_type == "bgp": + self.updates_cmd.append( + "vni %s head-end peer-list protocol %s" % (vni_id, protocol_type)) + else: + self.updates_cmd.append( + "undo vni %s head-end peer-list protocol bgp" % vni_id) + self.changed = True + + def config_delete_vni2bd(self, bd_id, vni_id): + """remove vni to bd id""" + + if not self.is_vni_bd_exist(vni_id, bd_id): + return + cfg_xml = CE_NC_DELETE_VNI_BD_ID % (vni_id, bd_id) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_BD") + self.updates_cmd.append( + "bridge-domain %s" % bd_id) + self.updates_cmd.append( + "undo vxlan vni %s" % vni_id) + + self.changed = True + + def config_delete_mode(self, nve_name, mode): + """nve mode""" + + if mode == "mode-l3": + if not self.is_nve_mode_exist(nve_name, mode): + return + cfg_xml = CE_NC_MERGE_NVE_MODE % (nve_name, "mode-l2") + + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_MODE") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("undo mode l3") + self.changed = True + else: + self.module.fail_json( + msg='Error: Can not configure undo mode l2.') + + def config_delete_source_ip(self, nve_name, source_ip): + """nve source ip""" + + if not self.is_nve_source_ip_exist(nve_name, source_ip): + return + ipaddr = "0.0.0.0" + cfg_xml = CE_NC_MERGE_NVE_SOURCE_IP_PROTOCOL % ( + nve_name, ipaddr) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_SOURCE_IP") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append("undo source %s" % source_ip) + self.changed = True + + def config_delete_vni_peer_ip(self, nve_name, vni_id, peer_ip_list): + """remove vni peer ip""" + + for peer_ip in peer_ip_list: + if not self.is_vni_peer_list_exist(nve_name, vni_id, peer_ip): + self.module.fail_json(msg='Error: The %s does not exist' % peer_ip) + + config = False + + nve_peer_info = list() + for nve_peer in self.nve_info["vni_peer_ips"]: + if nve_peer["vniId"] == vni_id: + nve_peer_info = nve_peer.get("peerAddr") + for peer in nve_peer_info: + if peer not in peer_ip_list: + config = True + + if not config: + cfg_xml = CE_NC_DELETE_VNI_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE % peer_ip + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_END + else: + cfg_xml = CE_NC_DELETE_PEER_ADDRESS_IP_HEAD % ( + nve_name, vni_id) + for peer_ip in peer_ip_list: + cfg_xml += CE_NC_DELETE_VNI_PEER_ADDRESS_IP_DELETE % peer_ip + cfg_xml += CE_NC_DELETE_PEER_ADDRESS_IP_END + + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_PEER_IP") + self.updates_cmd.append("interface %s" % nve_name) + + for peer_ip in peer_ip_list: + cmd_output = "undo vni %s head-end peer-list %s" % (vni_id, peer_ip) + self.updates_cmd.append(cmd_output) + + self.changed = True + + def config_delete_vni_protocol_type(self, nve_name, vni_id, protocol_type): + """remove vni protocol type""" + + if not self.is_vni_protocol_exist(nve_name, vni_id, protocol_type): + return + + cfg_xml = CE_NC_DELETE_VNI_PROTOCOL % (nve_name, vni_id, protocol_type) + recv_xml = set_nc_config(self.module, cfg_xml) + self.check_response(recv_xml, "DELETE_VNI_PEER_PROTOCOL") + self.updates_cmd.append("interface %s" % nve_name) + self.updates_cmd.append( + "undo vni %s head-end peer-list protocol bgp " % vni_id) + self.changed = True + + def check_params(self): + """Check all input params""" + + # bridge_domain_id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of bridge domain id is invalid.') + if int(self.bridge_domain_id) > 16777215 or int(self.bridge_domain_id) < 1: + self.module.fail_json( + msg='Error: The bridge domain id must be an integer between 1 and 16777215.') + # vni_id check + if self.vni_id: + if not self.vni_id.isdigit(): + self.module.fail_json( + msg='Error: The parameter of vni id is invalid.') + if int(self.vni_id) > 16000000 or int(self.vni_id) < 1: + self.module.fail_json( + msg='Error: The vni id must be an integer between 1 and 16000000.') + + # nve_name check + if self.nve_name: + if not self.check_nve_name(): + self.module.fail_json( + msg='Error: Error: NVE interface %s is invalid.' % self.nve_name) + + # peer_list_ip check + if self.peer_list_ip: + for peer_ip in self.peer_list_ip: + if not is_valid_address(peer_ip): + self.module.fail_json( + msg='Error: The ip address %s is invalid.' % self.peer_list_ip) + # source_ip check + if self.source_ip: + if not is_valid_address(self.source_ip): + self.module.fail_json( + msg='Error: The ip address %s is invalid.' % self.source_ip) + + def get_proposed(self): + """get proposed info""" + + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + if self.vni_id: + self.proposed["vni_id"] = self.vni_id + if self.nve_name: + self.proposed["nve_name"] = self.nve_name + if self.nve_mode: + self.proposed["nve_mode"] = self.nve_mode + if self.peer_list_ip: + self.proposed["peer_list_ip"] = self.peer_list_ip + if self.source_ip: + self.proposed["source_ip"] = self.source_ip + if self.state: + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.vni2bd_info: + self.existing["vni_to_bridge_domain"] = self.vni2bd_info[ + "vni2BdInfos"] + + if self.nve_info: + self.existing["nve_interface_name"] = self.nve_info["ifName"] + self.existing["source_ip"] = self.nve_info["srcAddr"] + self.existing["nve_mode"] = self.nve_info["nveType"] + self.existing["vni_peer_list_ip"] = self.nve_info[ + "vni_peer_ips"] + self.existing["vni_peer_list_protocol"] = self.nve_info[ + "vni_peer_protocols"] + + def get_end_state(self): + """get end state info""" + + vni2bd_info = self.get_vni2bd_dict() + if vni2bd_info: + self.end_state["vni_to_bridge_domain"] = vni2bd_info["vni2BdInfos"] + + nve_info = self.get_nve_dict(self.nve_name) + if nve_info: + self.end_state["nve_interface_name"] = nve_info["ifName"] + self.end_state["source_ip"] = nve_info["srcAddr"] + self.end_state["nve_mode"] = nve_info["nveType"] + self.end_state["vni_peer_list_ip"] = nve_info[ + "vni_peer_ips"] + self.end_state["vni_peer_list_protocol"] = nve_info[ + "vni_peer_protocols"] + + def work(self): + """worker""" + + self.check_params() + self.vni2bd_info = self.get_vni2bd_dict() + if self.nve_name: + self.nve_info = self.get_nve_dict(self.nve_name) + self.get_existing() + self.get_proposed() + # deal present or absent + if self.state == "present": + if self.bridge_domain_id and self.vni_id: + self.config_merge_vni2bd(self.bridge_domain_id, self.vni_id) + if self.nve_name: + if self.check_nve_interface(self.nve_name): + if self.nve_mode: + self.config_merge_mode(self.nve_name, self.nve_mode) + if self.source_ip: + self.config_merge_source_ip( + self.nve_name, self.source_ip) + if self.vni_id and self.peer_list_ip: + self.config_merge_vni_peer_ip( + self.nve_name, self.vni_id, self.peer_list_ip) + if self.vni_id and self.protocol_type: + self.config_merge_vni_protocol_type( + self.nve_name, self.vni_id, self.protocol_type) + else: + self.module.fail_json( + msg='Error: Nve interface %s does not exist.' % self.nve_name) + + else: + if self.bridge_domain_id and self.vni_id: + self.config_delete_vni2bd(self.bridge_domain_id, self.vni_id) + if self.nve_name: + if self.check_nve_interface(self.nve_name): + if self.nve_mode: + self.config_delete_mode(self.nve_name, self.nve_mode) + if self.source_ip: + self.config_delete_source_ip( + self.nve_name, self.source_ip) + if self.vni_id and self.peer_list_ip: + self.config_delete_vni_peer_ip( + self.nve_name, self.vni_id, self.peer_list_ip) + if self.vni_id and self.protocol_type: + self.config_delete_vni_protocol_type( + self.nve_name, self.vni_id, self.protocol_type) + else: + self.module.fail_json( + msg='Error: Nve interface %s does not exist.' % self.nve_name) + + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bridge_domain_id=dict(required=False), + vni_id=dict(required=False, type='str'), + nve_name=dict(required=False, type='str'), + nve_mode=dict(required=False, choices=['mode-l2', 'mode-l3']), + peer_list_ip=dict(required=False, type='list'), + protocol_type=dict(required=False, type='str', choices=[ + 'bgp', 'null']), + + source_ip=dict(required=False), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanTunnel(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_vap.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_vap.py new file mode 100644 index 00000000..fb1b096c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudengine/ce_vxlan_vap.py @@ -0,0 +1,933 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ce_vxlan_vap +short_description: Manages VXLAN virtual access point on HUAWEI CloudEngine Devices. +description: + - Manages VXLAN Virtual access point on HUAWEI CloudEngine Devices. +author: QijunPan (@QijunPan) +notes: + - This module requires the netconf system service be enabled on the remote device being managed. + - Recommended connection is C(netconf). + - This module also works with C(local) connections for legacy playbooks. +options: + bridge_domain_id: + description: + - Specifies a bridge domain ID. + The value is an integer ranging from 1 to 16777215. + bind_vlan_id: + description: + - Specifies the VLAN binding to a BD(Bridge Domain). + The value is an integer ranging ranging from 1 to 4094. + l2_sub_interface: + description: + - Specifies an Sub-Interface full name, i.e. "10GE1/0/41.1". + The value is a string of 1 to 63 case-insensitive characters, spaces supported. + encapsulation: + description: + - Specifies an encapsulation type of packets allowed to pass through a Layer 2 sub-interface. + choices: ['dot1q', 'default', 'untag', 'qinq', 'none'] + ce_vid: + description: + - When I(encapsulation) is 'dot1q', specifies a VLAN ID in the outer VLAN tag. + When I(encapsulation) is 'qinq', specifies an outer VLAN ID for + double-tagged packets to be received by a Layer 2 sub-interface. + The value is an integer ranging from 1 to 4094. + pe_vid: + description: + - When I(encapsulation) is 'qinq', specifies an inner VLAN ID for + double-tagged packets to be received by a Layer 2 sub-interface. + The value is an integer ranging from 1 to 4094. + state: + description: + - Determines whether the config should be present or not + on the device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = ''' +- name: Vxlan vap module test + hosts: ce128 + connection: local + gather_facts: no + vars: + cli: + host: "{{ inventory_hostname }}" + port: "{{ ansible_ssh_port }}" + username: "{{ username }}" + password: "{{ password }}" + transport: cli + + tasks: + + - name: Create a mapping between a VLAN and a BD + community.network.ce_vxlan_vap: + bridge_domain_id: 100 + bind_vlan_id: 99 + provider: "{{ cli }}" + + - name: Bind a Layer 2 sub-interface to a BD + community.network.ce_vxlan_vap: + bridge_domain_id: 100 + l2_sub_interface: 10GE2/0/20.1 + provider: "{{ cli }}" + + - name: Configure an encapsulation type on a Layer 2 sub-interface + community.network.ce_vxlan_vap: + l2_sub_interface: 10GE2/0/20.1 + encapsulation: dot1q + provider: "{{ cli }}" +''' + +RETURN = ''' +proposed: + description: k/v pairs of parameters passed into module + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_vlan_id": "99", state="present"} +existing: + description: k/v pairs of existing configuration + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_intf_list": ["10GE2/0/20.1", "10GE2/0/20.2"], + "bind_vlan_list": []} +end_state: + description: k/v pairs of configuration after module execution + returned: verbose mode + type: dict + sample: {"bridge_domain_id": "100", "bind_intf_list": ["110GE2/0/20.1", "10GE2/0/20.2"], + "bind_vlan_list": ["99"]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["bridge-domain 100", + "l2 binding vlan 99"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +''' + +from xml.etree import ElementTree +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec + +CE_NC_GET_BD_VAP = """ + + + + + %s + + + + + + + + + + + + +""" + +CE_NC_MERGE_BD_VLAN = """ + + + + + %s + + %s:%s + + + + + +""" + +CE_NC_MERGE_BD_INTF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_DELETE_BD_INTF = """ + + + + + %s + + + %s + + + + + + +""" + +CE_NC_GET_ENCAP = """ + + + + + %s + + + + + + + + + + + + + + +""" + +CE_NC_SET_ENCAP = """ + + + + + %s + %s + + + + +""" + +CE_NC_UNSET_ENCAP = """ + + + + + %s + none + + + + +""" + +CE_NC_SET_ENCAP_DOT1Q = """ + + + + + %s + dot1q + + %s:%s + + + + + +""" + +CE_NC_SET_ENCAP_QINQ = """ + + + + + %s + qinq + + + %s + %s:%s + + + + + + +""" + + +def vlan_vid_to_bitmap(vid): + """convert VLAN list to VLAN bitmap""" + + vlan_bit = ['0'] * 1024 + int_vid = int(vid) + j = int_vid // 4 + bit_int = 0x8 >> (int_vid % 4) + vlan_bit[j] = str(hex(bit_int))[2] + + return ''.join(vlan_bit) + + +def bitmap_to_vlan_list(bitmap): + """convert VLAN bitmap to VLAN list""" + + tmp = list() + if not bitmap: + return tmp + + bit_len = len(bitmap) + for i in range(bit_len): + if bitmap[i] == "0": + continue + bit = int(bitmap[i]) + if bit & 0x8: + tmp.append(str(i * 4)) + if bit & 0x4: + tmp.append(str(i * 4 + 1)) + if bit & 0x2: + tmp.append(str(i * 4 + 2)) + if bit & 0x1: + tmp.append(str(i * 4 + 3)) + + return tmp + + +def is_vlan_bitmap_empty(bitmap): + """check VLAN bitmap empty""" + + if not bitmap or len(bitmap) == 0: + return True + + for bit in bitmap: + if bit != '0': + return False + + return True + + +def is_vlan_in_bitmap(vid, bitmap): + """check is VLAN id in bitmap""" + + if is_vlan_bitmap_empty(bitmap): + return False + + i = int(vid) // 4 + if i > len(bitmap): + return False + + if int(bitmap[i]) & (0x8 >> (int(vid) % 4)): + return True + + return False + + +def get_interface_type(interface): + """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" + + if interface is None: + return None + + iftype = None + + if interface.upper().startswith('GE'): + iftype = 'ge' + elif interface.upper().startswith('10GE'): + iftype = '10ge' + elif interface.upper().startswith('25GE'): + iftype = '25ge' + elif interface.upper().startswith('4X10GE'): + iftype = '4x10ge' + elif interface.upper().startswith('40GE'): + iftype = '40ge' + elif interface.upper().startswith('100GE'): + iftype = '100ge' + elif interface.upper().startswith('VLANIF'): + iftype = 'vlanif' + elif interface.upper().startswith('LOOPBACK'): + iftype = 'loopback' + elif interface.upper().startswith('METH'): + iftype = 'meth' + elif interface.upper().startswith('ETH-TRUNK'): + iftype = 'eth-trunk' + elif interface.upper().startswith('VBDIF'): + iftype = 'vbdif' + elif interface.upper().startswith('NVE'): + iftype = 'nve' + elif interface.upper().startswith('TUNNEL'): + iftype = 'tunnel' + elif interface.upper().startswith('ETHERNET'): + iftype = 'ethernet' + elif interface.upper().startswith('FCOE-PORT'): + iftype = 'fcoe-port' + elif interface.upper().startswith('FABRIC-PORT'): + iftype = 'fabric-port' + elif interface.upper().startswith('STACK-PORT'): + iftype = 'stack-Port' + elif interface.upper().startswith('NULL'): + iftype = 'null' + else: + return None + + return iftype.lower() + + +class VxlanVap(object): + """ + Manages VXLAN virtual access point. + """ + + def __init__(self, argument_spec): + self.spec = argument_spec + self.module = None + self.__init_module__() + + # module input info + self.bridge_domain_id = self.module.params['bridge_domain_id'] + self.bind_vlan_id = self.module.params['bind_vlan_id'] + self.l2_sub_interface = self.module.params['l2_sub_interface'] + self.ce_vid = self.module.params['ce_vid'] + self.pe_vid = self.module.params['pe_vid'] + self.encapsulation = self.module.params['encapsulation'] + self.state = self.module.params['state'] + + # state + self.vap_info = dict() + self.l2sub_info = dict() + self.changed = False + self.updates_cmd = list() + self.commands = list() + self.results = dict() + self.proposed = dict() + self.existing = dict() + self.end_state = dict() + + def __init_module__(self): + """init module""" + + required_together = [()] + self.module = AnsibleModule( + argument_spec=self.spec, supports_check_mode=True) + + def check_response(self, xml_str, xml_name): + """Check if response message is already succeed.""" + + if "" not in xml_str: + self.module.fail_json(msg='Error: %s failed.' % xml_name) + + def get_bd_vap_dict(self): + """get virtual access point info""" + + vap_info = dict() + conf_str = CE_NC_GET_BD_VAP % self.bridge_domain_id + xml_str = get_nc_config(self.module, conf_str) + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get vap: VLAN + vap_info["bdId"] = self.bridge_domain_id + root = ElementTree.fromstring(xml_str) + vap_info["vlanList"] = "" + vap_vlan = root.find("evc/bds/bd/bdBindVlan") + if vap_vlan: + for ele in vap_vlan: + if ele.tag == "vlanList": + vap_info["vlanList"] = ele.text + + # get vap: l2 su-interface + vap_ifs = root.findall( + "evc/bds/bd/servicePoints/servicePoint/ifName") + if_list = list() + if vap_ifs: + for vap_if in vap_ifs: + if vap_if.tag == "ifName": + if_list.append(vap_if.text) + vap_info["intfList"] = if_list + + return vap_info + + def get_l2_sub_intf_dict(self, ifname): + """get l2 sub-interface info""" + + intf_info = dict() + if not ifname: + return intf_info + + conf_str = CE_NC_GET_ENCAP % ifname + xml_str = get_nc_config(self.module, conf_str) + + if "" in xml_str: + return intf_info + + xml_str = xml_str.replace('\r', '').replace('\n', '').\ + replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ + replace('xmlns="http://www.huawei.com/netconf/vrp"', "") + + # get l2 sub interface encapsulation info + root = ElementTree.fromstring(xml_str) + bds = root.find("ethernet/servicePoints/servicePoint") + if not bds: + return intf_info + + for ele in bds: + if ele.tag in ["ifName", "flowType"]: + intf_info[ele.tag] = ele.text.lower() + + if intf_info.get("flowType") == "dot1q": + ce_vid = root.find( + "ethernet/servicePoints/servicePoint/flowDot1qs") + intf_info["dot1qVids"] = "" + if ce_vid: + for ele in ce_vid: + if ele.tag == "dot1qVids": + intf_info["dot1qVids"] = ele.text + elif intf_info.get("flowType") == "qinq": + vids = root.find( + "ethernet/servicePoints/servicePoint/flowQinqs/flowQinq") + if vids: + for ele in vids: + if ele.tag in ["peVlanId", "ceVids"]: + intf_info[ele.tag] = ele.text + + return intf_info + + def config_traffic_encap_dot1q(self): + """configure traffic encapsulation type dot1q""" + + xml_str = "" + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + if self.ce_vid: + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_DOT1Q % ( + self.l2_sub_interface, vlan_bitmap, vlan_bitmap) + self.updates_cmd.append("encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.ce_vid and not is_vlan_in_bitmap( + self.ce_vid, self.l2sub_info.get("dot1qVids")): + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_DOT1Q % ( + self.l2_sub_interface, vlan_bitmap, vlan_bitmap) + self.updates_cmd.append("encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + if self.ce_vid: + if is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("dot1qVids")): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append("undo encapsulation %s vid %s" % ( + self.encapsulation, self.ce_vid)) + else: + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + + if not xml_str: + self.updates_cmd.pop() + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP_DOT1Q") + self.changed = True + + def config_traffic_encap_qinq(self): + """configure traffic encapsulation type qinq""" + + xml_str = "" + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + if self.ce_vid: + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_QINQ % (self.l2_sub_interface, + self.pe_vid, + vlan_bitmap, + vlan_bitmap) + self.updates_cmd.append( + "encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.ce_vid: + if not is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("ceVids")) \ + or self.pe_vid != self.l2sub_info.get("peVlanId"): + vlan_bitmap = vlan_vid_to_bitmap(self.ce_vid) + xml_str = CE_NC_SET_ENCAP_QINQ % (self.l2_sub_interface, + self.pe_vid, + vlan_bitmap, + vlan_bitmap) + self.updates_cmd.append( + "encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + if self.ce_vid: + if is_vlan_in_bitmap(self.ce_vid, self.l2sub_info.get("ceVids")) \ + and self.pe_vid == self.l2sub_info.get("peVlanId"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s vid %s ce-vid %s" % (self.encapsulation, + self.pe_vid, + self.ce_vid)) + else: + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + + if not xml_str: + self.updates_cmd.pop() + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP_QINQ") + self.changed = True + + def config_traffic_encap(self): + """configure traffic encapsulation types""" + + if not self.l2sub_info: + self.module.fail_json(msg="Error: Interface %s does not exist." % self.l2_sub_interface) + + if not self.encapsulation: + return + + xml_str = "" + if self.encapsulation in ["default", "untag"]: + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + xml_str = CE_NC_SET_ENCAP % ( + self.l2_sub_interface, self.encapsulation) + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "encapsulation %s" % self.encapsulation) + else: + if self.encapsulation == self.l2sub_info.get("flowType"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo encapsulation %s" % self.encapsulation) + elif self.encapsulation == "none": + if self.state == "present": + if self.encapsulation != self.l2sub_info.get("flowType"): + xml_str = CE_NC_UNSET_ENCAP % self.l2_sub_interface + self.updates_cmd.append( + "interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo encapsulation %s" % self.l2sub_info.get("flowType")) + elif self.encapsulation == "dot1q": + self.config_traffic_encap_dot1q() + return + elif self.encapsulation == "qinq": + self.config_traffic_encap_qinq() + return + else: + pass + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_INTF_ENCAP") + self.changed = True + + def config_vap_sub_intf(self): + """configure a Layer 2 sub-interface as a service access point""" + + if not self.vap_info: + self.module.fail_json(msg="Error: Bridge domain %s does not exist." % self.bridge_domain_id) + + xml_str = "" + if self.state == "present": + if self.l2_sub_interface not in self.vap_info["intfList"]: + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + xml_str = CE_NC_MERGE_BD_INTF % ( + self.bridge_domain_id, self.l2_sub_interface) + else: + if self.l2_sub_interface in self.vap_info["intfList"]: + self.updates_cmd.append("interface %s" % self.l2_sub_interface) + self.updates_cmd.append( + "undo bridge-domain %s" % self.bridge_domain_id) + xml_str = CE_NC_DELETE_BD_INTF % ( + self.bridge_domain_id, self.l2_sub_interface) + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_VAP_SUB_INTERFACE") + self.changed = True + + def config_vap_vlan(self): + """configure a VLAN as a service access point""" + + xml_str = "" + if self.state == "present": + if not is_vlan_in_bitmap(self.bind_vlan_id, self.vap_info["vlanList"]): + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + self.updates_cmd.append( + "l2 binding vlan %s" % self.bind_vlan_id) + vlan_bitmap = vlan_vid_to_bitmap(self.bind_vlan_id) + xml_str = CE_NC_MERGE_BD_VLAN % ( + self.bridge_domain_id, vlan_bitmap, vlan_bitmap) + else: + if is_vlan_in_bitmap(self.bind_vlan_id, self.vap_info["vlanList"]): + self.updates_cmd.append("bridge-domain %s" % + self.bridge_domain_id) + self.updates_cmd.append( + "undo l2 binding vlan %s" % self.bind_vlan_id) + vlan_bitmap = vlan_vid_to_bitmap(self.bind_vlan_id) + xml_str = CE_NC_MERGE_BD_VLAN % ( + self.bridge_domain_id, "0" * 1024, vlan_bitmap) + + if not xml_str: + return + recv_xml = set_nc_config(self.module, xml_str) + self.check_response(recv_xml, "CONFIG_VAP_VLAN") + self.changed = True + + def is_vlan_valid(self, vid, name): + """check VLAN id""" + + if not vid: + return + + if not vid.isdigit(): + self.module.fail_json(msg="Error: %s is not digit." % name) + return + + if int(vid) < 1 or int(vid) > 4094: + self.module.fail_json( + msg="Error: %s is not in the range from 1 to 4094." % name) + + def is_l2_sub_intf_valid(self, ifname): + """check l2 sub interface valid""" + + if ifname.count('.') != 1: + return False + + if_num = ifname.split('.')[1] + if not if_num.isdigit(): + return False + + if int(if_num) < 1 or int(if_num) > 4096: + self.module.fail_json( + msg="Error: Sub-interface number is not in the range from 1 to 4096.") + return False + + if not get_interface_type(ifname): + return False + + return True + + def check_params(self): + """Check all input params""" + + # bridge domain id check + if self.bridge_domain_id: + if not self.bridge_domain_id.isdigit(): + self.module.fail_json( + msg="Error: Bridge domain id is not digit.") + if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215: + self.module.fail_json( + msg="Error: Bridge domain id is not in the range from 1 to 16777215.") + + # check bind_vlan_id + if self.bind_vlan_id: + self.is_vlan_valid(self.bind_vlan_id, "bind_vlan_id") + + # check l2_sub_interface + if self.l2_sub_interface and not self.is_l2_sub_intf_valid(self.l2_sub_interface): + self.module.fail_json(msg="Error: l2_sub_interface is invalid.") + + # check ce_vid + if self.ce_vid: + self.is_vlan_valid(self.ce_vid, "ce_vid") + if not self.encapsulation or self.encapsulation not in ["dot1q", "qinq"]: + self.module.fail_json(msg="Error: ce_vid can not be set " + "when encapsulation is '%s'." % self.encapsulation) + if self.encapsulation == "qinq" and not self.pe_vid: + self.module.fail_json(msg="Error: ce_vid and pe_vid must be set at the same time " + "when encapsulation is '%s'." % self.encapsulation) + # check pe_vid + if self.pe_vid: + self.is_vlan_valid(self.pe_vid, "pe_vid") + if not self.encapsulation or self.encapsulation != "qinq": + self.module.fail_json(msg="Error: pe_vid can not be set " + "when encapsulation is '%s'." % self.encapsulation) + if not self.ce_vid: + self.module.fail_json(msg="Error: ce_vid and pe_vid must be set at the same time " + "when encapsulation is '%s'." % self.encapsulation) + + def get_proposed(self): + """get proposed info""" + + if self.bridge_domain_id: + self.proposed["bridge_domain_id"] = self.bridge_domain_id + if self.bind_vlan_id: + self.proposed["bind_vlan_id"] = self.bind_vlan_id + if self.l2_sub_interface: + self.proposed["l2_sub_interface"] = self.l2_sub_interface + if self.encapsulation: + self.proposed["encapsulation"] = self.encapsulation + if self.ce_vid: + self.proposed["ce_vid"] = self.ce_vid + if self.pe_vid: + self.proposed["pe_vid"] = self.pe_vid + self.proposed["state"] = self.state + + def get_existing(self): + """get existing info""" + + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + self.existing["bridge_domain_id"] = self.bridge_domain_id + self.existing["bind_vlan_list"] = bitmap_to_vlan_list( + self.vap_info.get("vlanList")) + self.existing["bind_intf_list"] = self.vap_info.get("intfList") + + if self.encapsulation and self.l2_sub_interface: + self.existing["l2_sub_interface"] = self.l2_sub_interface + self.existing["encapsulation"] = self.l2sub_info.get("flowType") + if self.existing["encapsulation"] == "dot1q": + self.existing["ce_vid"] = bitmap_to_vlan_list( + self.l2sub_info.get("dot1qVids")) + if self.existing["encapsulation"] == "qinq": + self.existing["ce_vid"] = bitmap_to_vlan_list( + self.l2sub_info.get("ceVids")) + self.existing["pe_vid"] = self.l2sub_info.get("peVlanId") + + def get_end_state(self): + """get end state info""" + + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + vap_info = self.get_bd_vap_dict() + self.end_state["bridge_domain_id"] = self.bridge_domain_id + self.end_state["bind_vlan_list"] = bitmap_to_vlan_list( + vap_info.get("vlanList")) + self.end_state["bind_intf_list"] = vap_info.get("intfList") + + if self.encapsulation and self.l2_sub_interface: + l2sub_info = self.get_l2_sub_intf_dict(self.l2_sub_interface) + self.end_state["l2_sub_interface"] = self.l2_sub_interface + self.end_state["encapsulation"] = l2sub_info.get("flowType") + if self.end_state["encapsulation"] == "dot1q": + self.end_state["ce_vid"] = bitmap_to_vlan_list( + l2sub_info.get("dot1qVids")) + if self.end_state["encapsulation"] == "qinq": + self.end_state["ce_vid"] = bitmap_to_vlan_list( + l2sub_info.get("ceVids")) + self.end_state["pe_vid"] = l2sub_info.get("peVlanId") + + def data_init(self): + """data init""" + if self.l2_sub_interface: + self.l2_sub_interface = self.l2_sub_interface.replace( + " ", "").upper() + if self.encapsulation and self.l2_sub_interface: + self.l2sub_info = self.get_l2_sub_intf_dict(self.l2_sub_interface) + if self.bridge_domain_id: + if self.bind_vlan_id or self.l2_sub_interface: + self.vap_info = self.get_bd_vap_dict() + + def work(self): + """worker""" + + self.check_params() + self.data_init() + self.get_existing() + self.get_proposed() + + # Traffic encapsulation types + if self.encapsulation and self.l2_sub_interface: + self.config_traffic_encap() + + # A VXLAN service access point can be a Layer 2 sub-interface or VLAN + if self.bridge_domain_id: + if self.l2_sub_interface: + # configure a Layer 2 sub-interface as a service access point + self.config_vap_sub_intf() + + if self.bind_vlan_id: + # configure a VLAN as a service access point + self.config_vap_vlan() + self.get_end_state() + self.results['changed'] = self.changed + self.results['proposed'] = self.proposed + self.results['existing'] = self.existing + self.results['end_state'] = self.end_state + if self.changed: + self.results['updates'] = self.updates_cmd + else: + self.results['updates'] = list() + self.module.exit_json(**self.results) + + +def main(): + """Module main""" + + argument_spec = dict( + bridge_domain_id=dict(required=False, type='str'), + bind_vlan_id=dict(required=False, type='str'), + l2_sub_interface=dict(required=False, type='str'), + encapsulation=dict(required=False, type='str', + choices=['dot1q', 'default', 'untag', 'qinq', 'none']), + ce_vid=dict(required=False, type='str'), + pe_vid=dict(required=False, type='str'), + state=dict(required=False, default='present', + choices=['present', 'absent']) + ) + argument_spec.update(ce_argument_spec) + module = VxlanVap(argument_spec) + module.work() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudvision/cv_server_provision.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudvision/cv_server_provision.py new file mode 100644 index 00000000..50d6e1ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cloudvision/cv_server_provision.py @@ -0,0 +1,638 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cv_server_provision +author: "EOS+ CS (ansible-dev@arista.com) (@mharista)" +short_description: + Provision server port by applying or removing template configuration to an + Arista CloudVision Portal configlet that is applied to a switch. +description: + - This module allows a server team to provision server network ports for + new servers without having to access Arista CVP or asking the network team + to do it for them. Provide the information for connecting to CVP, switch + rack, port the new server is connected to, optional vlan, and an action + and the module will apply the configuration to the switch port via CVP. + Actions are add (applies template config to port), + remove (defaults the interface config) and + show (returns the current port config). +options: + host: + description: + - The hostname or IP address of the CVP node being connected to. + required: true + port: + description: + - The port number to use when making API calls to the CVP node. This + will default to the default port for the specified protocol. Port 80 + for http and port 443 for https. + protocol: + description: + - The protocol to use when making API calls to CVP. CVP defaults to https + and newer versions of CVP no longer support http. + default: https + choices: [https, http] + username: + description: + - The user that will be used to connect to CVP for making API calls. + required: true + password: + description: + - The password of the user that will be used to connect to CVP for API + calls. + required: true + server_name: + description: + - The hostname or identifier for the server that is having it's switch + port provisioned. + required: true + switch_name: + description: + - The hostname of the switch is being configured for the server being + provisioned. + required: true + switch_port: + description: + - The physical port number on the switch that the new server is + connected to. + required: true + port_vlan: + description: + - The vlan that should be applied to the port for this server. + This parameter is dependent on a proper template that supports single + vlan provisioning with it. If a port vlan is specified by the template + specified does not support this the module will exit out with no + changes. If a template is specified that requires a port vlan but no + port vlan is specified the module will exit out with no changes. + template: + description: + - A path to a Jinja formatted template file that contains the + configuration block that will be applied to the specified switch port. + This template will have variable fields replaced by the module before + being applied to the switch configuration. + required: true + action: + description: + - The action for the module to take. The actions are add, which applies + the specified template config to port, remove, which defaults the + specified interface configuration, and show, which will return the + current port configuration with no changes. + default: show + choices: [show, add, remove] + auto_run: + description: + - Flag that determines whether or not the module will execute the CVP + task spawned as a result of changes to a switch configlet. When an + add or remove action is taken which results in a change to a switch + configlet, CVP will spawn a task that needs to be executed for the + configuration to be applied to the switch. If this option is True then + the module will determined the task number created by the configuration + change, execute it and wait for the task to complete. If the option + is False then the task will remain in the Pending state in CVP for + a network administrator to review and execute. + type: bool + default: 'no' +requirements: [Jinja2, cvprac >= 0.7.0] +''' + +EXAMPLES = ''' +- name: Get current configuration for interface Ethernet2 + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: template_file.j2 + action: show + +- name: Remove existing configuration from interface Ethernet2. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: template_file.j2 + action: remove + auto_run: True + +- name: Add template configuration to interface Ethernet2. No VLAN. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + template: single_attached_trunk.j2 + action: add + auto_run: True + +- name: Add template with VLAN configuration to interface Ethernet2. Run task. + community.network.cv_server_provision: + host: cvp_node + username: cvp_user + password: cvp_pass + protocol: https + server_name: new_server + switch_name: eos_switch_1 + switch_port: 2 + port_vlan: 22 + template: single_attached_vlan.j2 + action: add + auto_run: True +''' + +RETURN = ''' +changed: + description: Signifies if a change was made to the configlet + returned: success + type: bool + sample: true +currentConfigBlock: + description: The current config block for the user specified interface + returned: when action = show + type: str + sample: | + interface Ethernet4 + ! +newConfigBlock: + description: The new config block for the user specified interface + returned: when action = add or remove + type: str + sample: | + interface Ethernet3 + description example + no switchport + ! +oldConfigBlock: + description: The current config block for the user specified interface + before any changes are made + returned: when action = add or remove + type: str + sample: | + interface Ethernet3 + ! +fullConfig: + description: The full config of the configlet after being updated + returned: when action = add or remove + type: str + sample: | + ! + interface Ethernet3 + ! + interface Ethernet4 + ! +updateConfigletResponse: + description: Response returned from CVP when configlet update is triggered + returned: when action = add or remove and configuration changes + type: str + sample: "Configlet veos1-server successfully updated and task initiated." +portConfigurable: + description: Signifies if the user specified port has an entry in the + configlet that Ansible has access to + returned: success + type: bool + sample: true +switchConfigurable: + description: Signifies if the user specified switch has a configlet + applied to it that CVP is allowed to edit + returned: success + type: bool + sample: true +switchInfo: + description: Information from CVP describing the switch being configured + returned: success + type: dict + sample: {"architecture": "i386", + "bootupTimeStamp": 1491264298.21, + "complianceCode": "0000", + "complianceIndication": "NONE", + "deviceInfo": "Registered", + "deviceStatus": "Registered", + "fqdn": "veos1", + "hardwareRevision": "", + "internalBuildId": "12-12", + "internalVersion": "4.17.1F-11111.4171F", + "ipAddress": "192.168.1.20", + "isDANZEnabled": "no", + "isMLAGEnabled": "no", + "key": "00:50:56:5d:e5:e0", + "lastSyncUp": 1496432895799, + "memFree": 472976, + "memTotal": 1893460, + "modelName": "vEOS", + "parentContainerId": "container_13_5776759195930", + "serialNumber": "", + "systemMacAddress": "00:50:56:5d:e5:e0", + "taskIdList": [], + "tempAction": null, + "type": "netelement", + "unAuthorized": false, + "version": "4.17.1F", + "ztpMode": "false"} +taskCompleted: + description: Signifies if the task created and executed has completed successfully + returned: when action = add or remove, and auto_run = true, + and configuration changes + type: bool + sample: true +taskCreated: + description: Signifies if a task was created due to configlet changes + returned: when action = add or remove, and auto_run = true or false, + and configuration changes + type: bool + sample: true +taskExecuted: + description: Signifies if the automation executed the spawned task + returned: when action = add or remove, and auto_run = true, + and configuration changes + type: bool + sample: true +taskId: + description: The task ID created by CVP because of changes to configlet + returned: when action = add or remove, and auto_run = true or false, + and configuration changes + type: str + sample: "500" +''' + +import re +import time +from ansible.module_utils.basic import AnsibleModule +try: + import jinja2 + from jinja2 import meta + HAS_JINJA2 = True +except ImportError: + HAS_JINJA2 = False +try: + from cvprac.cvp_client import CvpClient + from cvprac.cvp_client_errors import CvpLoginError, CvpApiError + HAS_CVPRAC = True +except ImportError: + HAS_CVPRAC = False + + +def connect(module): + ''' Connects to CVP device using user provided credentials from playbook. + + :param module: Ansible module with parameters and client connection. + :return: CvpClient object with connection instantiated. + ''' + client = CvpClient() + try: + client.connect([module.params['host']], + module.params['username'], + module.params['password'], + protocol=module.params['protocol'], + port=module.params['port']) + except CvpLoginError as e: + module.fail_json(msg=str(e)) + return client + + +def switch_info(module): + ''' Get dictionary of switch info from CVP. + + :param module: Ansible module with parameters and client connection. + :return: Dict of switch info from CVP or exit with failure if no + info for device is found. + ''' + switch_name = module.params['switch_name'] + switch_info = module.client.api.get_device_by_name(switch_name) + if not switch_info: + module.fail_json(msg=str("Device with name '%s' does not exist." + % switch_name)) + return switch_info + + +def switch_in_compliance(module, sw_info): + ''' Check if switch is currently in compliance. + + :param module: Ansible module with parameters and client connection. + :param sw_info: Dict of switch info. + :return: Nothing or exit with failure if device is not in compliance. + ''' + compliance = module.client.api.check_compliance(sw_info['key'], + sw_info['type']) + if compliance['complianceCode'] != '0000': + module.fail_json(msg=str('Switch %s is not in compliance. Returned' + ' compliance code %s.' + % (sw_info['fqdn'], + compliance['complianceCode']))) + + +def server_configurable_configlet(module, sw_info): + ''' Check CVP that the user specified switch has a configlet assigned to + it that Ansible is allowed to edit. + + :param module: Ansible module with parameters and client connection. + :param sw_info: Dict of switch info. + :return: Dict of configlet information or None. + ''' + configurable_configlet = None + configlet_name = module.params['switch_name'] + '-server' + switch_configlets = module.client.api.get_configlets_by_device_id( + sw_info['key']) + for configlet in switch_configlets: + if configlet['name'] == configlet_name: + configurable_configlet = configlet + return configurable_configlet + + +def port_configurable(module, configlet): + ''' Check configlet if the user specified port has a configuration entry + in the configlet to determine if Ansible is allowed to configure the + port on this switch. + + :param module: Ansible module with parameters and client connection. + :param configlet: Dict of configlet info. + :return: True or False. + ''' + configurable = False + regex = r'^interface Ethernet%s' % module.params['switch_port'] + for config_line in configlet['config'].split('\n'): + if re.match(regex, config_line): + configurable = True + return configurable + + +def configlet_action(module, configlet): + ''' Take appropriate action based on current state of device and user + requested action. + + Return current config block for specified port if action is show. + + If action is add or remove make the appropriate changes to the + configlet and return the associated information. + + :param module: Ansible module with parameters and client connection. + :param configlet: Dict of configlet info. + :return: Dict of information to updated results with. + ''' + result = dict() + existing_config = current_config(module, configlet['config']) + if module.params['action'] == 'show': + result['currentConfigBlock'] = existing_config + return result + elif module.params['action'] == 'add': + result['newConfigBlock'] = config_from_template(module) + elif module.params['action'] == 'remove': + result['newConfigBlock'] = ('interface Ethernet%s\n!' + % module.params['switch_port']) + result['oldConfigBlock'] = existing_config + result['fullConfig'] = updated_configlet_content(module, + configlet['config'], + result['newConfigBlock']) + resp = module.client.api.update_configlet(result['fullConfig'], + configlet['key'], + configlet['name']) + if 'data' in resp: + result['updateConfigletResponse'] = resp['data'] + if 'task' in resp['data']: + result['changed'] = True + result['taskCreated'] = True + return result + + +def current_config(module, config): + ''' Parse the full port configuration for the user specified port out of + the full configlet configuration and return as a string. + + :param module: Ansible module with parameters and client connection. + :param config: Full config to parse specific port config from. + :return: String of current config block for user specified port. + ''' + regex = r'^interface Ethernet%s' % module.params['switch_port'] + match = re.search(regex, config, re.M) + if not match: + module.fail_json(msg=str('interface section not found - %s' + % config)) + block_start, line_end = match.regs[0] + + match = re.search(r'!', config[line_end:], re.M) + if not match: + return config[block_start:] + _, block_end = match.regs[0] + + block_end = line_end + block_end + return config[block_start:block_end] + + +def valid_template(port, template): + ''' Test if the user provided Jinja template is valid. + + :param port: User specified port. + :param template: Contents of Jinja template. + :return: True or False + ''' + valid = True + regex = r'^interface Ethernet%s' % port + match = re.match(regex, template, re.M) + if not match: + valid = False + return valid + + +def config_from_template(module): + ''' Load the Jinja template and apply user provided parameters in necessary + places. Fail if template is not found. Fail if rendered template does + not reference the correct port. Fail if the template requires a VLAN + but the user did not provide one with the port_vlan parameter. + + :param module: Ansible module with parameters and client connection. + :return: String of Jinja template rendered with parameters or exit with + failure. + ''' + template_loader = jinja2.FileSystemLoader('./templates') + env = jinja2.Environment(loader=template_loader, + undefined=jinja2.DebugUndefined) + template = env.get_template(module.params['template']) + if not template: + module.fail_json(msg=str('Could not find template - %s' + % module.params['template'])) + + data = {'switch_port': module.params['switch_port'], + 'server_name': module.params['server_name']} + + temp_source = env.loader.get_source(env, module.params['template'])[0] + parsed_content = env.parse(temp_source) + temp_vars = list(meta.find_undeclared_variables(parsed_content)) + if 'port_vlan' in temp_vars: + if module.params['port_vlan']: + data['port_vlan'] = module.params['port_vlan'] + else: + module.fail_json(msg=str('Template %s requires a vlan. Please' + ' re-run with vlan number provided.' + % module.params['template'])) + + template = template.render(data) + if not valid_template(module.params['switch_port'], template): + module.fail_json(msg=str('Template content does not configure proper' + ' interface - %s' % template)) + return template + + +def updated_configlet_content(module, existing_config, new_config): + ''' Update the configlet configuration with the new section for the port + specified by the user. + + :param module: Ansible module with parameters and client connection. + :param existing_config: String of current configlet configuration. + :param new_config: String of configuration for user specified port to + replace in the existing config. + :return: String of the full updated configuration. + ''' + regex = r'^interface Ethernet%s' % module.params['switch_port'] + match = re.search(regex, existing_config, re.M) + if not match: + module.fail_json(msg=str('interface section not found - %s' + % existing_config)) + block_start, line_end = match.regs[0] + + updated_config = existing_config[:block_start] + new_config + match = re.search(r'!\n', existing_config[line_end:], re.M) + if match: + _, block_end = match.regs[0] + block_end = line_end + block_end + updated_config += '\n%s' % existing_config[block_end:] + return updated_config + + +def configlet_update_task(module): + ''' Poll device info of switch from CVP up to three times to see if the + configlet updates have spawned a task. It sometimes takes a second for + the task to be spawned after configlet updates. If a task is found + return the task ID. Otherwise return None. + + :param module: Ansible module with parameters and client connection. + :return: Task ID or None. + ''' + for num in range(3): + device_info = switch_info(module) + if (('taskIdList' in device_info) and + (len(device_info['taskIdList']) > 0)): + for task in device_info['taskIdList']: + if ('Configlet Assign' in task['description'] and + task['data']['WORKFLOW_ACTION'] == 'Configlet Push'): + return task['workOrderId'] + time.sleep(1) + return None + + +def wait_for_task_completion(module, task): + ''' Poll CVP for the executed task to complete. There is currently no + timeout. Exits with failure if task status is Failed or Cancelled. + + :param module: Ansible module with parameters and client connection. + :param task: Task ID to poll for completion. + :return: True or exit with failure if task is cancelled or fails. + ''' + task_complete = False + while not task_complete: + task_info = module.client.api.get_task_by_id(task) + task_status = task_info['workOrderUserDefinedStatus'] + if task_status == 'Completed': + return True + elif task_status in ['Failed', 'Cancelled']: + module.fail_json(msg=str('Task %s has reported status %s. Please' + ' consult the CVP admins for more' + ' information.' % (task, task_status))) + time.sleep(2) + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + host=dict(required=True), + port=dict(required=False, default=None), + protocol=dict(default='https', choices=['http', 'https']), + username=dict(required=True), + password=dict(required=True, no_log=True), + server_name=dict(required=True), + switch_name=dict(required=True), + switch_port=dict(required=True), + port_vlan=dict(required=False, default=None), + template=dict(require=True), + action=dict(default='show', choices=['show', 'add', 'remove']), + auto_run=dict(type='bool', default=False)) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + if not HAS_JINJA2: + module.fail_json(msg='The Jinja2 python module is required.') + if not HAS_CVPRAC: + module.fail_json(msg='The cvprac python module is required.') + result = dict(changed=False) + module.client = connect(module) + + try: + result['switchInfo'] = switch_info(module) + if module.params['action'] in ['add', 'remove']: + switch_in_compliance(module, result['switchInfo']) + switch_configlet = server_configurable_configlet(module, + result['switchInfo']) + if not switch_configlet: + module.fail_json(msg=str('Switch %s has no configurable server' + ' ports.' % module.params['switch_name'])) + result['switchConfigurable'] = True + if not port_configurable(module, switch_configlet): + module.fail_json(msg=str('Port %s is not configurable as a server' + ' port on switch %s.' + % (module.params['switch_port'], + module.params['switch_name']))) + result['portConfigurable'] = True + result['taskCreated'] = False + result['taskExecuted'] = False + result['taskCompleted'] = False + result.update(configlet_action(module, switch_configlet)) + if module.params['auto_run'] and module.params['action'] != 'show': + task_id = configlet_update_task(module) + if task_id: + result['taskId'] = task_id + note = ('Update config on %s with %s action from Ansible.' + % (module.params['switch_name'], + module.params['action'])) + module.client.api.add_note_to_task(task_id, note) + module.client.api.execute_task(task_id) + result['taskExecuted'] = True + task_completed = wait_for_task_completion(module, task_id) + if task_completed: + result['taskCompleted'] = True + else: + result['taskCreated'] = False + except CvpApiError as e: + module.fail_json(msg=str(e)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_backup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_backup.py new file mode 100644 index 00000000..5105e759 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_backup.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Backup Config to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_backup +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Backup the current running or startup configuration to a + remote server on devices running Lenovo CNOS +description: + - This module allows you to work with switch configurations. It provides a + way to back up the running or startup configurations of a switch to a + remote server. This is achieved by periodically saving a copy of the + startup or running configuration of the network device to a remote server + using FTP, SFTP, TFTP, or SCP. The first step is to create a directory from + where the remote server can be reached. The next step is to provide the + full file path of the location where the configuration will be backed up. + Authentication details required by the remote server must be provided as + well. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + configType: + description: + - This specifies what type of configuration will be backed up. The + choices are the running or startup configurations. There is no + default value, so it will result in an error if the input is + incorrect. + required: Yes + default: Null + choices: [running-config, startup-config] + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server to where to upload the backup + configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is + not specified, there is no default value to be used. + required: Yes + default: Null + choices: [SFTP, SCP, FTP, TFTP] + rcserverip: + description: + -This specifies the IP Address of the remote server to where the + configuration will be backed up. + required: Yes + default: Null + rcpath: + description: + - This specifies the full file path where the configuration file + will be copied on the remote server. In case the relative path is + used as the variable value, the root folder for the user of the + server needs to be specified. + required: Yes + default: Null + serverusername: + description: + - Specify the username for the server relating to the protocol + used. + required: Yes + default: Null + serverpassword: + description: + - Specify the password for the server relating to the protocol + used. + required: Yes + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_backup. + These are written in the main.yml file of the tasks directory. +--- +- name: Test Running Config Backup + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Startup Config Backup + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Running Config Backup -TFTP + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Startup Config Backup - TFTP + community.network.cnos_backup: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Config file transferred to server" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +# Utility Method to back up the running config or start up config +# This method supports only SCP or SFTP or FTP or TFTP +# Tuning of timeout parameter is pending +def doConfigBackUp(module, prompt, answer): + host = module.params['host'] + server = module.params['serverip'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + protocol = module.params['protocol'].lower() + rcPath = module.params['rcpath'] + configType = module.params['configType'] + confPath = rcPath + host + '_' + configType + '.txt' + + retVal = '' + + # config backup command happens here + command = "copy " + configType + " " + protocol + " " + protocol + "://" + command = command + username + "@" + server + "/" + confPath + command = command + " vrf management\n" + cnos.debugOutput(command + "\n") + # cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes') + cmd = [] + if(protocol == "scp"): + scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}] + scp_cmd2 = [{'command': '\n', 'prompt': 'Password:', + 'answer': password}] + cmd.extend(scp_cmd1) + cmd.extend(scp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + sftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(sftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + ftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(ftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + configType + " " + protocol + " " + protocol + command = command + "://" + server + "/" + confPath + command = command + " vrf management\n" + # cnos.debugOutput(command) + tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + configType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + rcpath=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + if(protocol == "tftp" or protocol == "ftp" or + protocol == "sftp" or protocol == "scp"): + transfer_status = doConfigBackUp(module, None, None) + else: + transfer_status = "Invalid Protocol option" + + output = output + "\n Config Back Up status \n" + transfer_status + + # Save it into the file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Config file transferred to server") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_banner.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_banner.py new file mode 100644 index 00000000..09622ffe --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_banner.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send banner commands to Lenovo Switches +# Two types of banners are supported login and motd +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_banner +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage multiline banners on Lenovo CNOS devices +description: + - This will configure both login and motd banners on remote devices + running Lenovo CNOS. It allows playbooks to add or remote + banner text from the active running configuration. +notes: + - Tested against CNOS 10.8.1 +options: + banner: + description: + - Specifies which banner should be configured on the remote device. + In Ansible 2.8 and earlier only I(login) and I(motd) were supported. + required: true + choices: ['login', 'motd'] + text: + description: + - The banner text that should be + present in the remote device running configuration. This argument + accepts a multiline string, with no empty lines. Requires + I(state=present). + state: + description: + - Specifies whether or not the configuration is + present in the current devices active running configuration. + default: present + choices: ['present', 'absent'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Configure the login banner + community.network.cnos_banner: + banner: login + text: | + this is my login banner + that contains a multiline + string + state: present + +- name: Remove the motd banner + community.network.cnos_banner: + banner: motd + state: absent + +- name: Configure banner from file + community.network.cnos_banner: + banner: motd + text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}" + state: present + +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner login + - this is my login banner + - that contains a multiline + - string +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils._text import to_text +import re + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + + if state == 'absent' and 'text' in have.keys() and have['text']: + commands.append('no banner %s' % module.params['banner']) + + elif state == 'present': + if want['text'] and (want['text'] != have.get('text')): + banner_cmd = 'banner %s ' % module.params['banner'] + for bline in want['text'].strip().splitlines(): + final_cmd = banner_cmd + bline.strip() + commands.append(final_cmd) + + return commands + + +def map_config_to_obj(module): + rc, out, err = exec_command(module, + 'show banner %s' % module.params['banner']) + if rc == 0: + output = out + else: + rc, out, err = exec_command(module, + 'show running-config | include banner %s' + % module.params['banner']) + if out: + output = re.search(r'\^C(.*)\^C', out, re.S).group(1).strip() + else: + output = None + obj = {'banner': module.params['banner'], 'state': 'absent'} + if output: + obj['text'] = output + obj['state'] = 'present' + return obj + + +def map_params_to_obj(module): + text = module.params['text'] + if text: + text = to_text(text).strip() + + return { + 'banner': module.params['banner'], + 'text': text, + 'state': module.params['state'] + } + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + banner=dict(required=True, choices=['login', 'motd']), + text=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(cnos_argument_spec) + + required_if = [('state', 'present', ('text',))] + + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + response = load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_bgp.py new file mode 100644 index 00000000..597a4abd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_bgp.py @@ -0,0 +1,1176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send BGP commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_bgp +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage BGP resources and attributes on devices running CNOS +description: + - This module allows you to work with Border Gateway Protocol (BGP) related + configurations. The operators used are overloaded to ensure control over + switch BGP configurations. This module is invoked using method with + asNumber as one of its arguments. The first level of the BGP configuration + allows to set up an AS number, with the following attributes going + into various configuration operations under the context of BGP. + After passing this level, there are eight BGP arguments that will perform + further configurations. They are bgpArg1, bgpArg2, bgpArg3, bgpArg4, + bgpArg5, bgpArg6, bgpArg7, and bgpArg8. For more details on how to use + these arguments, see [Overloaded Variables]. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + asNum: + description: + - AS number + required: Yes + default: Null + bgpArg1: + description: + - This is an overloaded bgp first argument. Usage of this argument + can be found is the User Guide referenced above. + required: Yes + default: Null + choices: [address-family,bestpath,bgp,cluster-id,confederation, + enforce-first-as,fast-external-failover,graceful-restart, + graceful-restart-helper,log-neighbor-changes, + maxas-limit,neighbor,router-id,shutdown,synchronization, + timers,vrf] + bgpArg2: + description: + - This is an overloaded bgp second argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [ipv4 or ipv6, always-compare-med,compare-confed-aspath, + compare-routerid,dont-compare-originator-id,tie-break-on-age, + as-path,med,identifier,peers] + bgpArg3: + description: + - This is an overloaded bgp third argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [aggregate-address,client-to-client,dampening,distance, + maximum-paths,network,nexthop,redistribute,save, + synchronization,ignore or multipath-relax, + confed or missing-as-worst or non-deterministic or + remove-recv-med or remove-send-med] + bgpArg4: + description: + - This is an overloaded bgp fourth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Aggregate prefix, Reachability Half-life time,route-map, + Distance for routes ext,ebgp or ibgp,IP prefix , + IP prefix /, synchronization, + Delay value, direct, ospf, static, memory] + bgpArg5: + description: + - This is an overloaded bgp fifth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [as-set, summary-only, Value to start reusing a route, + Distance for routes internal, Supported multipath numbers, + backdoor, map, route-map ] + bgpArg6: + description: + - This is an overloaded bgp sixth argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [summary-only,as-set, route-map name, + Value to start suppressing a route, Distance local routes, + Network mask, Pointer to route-map entries] + bgpArg7: + description: + - This is an overloaded bgp seventh argument. Use of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Maximum duration to suppress a stable route(minutes), + backdoor,route-map, Name of the route map ] + bgpArg8: + description: + - This is an overloaded bgp eight argument. Usage of this argument + can be found is the User Guide referenced above. + required: No + default: Null + choices: [Un-reachability Half-life time for the penalty(minutes), + backdoor] +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module cnos_bgp. These are + written in the main.yml file of the tasks directory. +--- +- name: Test BGP - neighbor + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "address-family" + bgpArg5: "ipv4" + bgpArg6: "next-hop-self" + +- name: Test BGP - BFD + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "bfd" + +- name: Test BGP - address-family - dampening + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "address-family" + bgpArg2: "ipv4" + bgpArg3: "dampening" + bgpArg4: 13 + bgpArg5: 233 + bgpArg6: 333 + bgpArg7: 15 + bgpArg8: 33 + +- name: Test BGP - address-family - network + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "address-family" + bgpArg2: "ipv4" + bgpArg3: "network" + bgpArg4: "1.2.3.4/5" + bgpArg5: "backdoor" + +- name: Test BGP - bestpath - always-compare-med + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bestpath" + bgpArg2: "always-compare-med" + +- name: Test BGP - bestpath-compare-confed-aspat + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bestpath" + bgpArg2: "compare-confed-aspath" + +- name: Test BGP - bgp + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "bgp" + bgpArg2: 33 + +- name: Test BGP - cluster-id + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "cluster-id" + bgpArg2: "1.2.3.4" + +- name: Test BGP - confederation-identifier + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "confederation" + bgpArg2: "identifier" + bgpArg3: 333 + +- name: Test BGP - enforce-first-as + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "enforce-first-as" + +- name: Test BGP - fast-external-failover + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "fast-external-failover" + +- name: Test BGP - graceful-restart + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "graceful-restart" + bgpArg2: 333 + +- name: Test BGP - graceful-restart-helper + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "graceful-restart-helper" + +- name: Test BGP - maxas-limit + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "maxas-limit" + bgpArg2: 333 + +- name: Test BGP - neighbor + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "neighbor" + bgpArg2: "10.241.107.40" + bgpArg3: 13 + bgpArg4: "address-family" + bgpArg5: "ipv4" + bgpArg6: "next-hop-self" + +- name: Test BGP - router-id + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "router-id" + bgpArg2: "1.2.3.4" + +- name: Test BGP - synchronization + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "synchronization" + +- name: Test BGP - timers + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "timers" + bgpArg2: 333 + bgpArg3: 3333 + +- name: Test BGP - vrf + community.network.cnos_bgp: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_bgp_{{ inventory_hostname }}_output.txt" + asNum: 33 + bgpArg1: "vrf" + +''' +RETURN = ''' +msg: + description: Success or failure message. Upon any failure, the method returns + an error display string. + returned: always + type: str +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def bgpNeighborConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpNeighborArg1 = module.params['bgpArg4'] + bgpNeighborArg2 = module.params['bgpArg5'] + bgpNeighborArg3 = module.params['bgpArg6'] + bgpNeighborArg4 = module.params['bgpArg7'] + bgpNeighborArg5 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + + if(bgpNeighborArg1 == "address-family"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_address_family", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " unicast" + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpNeighborAFConfig(module, cmd, '(config-router-neighbor-af)#', answer) + return retVal + else: + retVal = "Error-316" + return retVal + + elif(bgpNeighborArg1 == "advertisement-interval"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "bfd"): + command = command + bgpNeighborArg1 + " " + if(bgpNeighborArg2 is not None and bgpNeighborArg2 == "mutihop"): + command = command + bgpNeighborArg2 + + elif(bgpNeighborArg1 == "connection-retry-time"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_connection_retrytime", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-315" + return retVal + + elif(bgpNeighborArg1 == "description"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_description", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-314" + return retVal + + elif(bgpNeighborArg1 == "disallow-infinite-holdtime"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "dont-capability-negotiate"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "dynamic-capability"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "ebgp-multihop"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_maxhopcount", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-313" + return retVal + + elif(bgpNeighborArg1 == "interface"): + command = command + bgpNeighborArg1 + " " + # TBD + + elif(bgpNeighborArg1 == "local-as"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_local_as", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + if(bgpNeighborArg3 is not None and + bgpNeighborArg3 == "no-prepend"): + command = command + bgpNeighborArg3 + " " + if(bgpNeighborArg4 is not None and + bgpNeighborArg4 == "replace-as"): + command = command + bgpNeighborArg4 + " " + if(bgpNeighborArg5 is not None and + bgpNeighborArg5 == "dual-as"): + command = command + bgpNeighborArg5 + else: + command = command.strip() + else: + command = command.strip() + else: + command = command.strip() + else: + retVal = "Error-312" + return retVal + + elif(bgpNeighborArg1 == "maximum-peers"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_maxpeers", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-311" + return retVal + + elif(bgpNeighborArg1 == "password"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_password", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-310" + return retVal + + elif(bgpNeighborArg1 == "remove-private-AS"): + command = command + bgpNeighborArg1 + + elif(bgpNeighborArg1 == "timers"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_timers_Keepalive", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_timers_holdtime", bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-309" + return retVal + else: + retVal = "Error-308" + return retVal + + elif(bgpNeighborArg1 == "transport"): + command = command + bgpNeighborArg1 + " connection-mode passive " + + elif(bgpNeighborArg1 == "ttl-security"): + command = command + bgpNeighborArg1 + " hops " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_ttl_hops", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-307" + return retVal + + elif(bgpNeighborArg1 == "update-source"): + command = command + bgpNeighborArg1 + " " + if(bgpNeighborArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_options", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + " " + if(bgpNeighborArg2 == "ethernet"): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_ethernet", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-304" + return retVal + elif(bgpNeighborArg2 == "loopback"): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_loopback", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-305" + return retVal + else: + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_update_vlan", + bgpNeighborArg3) + if(value == "ok"): + command = command + bgpNeighborArg3 + else: + retVal = "Error-306" + return retVal + else: + command = command + bgpNeighborArg2 + else: + retVal = "Error-303" + return retVal + + elif(bgpNeighborArg1 == "weight"): + command = command + bgpNeighborArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_weight", bgpNeighborArg2) + if(value == "ok"): + command = command + bgpNeighborArg2 + else: + retVal = "Error-302" + return retVal + + else: + retVal = "Error-301" + return retVal + + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + return retVal +# EOM + + +def bgpNeighborAFConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpNeighborAFArg1 = module.params['bgpArg6'] + bgpNeighborAFArg2 = module.params['bgpArg7'] + bgpNeighborAFArg3 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + if(bgpNeighborAFArg1 == "allowas-in"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_occurances", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-325" + return retVal + + elif(bgpNeighborAFArg1 == "default-originate"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None and bgpNeighborAFArg2 == "route-map"): + command = command + bgpNeighborAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-324" + return retVal + + elif(bgpNeighborAFArg1 == "filter-list"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_filtername", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 == "in" or bgpNeighborAFArg3 == "out"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-323" + return retVal + else: + retVal = "Error-322" + return retVal + + elif(bgpNeighborAFArg1 == "maximum-prefix"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_maxprefix", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 is not None): + command = command + bgpNeighborAFArg3 + else: + command = command.strip() + else: + retVal = "Error-326" + return retVal + + elif(bgpNeighborAFArg1 == "next-hop-self"): + command = command + bgpNeighborAFArg1 + + elif(bgpNeighborAFArg1 == "prefix-list"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_prefixname", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + " " + if(bgpNeighborAFArg3 == "in" or bgpNeighborAFArg3 == "out"): + command = command + bgpNeighborAFArg3 + else: + retVal = "Error-321" + return retVal + else: + retVal = "Error-320" + return retVal + + elif(bgpNeighborAFArg1 == "route-map"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-319" + return retVal + elif(bgpNeighborAFArg1 == "route-reflector-client"): + command = command + bgpNeighborAFArg1 + + elif(bgpNeighborAFArg1 == "send-community"): + command = command + bgpNeighborAFArg1 + " " + if(bgpNeighborAFArg2 is not None and bgpNeighborAFArg2 == "extended"): + command = command + bgpNeighborAFArg2 + + elif(bgpNeighborAFArg1 == "soft-reconfiguration"): + command = command + bgpNeighborAFArg1 + " inbound" + + elif(bgpNeighborAFArg1 == "unsuppress-map"): + command = command + bgpNeighborAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_neighbor_af_routemap", bgpNeighborAFArg2) + if(value == "ok"): + command = command + bgpNeighborAFArg2 + else: + retVal = "Error-318" + return retVal + + else: + retVal = "Error-317" + return retVal + + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + return retVal +# EOM + + +def bgpAFConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpAFArg1 = module.params['bgpArg3'] + bgpAFArg2 = module.params['bgpArg4'] + bgpAFArg3 = module.params['bgpArg5'] + bgpAFArg4 = module.params['bgpArg6'] + bgpAFArg5 = module.params['bgpArg7'] + bgpAFArg6 = module.params['bgpArg8'] + deviceType = module.params['deviceType'] + if(bgpAFArg1 == "aggregate-address"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_aggregate_prefix", bgpAFArg2) + if(value == "ok"): + if(bgpAFArg2 is None): + command = command.strip() + elif(bgpAFArg2 == "as-set" or bgpAFArg2 == "summary-only"): + command = command + bgpAFArg2 + " " + if((bgpAFArg3 is not None) and (bgpAFArg2 == "as-set")): + command = command + "summary-only" + else: + retVal = "Error-297" + return retVal + else: + retVal = "Error-296" + return retVal + + elif(bgpAFArg1 == "client-to-client"): + command = command + bgpAFArg1 + " reflection " + + elif(bgpAFArg1 == "dampening"): + command = command + bgpAFArg1 + " " + if(bgpAFArg2 == "route-map"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-196" + return retVal + elif(bgpAFArg2 is not None): + value = cnos.checkSanityofVariable( + deviceType, "reachability_half_life", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None): + value1 = cnos.checkSanityofVariable( + deviceType, "start_reuse_route_value", bgpAFArg3) + value2 = cnos.checkSanityofVariable( + deviceType, "start_suppress_route_value", bgpAFArg4) + value3 = cnos.checkSanityofVariable( + deviceType, "max_duration_to_suppress_route", + bgpAFArg5) + if(value1 == "ok" and value2 == "ok" and value3 == "ok"): + command = command + bgpAFArg3 + " " + bgpAFArg4 + \ + " " + bgpAFArg5 + " " + if(bgpAFArg6 is not None): + value = cnos.checkSanityofVariable( + deviceType, + "unreachability_halftime_for_penalty", + bgpAFArg6) + if(value == "ok"): + command = command + bgpAFArg6 + else: + retVal = "Error-295" + return retVal + else: + command = command.strip() + else: + retVal = "Error-294" + return retVal + + elif(bgpAFArg1 == "distance"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_external_AS", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_internal_AS", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + " " + value = cnos.checkSanityofVariable( + deviceType, "distance_local_routes", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + else: + retVal = "Error-291" + return retVal + else: + retVal = "Error-292" + return retVal + else: + retVal = "Error-293" + return retVal + + elif(bgpAFArg1 == "maximum-paths"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "maxpath_option", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "maxpath_numbers", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-199" + return retVal + else: + retVal = "Error-290" + return retVal + + elif(bgpAFArg1 == "network"): + command = command + bgpAFArg1 + " " + if(bgpAFArg2 == "synchronization"): + command = command + bgpAFArg2 + else: + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_with_mask", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None and bgpAFArg3 == "backdoor"): + command = command + bgpAFArg3 + elif(bgpAFArg3 is not None and bgpAFArg3 == "route-map"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + if(bgpAFArg5 is not None and bgpAFArg5 == "backdoor"): + command = command + bgpAFArg5 + else: + retVal = "Error-298" + return retVal + else: + retVal = "Error-196" + return retVal + else: + command = command.strip() + else: + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_value", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + if(bgpAFArg3 is not None and bgpAFArg3 == "backdoor"): + command = command + bgpAFArg3 + elif(bgpAFArg3 is not None and bgpAFArg3 == "route-map"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + if(bgpAFArg5 is not None and + bgpAFArg5 == "backdoor"): + command = command + bgpAFArg5 + else: + retVal = "Error-298" + return retVal + else: + retVal = "Error-196" + return retVal + elif(bgpAFArg3 is not None and bgpAFArg3 == "mask"): + command = command + bgpAFArg3 + value = cnos.checkSanityofVariable( + deviceType, "network_ip_prefix_mask", bgpAFArg4) + if(value == "ok"): + command = command + bgpAFArg4 + " " + else: + retVal = "Error-299" + return retVal + else: + command = command.strip() + else: + retVal = "Error-300" + return retVal + + elif(bgpAFArg1 == "nexthop"): + command = command + bgpAFArg1 + " trigger-delay critical " + value = cnos.checkSanityofVariable( + deviceType, "nexthop_crtitical_delay", bgpAFArg2) + if(value == "ok"): + command = command + bgpAFArg2 + " " + value = cnos.checkSanityofVariable( + deviceType, "nexthop_noncrtitical_delay", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + " " + else: + retVal = "Error-198" + return retVal + else: + retVal = "Error-197" + return retVal + + elif(bgpAFArg1 == "redistribute"): + command = command + bgpAFArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_redistribute_option", bgpAFArg2) + if(value == "ok"): + if(bgpAFArg2 is not None): + command = command + bgpAFArg2 + " " + "route-map " + value = cnos.checkSanityofVariable( + deviceType, "addrfamily_routemap_name", bgpAFArg3) + if(value == "ok"): + command = command + bgpAFArg3 + else: + retVal = "Error-196" + return retVal + else: + retVal = "Error-195" + return retVal + + elif(bgpAFArg1 == "save" or bgpAFArg1 == "synchronization"): + command = command + bgpAFArg1 + + else: + retVal = "Error-194" + return retVal + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + return retVal +# EOM + + +def bgpConfig(module, cmd, prompt, answer): + retVal = '' + command = '' + bgpArg1 = module.params['bgpArg1'] + bgpArg2 = module.params['bgpArg2'] + bgpArg3 = module.params['bgpArg3'] + bgpArg4 = module.params['bgpArg4'] + bgpArg5 = module.params['bgpArg5'] + bgpArg6 = module.params['bgpArg6'] + bgpArg7 = module.params['bgpArg7'] + bgpArg8 = module.params['bgpArg8'] + asNum = module.params['asNum'] + deviceType = module.params['deviceType'] + # cnos.debugOutput(bgpArg1) + if(bgpArg1 == "address-family"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "bgp_address_family", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + " " + "unicast \n" + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpAFConfig(module, cmd, prompt, answer) + return retVal + else: + retVal = "Error-178" + return retVal + + elif(bgpArg1 == "bestpath"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + if(bgpArg2 == "always-compare-med"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "compare-confed-aspath"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "compare-routerid"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "dont-compare-originator-id"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "tie-break-on-age"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + elif(bgpArg2 == "as-path"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + " " + if(bgpArg3 == "ignore" or bgpArg3 == "multipath-relax"): + command = command + bgpArg3 + else: + retVal = "Error-179" + return retVal + elif(bgpArg2 == "med"): + # debugOutput(bgpArg2) + command = command + bgpArg2 + " " + if(bgpArg3 == "confed" or + bgpArg3 == "missing-as-worst" or + bgpArg3 == "non-deterministic" or + bgpArg3 == "remove-recv-med" or + bgpArg3 == "remove-send-med"): + command = command + bgpArg3 + else: + retVal = "Error-180" + return retVal + else: + retVal = "Error-181" + return retVal + + elif(bgpArg1 == "bgp"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " as-local-count " + value = cnos.checkSanityofVariable( + deviceType, "bgp_bgp_local_count", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-182" + return retVal + + elif(bgpArg1 == "cluster-id"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "cluster_id_as_ip", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + value = cnos.checkSanityofVariable( + deviceType, "cluster_id_as_number", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-183" + return retVal + + elif(bgpArg1 == "confederation"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + if(bgpArg2 == "identifier"): + value = cnos.checkSanityofVariable( + deviceType, "confederation_identifier", bgpArg3) + if(value == "ok"): + command = command + bgpArg2 + " " + bgpArg3 + "\n" + else: + retVal = "Error-184" + return retVal + elif(bgpArg2 == "peers"): + value = cnos.checkSanityofVariable( + deviceType, "confederation_peers_as", bgpArg3) + if(value == "ok"): + command = command + bgpArg2 + " " + bgpArg3 + else: + retVal = "Error-185" + return retVal + else: + retVal = "Error-186" + return retVal + + elif(bgpArg1 == "enforce-first-as"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "fast-external-failover"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "graceful-restart"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " stalepath-time " + value = cnos.checkSanityofVariable( + deviceType, "stalepath_delay_value", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-187" + return retVal + + elif(bgpArg1 == "graceful-restart-helper"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "log-neighbor-changes"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "maxas-limit"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "maxas_limit_as", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-188" + return retVal + + elif(bgpArg1 == "neighbor"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "neighbor_ipaddress", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + if(bgpArg3 is not None): + command = command + " remote-as " + value = cnos.checkSanityofVariable( + deviceType, "neighbor_as", bgpArg3) + if(value == "ok"): + # debugOutput(command) + command = command + bgpArg3 + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + bgpNeighborConfig(module, cmd, prompt, answer) + return retVal + else: + retVal = "Error-189" + return retVal + + elif(bgpArg1 == "router-id"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "router_id", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-190" + return retVal + + elif(bgpArg1 == "shutdown"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "synchronization"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + + elif(bgpArg1 == "timers"): + # cnos.debugOutput(bgpArg3) + command = command + bgpArg1 + " bgp " + value = cnos.checkSanityofVariable( + deviceType, "bgp_keepalive_interval", bgpArg2) + if(value == "ok"): + command = command + bgpArg2 + else: + retVal = "Error-191" + return retVal + if(bgpArg3 is not None): + value = cnos.checkSanityofVariable(deviceType, "bgp_holdtime", bgpArg3) + if(value == "ok"): + command = command + " " + bgpArg3 + else: + retVal = "Error-192" + return retVal + else: + retVal = "Error-192" + return retVal + + elif(bgpArg1 == "vrf"): + # debugOutput(bgpArg1) + command = command + bgpArg1 + " default" + else: + # debugOutput(bgpArg1) + retVal = "Error-192" + return retVal + # debugOutput(command) + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + command = "exit \n" + # debugOutput(command) + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + bgpArg1=dict(required=True), + bgpArg2=dict(required=False), + bgpArg3=dict(required=False), + bgpArg4=dict(required=False), + bgpArg5=dict(required=False), + bgpArg6=dict(required=False), + bgpArg7=dict(required=False), + bgpArg8=dict(required=False), + asNum=dict(required=True),), + supports_check_mode=False) + + asNum = module.params['asNum'] + outputfile = module.params['outputfile'] + deviceType = module.params['deviceType'] + output = '' + command = 'router bgp ' + value = cnos.checkSanityofVariable(deviceType, "bgp_as_number", asNum) + if(value == "ok"): + # BGP command happens here. It creates if not present + command = command + asNum + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + bgpConfig(module, cmd, '(config)#', None) + else: + output = "Error-176" + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="BGP configurations accomplished") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_command.py new file mode 100644 index 00000000..d38c8ef7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_command.py @@ -0,0 +1,203 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute CNOS Commands on Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Run arbitrary commands on Lenovo CNOS devices +description: + - Sends arbitrary commands to an CNOS node and returns the results + read from the device. The C(cnos_command) module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +options: + commands: + description: + - List of commands to send to the remote device. + The resulting output from the command is returned. + If the I(wait_for) argument is provided, the module is not + returned until the condition is satisfied or the number of + retires is expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +--- +- name: Test contains operator + community.network.cnos_command: + commands: + - show version + - show system memory + wait_for: + - "result[0] contains 'Lenovo'" + - "result[1] contains 'MemFree'" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for single command + community.network.cnos_command: + commands: ['show version'] + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for multiple commands + community.network.cnos_command: + commands: + - show version + - show interface information + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + - "result.stdout | length == 2" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands, check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_command.py new file mode 100644 index 00000000..029a90b1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_command.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send Conditional CLI commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_conditional_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Execute a single command based on condition on devices + running Lenovo CNOS +description: + - This module allows you to modify the running configuration of a switch. It + provides a way to execute a single CNOS command on a network device by + evaluating the current running configuration and executing the command only + if the specific settings have not been already configured. + The CNOS command is passed as an argument of the method. + This module functions the same as the cnos_command module. + The only exception is that following inventory variable can be specified + ["condition = "] + When this inventory variable is specified as the variable of a task, the + command is executed for the network element that matches the flag string. + Usually, commands are executed across a group of network devices. When + there is a requirement to skip the execution of the command on one or + more devices, it is recommended to use this module. This module uses SSH to + manage network device configuration. +extends_documentation_fragment: +- community.network.cnos + +options: + clicommand: + description: + - This specifies the CLI command as an attribute to this method. + The command is passed using double quotes. The variables can be + placed directly on to the CLI commands or can be invoked + from the vars directory. + required: true + default: Null + condition: + description: + - If you specify condition=false in the inventory file against any + device, the command execution is skipped for that device. + required: true + default: Null + flag: + description: + - If a task needs to be executed, you have to set the flag the same + as it is specified in the inventory for that device. + required: true + default: Null + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module + cnos_conditional_command. These are written in the main.yml file of the tasks + directory. +--- +- name: Applying CLI template on VLAG Tier1 Leaf Switch1 + community.network.cnos_conditional_command: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_conditional_command_ + {{ inventory_hostname }}_output.txt" + condition: "{{ hostvars[inventory_hostname]['condition']}}" + flag: leaf_switch2 + command: "spanning-tree mode enable" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Command Applied" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + clicommand=dict(required=True), + outputfile=dict(required=True), + condition=dict(required=True), + flag=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, + no_log=True), ), supports_check_mode=False) + + condition = module.params['condition'] + flag = module.params['flag'] + cliCommand = module.params['clicommand'] + outputfile = module.params['outputfile'] + output = '' + if (condition is None or condition != flag): + module.exit_json(changed=True, msg="Command Skipped for this switch") + return '' + # Send the CLi command + cmd = [{'command': cliCommand, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="CLI Command executed and results saved in file ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_template.py new file mode 100644 index 00000000..f9d675ce --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_conditional_template.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send conditional template to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_conditional_template +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage switch configuration using templates based on + condition on devices running Lenovo CNOS +description: + - This module allows you to work with the running configuration of a + switch. It provides a way to execute a set of CNOS commands on a switch by + evaluating the current running configuration and executing the commands + only if the specific settings have not been already configured. + The configuration source can be a set of commands or a template written in + the Jinja2 templating language. This module functions the same as the + cnos_template module. The only exception is that the following inventory + variable can be specified. + ["condition = "] + When this inventory variable is specified as the variable of a task, the + template is executed for the network element that matches the flag string. + Usually, templates are used when commands are the same across a group of + network devices. When there is a requirement to skip the execution of the + template on one or more devices, it is recommended to use this module. + This module uses SSH to manage network device configuration. +extends_documentation_fragment: +- community.network.cnos + +options: + commandfile: + description: + - This specifies the path to the CNOS command file which needs to + be applied. This usually comes from the commands folder. Generally + this file is the output of the variables applied on a template + file. So this command is preceded by a template module. The + command file must contain the Ansible keyword + {{ inventory_hostname }} and the condition flag in its filename to + ensure that the command file is unique for each switch and + condition. If this is omitted, the command file will be + overwritten during iteration. For example, + commandfile=./commands/clos_leaf_bgp_ + {{ inventory_hostname }}_LP21_commands.txt + required: true + default: Null + condition: + description: + - If you specify condition= in the inventory file + against any device, the template execution is done for that device + in case it matches the flag setting for that task. + required: true + default: Null + flag: + description: + - If a task needs to be executed, you have to set the flag the same + as it is specified in the inventory for that device. + required: true + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module + cnos_conditional_template. These are written in the main.yml file of the + tasks directory. +--- +- name: Applying CLI template on VLAG Tier1 Leaf Switch1 + community.network.cnos_conditional_template: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/vlag_1tier_leaf_switch1_ + {{ inventory_hostname }}_output.txt" + condition: "{{ hostvars[inventory_hostname]['condition']}}" + flag: "leaf_switch1" + commandfile: "./commands/vlag_1tier_leaf_switch1_ + {{ inventory_hostname }}_commands.txt" + stp_mode1: "disable" + port_range1: "17,18,29,30" + portchannel_interface_number1: 1001 + portchannel_mode1: active + slot_chassis_number1: 1/48 + switchport_mode1: trunk +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Template Applied." +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + commandfile=dict(required=True), + outputfile=dict(required=True), + condition=dict(required=True), + flag=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + condition = module.params['condition'] + flag = module.params['flag'] + commandfile = module.params['commandfile'] + outputfile = module.params['outputfile'] + + output = '' + if (condition is None or condition != flag): + module.exit_json(changed=True, msg="Template Skipped for this switch") + return " " + # Send commands one by one + f = open(commandfile, "r") + cmd = [] + for line in f: + # Omit the comment lines in template file + if not line.startswith("#"): + # cnos.debugOutput(line) + command = line.strip() + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write output to file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Template Applied") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_config.py new file mode 100644 index 00000000..e32deb7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_config.py @@ -0,0 +1,302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to configure Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_config +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Lenovo CNOS configuration sections +description: + - Lenovo CNOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with CNOS configuration sections in + a deterministic way. +notes: + - Tested against CNOS 10.9.1 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is + mutually exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block', 'config'] + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by cnos_config' + admin: + description: + - Enters into administration configuration mode for making config + changes to the device. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +Tasks: The following are examples of using the module cnos_config. +--- +- name: Configure top level configuration + community.network.cnos_config: + "lines: hostname {{ inventory_hostname }}" + +- name: Configure interface settings + community.network.cnos_config: + lines: + - enable + - ip ospf enable + parents: interface ip 13 + +- name: Load a config from disk and replace the current config + community.network.cnos_config: + src: config.cfg + backup: yes + +- name: Configurable backup path + community.network.cnos_config: + src: config.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/cnos01.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, get_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +DEFAULT_COMMIT_COMMENT = 'configured by cnos_config' + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + replace_config = replace == 'config' + path = module.params['parents'] + comment = module.params['comment'] + admin = module.params['admin'] + check_mode = module.check_mode + + candidate = get_candidate(module) + + if match != 'none' and replace != 'config': + contents = get_running_config(module) + configobj = NetworkConfig(contents=contents, indent=1) + commands = candidate.difference(configobj, path=path, match=match, + replace=replace) + else: + commands = candidate.items + + if commands: + commands = dumps(commands, 'commands').split('\n') + + if any((module.params['lines'], module.params['src'])): + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + + diff = load_config(module, commands) + if diff: + result['diff'] = dict(prepared=diff) + result['changed'] = True + + +def main(): + """main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', + 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block', 'config']), + + config=dict(), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + comment=dict(default=DEFAULT_COMMIT_COMMENT), + admin=dict(type='bool', default=False) + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('replace', 'config', ['src'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_factory.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_factory.py new file mode 100644 index 00000000..606bd8fb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_factory.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Reset to factory settings of Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_factory +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Reset the switch startup configuration to default (factory) + on devices running Lenovo CNOS. +description: + - This module allows you to reset a switch's startup configuration. The + method provides a way to reset the startup configuration to its factory + settings. This is helpful when you want to move the switch to another + topology as a new network device. This module uses SSH to manage network + device configuration. The result of the operation can be viewed in results + directory. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_reload. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Reset to factory + community.network.cnos_factory: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_factory_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Switch Startup Config is Reset to factory settings" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'write erase' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': '[n]', 'answer': 'y'}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Switch Startup Config is Reset to Factory settings") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_facts.py new file mode 100644 index 00000000..a036b63b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_facts.py @@ -0,0 +1,535 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2019 Red Hat Inc. +# Copyright (C) 2019 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Module to Collect facts from Lenovo Switches running Lenovo CNOS commands +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_facts +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect facts from remote devices running Lenovo CNOS +description: + - Collects a base set of device facts from a remote Lenovo device + running on CNOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against CNOS 10.8.1 +options: + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module cnos_facts. +--- +- name: Test cnos Facts + community.network.cnos_facts: + +--- +# Collect all facts from the device +- community.network.cnos_facts: + gather_subset: all + +# Collect only the config and default facts +- community.network.cnos_facts: + gather_subset: + - config + +# Do not collect hardware facts +- community.network.cnos_facts: + gather_subset: + - "!hardware" +''' +RETURN = ''' + ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +# default + ansible_net_model: + description: The model name returned from the Lenovo CNOS device + returned: always + type: str + ansible_net_serialnum: + description: The serial number of the Lenovo CNOS device + returned: always + type: str + ansible_net_version: + description: The CNOS operating system version running on the remote device + returned: always + type: str + ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + ansible_net_image: + description: Indicates the active image for the device + returned: always + type: str +# hardware + ansible_net_memfree_mb: + description: The available free memory on the remote device in MB + returned: when hardware is configured + type: int +# config + ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces + ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_interfaces: + description: A hash of all interfaces running on the system. + This gives information on description, mac address, mtu, speed, + duplex and operstatus + returned: when interfaces is configured + type: dict + ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +''' + +import re + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self.PERSISTENT_COMMAND_TIMEOUT = 60 + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, + check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show sys-info', 'show running-config'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + data_run = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + if data_run: + self.facts['hostname'] = self.parse_hostname(data_run) + + def parse_version(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Software Revision (.*?)', + line, re.M | re.I) + if match: + vers = line.split(':') + ver = vers[1].strip() + return ver + return "NA" + + def parse_hostname(self, data_run): + for line in data_run.split('\n'): + line = line.strip() + match = re.match(r'hostname (.*?)', line, re.M | re.I) + if match: + hosts = line.split() + hostname = hosts[1].strip('\"') + return hostname + return "NA" + + def parse_model(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Model (.*?)', line, re.M | re.I) + if match: + mdls = line.split(':') + mdl = mdls[1].strip() + return mdl + return "NA" + + def parse_image(self, data): + match = re.search(r'(.*) image(.*)', data, re.M | re.I) + if match: + return "Image1" + else: + return "Image2" + + def parse_serialnum(self, data): + for line in data.split('\n'): + line = line.strip() + match = re.match(r'System Serial Number (.*?)', line, re.M | re.I) + if match: + serNums = line.split(':') + ser = serNums[1].strip() + return ser + return "NA" + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show running-config' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.run(['show process memory']) + data = to_text(data, errors='surrogate_or_strict').strip() + data = data.replace(r"\n", "\n") + if data: + for line in data.split('\n'): + line = line.strip() + match = re.match(r'Mem: (.*?)', line, re.M | re.I) + if match: + memline = line.split(':') + mems = memline[1].strip().split() + self.facts['memtotal_mb'] = int(mems[0]) / 1024 + self.facts['memused_mb'] = int(mems[1]) / 1024 + self.facts['memfree_mb'] = int(mems[2]) / 1024 + self.facts['memshared_mb'] = int(mems[3]) / 1024 + self.facts['memavailable_mb'] = int(mems[5]) / 1024 + + def parse_memtotal(self, data): + match = re.search(r'^MemTotal:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + def parse_memfree(self, data): + match = re.search(r'^MemFree:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interface brief'] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data1 = self.run(['show interface status']) + data1 = to_text(data1, errors='surrogate_or_strict').strip() + data1 = data1.replace(r"\n", "\n") + data2 = self.run(['show interface mac-address']) + data2 = to_text(data2, errors='surrogate_or_strict').strip() + data2 = data2.replace(r"\n", "\n") + lines1 = None + lines2 = None + if data1: + lines1 = self.parse_interfaces(data1) + if data2: + lines2 = self.parse_interfaces(data2) + if lines1 is not None and lines2 is not None: + self.facts['interfaces'] = self.populate_interfaces(lines1, lines2) + data3 = self.run(['show lldp neighbors']) + data3 = to_text(data3, errors='surrogate_or_strict').strip() + data3 = data3.replace(r"\n", "\n") + if data3: + lines3 = self.parse_neighbors(data3) + if lines3 is not None: + self.facts['neighbors'] = self.populate_neighbors(lines3) + + data4 = self.run(['show ip interface brief vrf all']) + data5 = self.run(['show ipv6 interface brief vrf all']) + data4 = to_text(data4, errors='surrogate_or_strict').strip() + data4 = data4.replace(r"\n", "\n") + data5 = to_text(data5, errors='surrogate_or_strict').strip() + data5 = data5.replace(r"\n", "\n") + lines4 = None + lines5 = None + if data4: + lines4 = self.parse_ipaddresses(data4) + ipv4_interfaces = self.set_ip_interfaces(lines4) + self.facts['all_ipv4_addresses'] = ipv4_interfaces + if data5: + lines5 = self.parse_ipaddresses(data5) + ipv6_interfaces = self.set_ipv6_interfaces(lines5) + self.facts['all_ipv6_addresses'] = ipv6_interfaces + + def parse_ipaddresses(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^(Ethernet+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(po+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(mgmt+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(loopback+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def populate_interfaces(self, lines1, lines2): + interfaces = dict() + for line1, line2 in zip(lines1, lines2): + line = line1 + " " + line2 + intfSplit = line.split() + innerData = dict() + innerData['description'] = intfSplit[1].strip() + innerData['macaddress'] = intfSplit[8].strip() + innerData['type'] = intfSplit[6].strip() + innerData['speed'] = intfSplit[5].strip() + innerData['duplex'] = intfSplit[4].strip() + innerData['operstatus'] = intfSplit[2].strip() + interfaces[intfSplit[0].strip()] = innerData + return interfaces + + def parse_interfaces(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^(Ethernet+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(po+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(mgmt+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def set_ip_interfaces(self, line4): + ipv4_addresses = list() + for line in line4: + ipv4Split = line.split() + if 'Ethernet' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'mgmt' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'po' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + if 'loopback' in ipv4Split[0]: + ipv4_addresses.append(ipv4Split[1]) + return ipv4_addresses + + def set_ipv6_interfaces(self, line4): + ipv6_addresses = list() + for line in line4: + ipv6Split = line.split() + if 'Ethernet' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'mgmt' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'po' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + if 'loopback' in ipv6Split[0]: + ipv6_addresses.append(ipv6Split[1]) + return ipv6_addresses + + def populate_neighbors(self, lines3): + neighbors = dict() + device_name = '' + for line in lines3: + neighborSplit = line.split() + innerData = dict() + count = len(neighborSplit) + if count == 5: + local_interface = neighborSplit[1].strip() + innerData['Device Name'] = neighborSplit[0].strip() + innerData['Hold Time'] = neighborSplit[2].strip() + innerData['Capability'] = neighborSplit[3].strip() + innerData['Remote Port'] = neighborSplit[4].strip() + neighbors[local_interface] = innerData + elif count == 4: + local_interface = neighborSplit[0].strip() + innerData['Hold Time'] = neighborSplit[1].strip() + innerData['Capability'] = neighborSplit[2].strip() + innerData['Remote Port'] = neighborSplit[3].strip() + neighbors[local_interface] = innerData + return neighbors + + def parse_neighbors(self, neighbors): + parsed = list() + for line in neighbors.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + if 'Ethernet' in line: + parsed.append(line) + if 'mgmt' in line: + parsed.append(line) + if 'po' in line: + parsed.append(line) + if 'loopback' in line: + parsed.append(line) + return parsed + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +PERSISTENT_COMMAND_TIMEOUT = 60 + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + check_args(module, warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_image.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_image.py new file mode 100644 index 00000000..ee9c2cda --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_image.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to download new image to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_image +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Perform firmware upgrade/download from a remote server on + devices running Lenovo CNOS +description: + - This module allows you to work with switch firmware images. It provides a + way to download a firmware image to a network device from a remote server + using FTP, SFTP, TFTP, or SCP. The first step is to create a directory + from where the remote server can be reached. The next step is to provide + the full file path of the image's location. Authentication details + required by the remote server must be provided as well. By default, this + method makes the newly downloaded firmware image the active image, which + will be used by the switch during the next restart. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server from where to download the + firmware image. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is not specified + there is no default value to be used. + required: true + choices: [SFTP, SCP, FTP, TFTP] + serverip: + description: + - This specifies the IP Address of the remote server from where the + software image will be downloaded. + required: true + imgpath: + description: + - This specifies the full file path of the image located on the + remote server. In case the relative path is used as the variable + value, the root folder for the user of the server needs to be + specified. + required: true + imgtype: + description: + - This specifies the firmware image type to be downloaded + required: true + choices: [all, boot, os, onie] + serverusername: + description: + - Specify the username for the server relating to the protocol used + required: true + serverpassword: + description: + - Specify the password for the server relating to the protocol used +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_image. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Image transfer + community.network.cnos_image: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt" + protocol: "sftp" + serverip: "10.241.106.118" + imgpath: "/root/cnos_images/G8272-10.1.0.112.img" + imgtype: "os" + serverusername: "root" + serverpassword: "root123" + +- name: Test Image tftp + community.network.cnos_image: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt" + protocol: "tftp" + serverip: "10.241.106.118" + imgpath: "/anil/G8272-10.2.0.34.img" + imgtype: "os" + serverusername: "root" + serverpassword: "root123" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Image file transferred to device" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def doImageDownload(module, prompt, answer): + protocol = module.params['protocol'].lower() + server = module.params['serverip'] + imgPath = module.params['imgpath'] + imgType = module.params['imgtype'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + retVal = '' + command = "copy " + protocol + " " + protocol + "://" + username + "@" + command = command + server + "/" + imgPath + " system-image " + command = command + imgType + " vrf management" + cmd = [] + if(protocol == "scp"): + prompt = ['timeout', 'Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['240', 'y', password, 'y'] + scp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(scp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + prompt = ['Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['y', password, 'y'] + sftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(sftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + prompt = ['Confirm download operation', 'Password', + 'Do you want to change that to the standby image'] + answer = ['y', password, 'y'] + ftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(ftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + protocol + " " + protocol + "://" + server + command = command + "/" + imgPath + " system-image " + imgType + command = command + " vrf management" + prompt = ['Confirm download operation', + 'Do you want to change that to the standby image'] + answer = ['y', 'y'] + tftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer, + 'check_all': True}] + cmd.extend(tftp_cmd) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + imgpath=dict(required=True), + imgtype=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + + # Invoke method for image transfer from server + if(protocol == "tftp" or protocol == "ftp" or protocol == "sftp" or + protocol == "scp"): + transfer_status = doImageDownload(module, None, None) + else: + transfer_status = "Invalid Protocol option" + + output = output + "\n Image Transfer status \n" + transfer_status + + # Save it into the file + path = outputfile.rsplit('/', 1) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Image file transferred to device") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_interface.py new file mode 100644 index 00000000..57e07dde --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_interface.py @@ -0,0 +1,550 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Interfaces with Lenovo Switches +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_interface +author: "Anil Kumar Muraleedharan(@amuraleedhar)" +short_description: Manage Interface on Lenovo CNOS network devices +description: + - This module provides declarative management of Interfaces + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the Interface. + required: true + description: + description: + - Description of Interface. + enabled: + description: + - Interface link status. + type: bool + default: True + speed: + description: + - Interface link speed. + mtu: + description: + - Maximum size of transmit packet. + duplex: + description: + - Interface link status + default: auto + choices: ['full', 'half', 'auto'] + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules, + ../network/user_guide/network_working_with_command_output.html) + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules, + ../network/user_guide/network_working_with_command_output.html) + neighbors: + description: + - Check operational state of given interface C(name) for LLDP neighbor. + - The following suboptions are available. + suboptions: + host: + description: + - "LLDP neighbor host for given interface C(name)." + port: + description: + - "LLDP neighbor port to which interface C(name) is connected." + aggregate: + description: List of Interfaces definitions. + delay: + description: + - Time in seconds to wait before checking for the operational state on + remote device. This wait is applicable for operational state argument + which are I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate) + default: 20 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + choices: ['present', 'absent', 'up', 'down'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Configure interface + community.network.cnos_interface: + name: Ethernet1/33 + description: test-interface + speed: 100 + duplex: half + mtu: 999 + +- name: Remove interface + community.network.cnos_interface: + name: loopback3 + state: absent + +- name: Make interface up + community.network.cnos_interface: + name: Ethernet1/33 + enabled: True + +- name: Make interface down + community.network.cnos_interface: + name: Ethernet1/33 + enabled: False + +- name: Check intent arguments + community.network.cnos_interface: + name: Ethernet1/33 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.cnos_interface: + name: Ethernet1/33 + neighbors: + - port: eth0 + host: netdev + +- name: Config + intent + community.network.cnos_interface: + name: Ethernet1/33 + enabled: False + state: down + +- name: Add interface using aggregate + community.network.cnos_interface: + aggregate: + - { name: Ethernet1/33, mtu: 256, description: test-interface-1 } + - { name: Ethernet1/44, mtu: 516, description: test-interface-2 } + duplex: full + speed: 100 + state: present + +- name: Delete interface using aggregate + community.network.cnos_interface: + aggregate: + - name: loopback3 + - name: loopback6 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/33 + - description test-interface + - duplex half + - mtu 512 +""" +import re + +from copy import deepcopy +from time import sleep + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec + + +def validate_mtu(value, module): + if value and not 64 <= int(value) <= 9216: + module.fail_json(msg='mtu must be between 64 and 9216') + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_shutdown(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^shutdown', cfg, re.M) + if match: + return True + else: + return False + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'description': parse_config_argument(configobj, item, 'description'), + 'speed': parse_config_argument(configobj, item, 'speed'), + 'duplex': parse_config_argument(configobj, item, 'duplex'), + 'mtu': parse_config_argument(configobj, item, 'mtu'), + 'disable': True if parse_shutdown(configobj, item) else False, + 'state': 'present' + } + instances.append(obj) + return instances + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'description': module.params['description'], + 'speed': module.params['speed'], + 'mtu': module.params['mtu'], + 'duplex': module.params['duplex'], + 'state': module.params['state'], + 'delay': module.params['delay'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + + validate_param_values(module, params) + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + + args = ('speed', 'description', 'duplex', 'mtu') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + if state == 'absent' and obj_in_have: + commands.append('no ' + interface) + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate != running: + if candidate: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'shutdown', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'no shutdown', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('no shutdown') + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors_lldp = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interface %s brief' % w['name'] + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + if want_state in ('up', 'down'): + state_data = out.strip().lower().split(w['name']) + have_state = None + have_state = state_data[1].split()[3] + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + command = 'show interface %s' % w['name'] + rc, out, err = exec_command(module, command) + have_tx_rate = None + have_rx_rate = None + rates = out.splitlines() + for s in rates: + s = s.strip() + if 'output rate' in s and 'input rate' in s: + sub = s.split() + if want_tx_rate: + have_tx_rate = sub[8] + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + if want_rx_rate: + have_rx_rate = sub[2] + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + if want_neighbors: + have_host = [] + have_port = [] + + # 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_lldp: + lines = have_neighbors_lldp.strip().split('Local Port ID: ') + for line in lines: + field = line.split('\n') + if field[0].strip() == w['name']: + for item in field: + if item.startswith('System Name:'): + have_host.append(item.split(':')[1].strip()) + if item.startswith('Port Description:'): + have_port.append(item.split(':')[1].strip()) + + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + neighbors_spec = dict( + host=dict(), + port=dict() + ) + + element_spec = dict( + name=dict(), + description=dict(), + speed=dict(), + mtu=dict(), + duplex=dict(default='auto', choices=['full', 'half', 'auto']), + enabled=dict(default=True, type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=20, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l2_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l2_interface.py new file mode 100644 index 00000000..9693685f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l2_interface.py @@ -0,0 +1,593 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send banner commands to Lenovo Switches +# Two types of banners are supported login and motd +# Lenovo Networking +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_l2_interface +short_description: Manage Layer-2 interface on Lenovo CNOS devices. +description: + - This module provides declarative management of Layer-2 interfaces on + Lenovo CNOS devices. +author: + - Anil Kumar Muraleedharan (@amuraleedhar) +options: + name: + description: + - Full name of the interface excluding any logical + unit number, i.e. Ethernet1/3. + required: true + aliases: ['interface'] + mode: + description: + - Mode in which interface needs to be configured. + default: access + choices: ['access', 'trunk'] + access_vlan: + description: + - Configure given VLAN in access port. + If C(mode=access), used as the access VLAN ID. + trunk_vlans: + description: + - List of VLANs to be configured in trunk port. + If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk. + native_vlan: + description: + - Native VLAN to be configured in trunk port. + If C(mode=trunk), used as the trunk native VLAN ID. + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. + If C(mode=trunk), these are the only VLANs that will be + configured on the trunk, i.e. "2-10,15". + aggregate: + description: + - List of Layer-2 interface definitions. + state: + description: + - Manage the state of the Layer-2 Interface configuration. + default: present + choices: ['present','absent', 'unconfigured'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Ensure Ethernet1/5 is in its default l2 interface state + community.network.cnos_l2_interface: + name: Ethernet1/5 + state: unconfigured + +- name: Ensure Ethernet1/5 is configured for access vlan 20 + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: access + access_vlan: 20 + +- name: Ensure Ethernet1/5 only has vlans 5-10 as trunk vlans + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 5-10 + +- name: Ensure Ethernet1/5 is a trunk port and ensure 2-50 are being tagged + (doesn't mean others aren't also being tagged) + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 2-50 + +- name: Ensure these VLANs are not being tagged on the trunk + community.network.cnos_l2_interface: + name: Ethernet1/5 + mode: trunk + trunk_vlans: 51-4094 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/5 + - switchport access vlan 20 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def interface_is_portchannel(name, module): + if get_interface_type(name) == 'ethernet': + config = run_commands(module, ['show run interface {0}'.format(name)])[0] + if any(c in config for c in ['channel group', 'channel-group']): + return True + return False + + +def get_switchport(name, module): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + mode = re.search(r'Switchport mode : (?:.* )?(\w+)$', config, re.M) + access = re.search(r'Configured Vlans : (\d+)', config) + native = re.search(r'Default/Native Vlan : (\d+)', config) + trunk = re.search(r'Enabled Vlans : (.+)$', config, re.M) + if mode: + mode = mode.group(1) + if access: + access = access.group(1) + if native: + native = native.group(1) + if trunk: + trunk = trunk.group(1) + if trunk == 'ALL': + trunk = '1-4094' + + switchport_config = { + "interface": name, + "mode": mode, + "access_vlan": access, + "native_vlan": native, + "trunk_vlans": trunk, + } + + return switchport_config + + +def remove_switchport_config_commands(name, existing, proposed, module): + mode = proposed.get('mode') + commands = [] + command = None + + if mode == 'access': + av_check = existing.get('access_vlan') == proposed.get('access_vlan') + if av_check: + command = 'no switchport access vlan' + commands.append(command) + + elif mode == 'trunk': + # 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}' + command = command.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'): + command = 'no switchport trunk native vlan' + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def get_switchport_config_commands(name, existing, proposed, module): + """Gets commands required to config a given switchport interface + """ + + proposed_mode = proposed.get('mode') + existing_mode = existing.get('mode') + commands = [] + command = None + + if proposed_mode != existing_mode: + if proposed_mode == 'trunk': + command = 'switchport mode trunk' + elif proposed_mode == 'access': + command = 'switchport mode access' + + if command: + commands.append(command) + + if proposed_mode == 'access': + av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan')) + if not av_check: + command = 'switchport access vlan {0}'.format(proposed.get('access_vlan')) + commands.append(command) + + elif proposed_mode == 'trunk': + tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list') + + if not tv_check: + if proposed.get('allowed'): + command = 'switchport trunk allowed vlan {0}' + command = command.format(proposed.get('trunk_allowed_vlans')) + commands.append(command) + + else: + existing_vlans = existing.get('trunk_vlans_list') + proposed_vlans = proposed.get('trunk_vlans_list') + vlans_to_add = set(proposed_vlans).difference(existing_vlans) + if vlans_to_add: + command = 'switchport trunk allowed vlan add {0}' + command = command.format(proposed.get('trunk_vlans')) + commands.append(command) + + native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan')) + if not native_check and proposed.get('native_vlan'): + command = 'switchport trunk native vlan {0}' + command = command.format(proposed.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def is_switchport_default(existing): + """Determines if switchport has a default config based on mode + Args: + existing (dict): existing switchport configuration from Ansible mod + Returns: + boolean: True if switchport has OOB Layer 2 config, i.e. + vlan 1 and trunk all and mode is access + """ + + c1 = str(existing['access_vlan']) == '1' + c2 = str(existing['native_vlan']) == '1' + c3 = existing['trunk_vlans'] == '1-4094' + c4 = existing['mode'] == 'access' + + default = c1 and c2 and c3 and c4 + + return default + + +def default_switchport_config(name): + commands = [] + commands.append('interface ' + name) + commands.append('switchport mode access') + commands.append('switch access vlan 1') + commands.append('switchport trunk native vlan 1') + commands.append('switchport trunk allowed vlan all') + return commands + + +def vlan_range_to_list(vlans): + result = [] + if vlans: + for part in vlans.split(','): + if part.lower() == 'none': + break + if part: + if '-' in part: + start, stop = (int(i) for i in part.split('-')) + result.extend(range(start, stop + 1)) + else: + result.append(int(part)) + return sorted(result) + + +def get_list_of_vlans(module): + config = run_commands(module, ['show vlan'])[0] + vlans = set() + + lines = config.strip().splitlines() + for line in lines: + line_parts = line.split() + if line_parts: + try: + int(line_parts[0]) + except ValueError: + continue + vlans.add(line_parts[0]) + + return list(vlans) + + +def flatten_list(commands): + flat_list = [] + for command in commands: + if isinstance(command, list): + flat_list.extend(command) + else: + flat_list.append(command) + return flat_list + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'mode': module.params['mode'], + 'access_vlan': module.params['access_vlan'], + 'native_vlan': module.params['native_vlan'], + 'trunk_vlans': module.params['trunk_vlans'], + 'trunk_allowed_vlans': module.params['trunk_allowed_vlans'], + 'state': module.params['state'] + }) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(type='str', aliases=['interface']), + mode=dict(choices=['access', 'trunk'], default='access'), + access_vlan=dict(type='str'), + native_vlan=dict(type='str'), + trunk_vlans=dict(type='str'), + trunk_allowed_vlans=dict(type='str'), + state=dict(choices=['absent', 'present', 'unconfigured'], + default='present') + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=[['access_vlan', 'trunk_vlans'], + ['access_vlan', 'native_vlan'], + ['access_vlan', 'trunk_allowed_vlans']], + supports_check_mode=True) + + warnings = list() + commands = [] + result = {'changed': False, 'warnings': warnings} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + mode = w['mode'] + access_vlan = w['access_vlan'] + state = w['state'] + trunk_vlans = w['trunk_vlans'] + native_vlan = w['native_vlan'] + trunk_allowed_vlans = w['trunk_allowed_vlans'] + + args = dict(name=name, mode=mode, access_vlan=access_vlan, + native_vlan=native_vlan, trunk_vlans=trunk_vlans, + trunk_allowed_vlans=trunk_allowed_vlans) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + name = name.lower() + + if mode == 'access' and state == 'present' and not access_vlan: + msg = 'access_vlan param required for mode=access && state=present' + module.fail_json(msg=msg) + + if mode == 'trunk' and access_vlan: + msg = 'access_vlan param not supported when using mode=trunk' + module.fail_json(msg=msg) + + if not is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L2' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + + if interface_is_portchannel(name, module): + module.fail_json(msg='Cannot change L2 config on physical ' + '\nport because it is in a portchannel. ' + '\nYou should update the portchannel config.') + + # existing will never be null for Eth intfs as there is always a default + existing = get_switchport(name, module) + + # Safeguard check + # If there isn't an existing, something is wrong per previous comment + if not existing: + msg = 'Make sure you are using the FULL interface name' + module.fail_json(msg=msg) + + if trunk_vlans or trunk_allowed_vlans: + if trunk_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_vlans) + elif trunk_allowed_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans) + proposed['allowed'] = True + + existing_trunks_list = vlan_range_to_list((existing['trunk_vlans'])) + + existing['trunk_vlans_list'] = existing_trunks_list + proposed['trunk_vlans_list'] = trunk_vlans_list + + current_vlans = get_list_of_vlans(module) + + if state == 'present': + if access_vlan and access_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=access_vlan) + elif native_vlan and native_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN on' + ' an interface that\ndoes not exist on the ' + ' switch yet!', vlan=native_vlan) + else: + command = get_switchport_config_commands(name, existing, + proposed, module) + commands.append(command) + elif state == 'unconfigured': + is_default = is_switchport_default(existing) + if not is_default: + command = default_switchport_config(name) + commands.append(command) + elif state == 'absent': + command = remove_switchport_config_commands(name, existing, + proposed, module) + commands.append(command) + + if trunk_vlans or trunk_allowed_vlans: + existing.pop('trunk_vlans_list') + proposed.pop('trunk_vlans_list') + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + result['changed'] = True + load_config(module, cmds) + if 'configure' in cmds: + cmds.pop(0) + + result['commands'] = cmds + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l3_interface.py new file mode 100644 index 00000000..76296ad7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_l3_interface.py @@ -0,0 +1,457 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2019, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_l3_interface +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Layer-3 interfaces on Lenovo CNOS network devices. +description: + - This module provides declarative management of Layer-3 interfaces + on CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the Layer-3 interface to be configured eg. Ethernet1/2 + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) + option. The address format is /, the mask is number + in range 0-32 eg. 10.241.107.1/24 + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) + option. The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64 + aggregate: + description: + - List of Layer-3 interfaces definitions. Each of the entry in aggregate + list should define name of interface C(name) and a optional C(ipv4) or + C(ipv6) address. + state: + description: + - State of the Layer-3 interface configuration. It indicates if the + configuration should be present or absent on remote device. + default: present + choices: ['present', 'absent'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using + C(connection: network_cli)." + - For more information please see the + L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the + remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used + instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used + instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network + device for either connecting or sending commands. If the timeout + is exceeded before the operation is completed, the module will + error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not + specified in the task, the value of environment variable + C(ANSIBLE_NET_SSH_KEYFILE)will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the + value is not specified in the task, the value of environment + variable C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value + of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used + instead. +''' + +EXAMPLES = """ +- name: Remove Ethernet1/33 IPv4 and IPv6 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + state: absent + +- name: Set Ethernet1/33 IPv4 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv4: 10.241.107.1/24 + +- name: Set Ethernet1/33 IPv6 address + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set Ethernet1/33 in dhcp + community.network.cnos_l3_interface: + name: Ethernet1/33 + ipv4: dhcp + ipv6: dhcp + +- name: Set interface Vlan1 (SVI) IPv4 address + community.network.cnos_l3_interface: + name: Vlan1 + ipv4: 192.168.0.5/24 + +- name: Set IP addresses on aggregate + community.network.cnos_l3_interface: + aggregate: + - { name: Ethernet1/33, ipv4: 10.241.107.1/24 } + - { name: Ethernet1/44, ipv4: 10.240.106.1/24, + ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.cnos_l3_interface: + aggregate: + - { name: Ethernet1/33, ipv4: 10.241.107.1/24 } + - { name: Ethernet1/44, ipv4: 10.240.106.1/24, + ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface Ethernet1/33 + - ip address 10.241.107.1 255.255.255.0 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" +import re + +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json( + msg='address format is /,got invalid format %s' % value) + if not is_masklen(address[1]): + module.fail_json( + msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json( + msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json( + msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + + values = [] + matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M) + for match in matches: + match_str = match.group(1).strip() + if arg == 'ipv6 address': + values.append(match_str) + else: + values = match_str + break + + return values or None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'].lower() == name.lower(): + return o + + return None + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format( + address[0], to_netmask(address[1])) + commands.append('no ip address %s' % ipv4) + else: + commands.append('no ip address') + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address %s' % ipv6) + else: + commands.append('no ipv6 address') + if 'dhcp' in obj_in_have['ipv6']: + commands.append('no ipv6 address dhcp') + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format( + address[0], to_netmask(address[1])) + commands.append('ip address %s' % ipv4) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]: + commands.append('ipv6 address %s' % ipv6) + if commands[-1] == interface: + commands.pop(-1) + + return commands + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + # eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24 + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1]))) + + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'] + }) + + validate_param_values(module, obj) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + name = name.lower() + if is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L3' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + resp = load_config(module, commands) + if resp is not None: + warnings.extend((out for out in resp if out)) + + result['changed'] = True + + if warnings: + result['warnings'] = warnings + if 'overlaps with address configured on' in warnings[0]: + result['failed'] = True + result['msg'] = warnings[0] + if 'Cannot set overlapping address' in warnings[0]: + result['failed'] = True + result['msg'] = warnings[0] + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_linkagg.py new file mode 100644 index 00000000..0e1c1513 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_linkagg.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_linkagg +author: "Anil Kumar Muraleedharan (@auraleedhar)" +short_description: Manage link aggregation groups on Lenovo CNOS devices +description: + - This module provides declarative management of link aggregation groups + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255. + mode: + description: + - Mode of the link aggregation group. + choices: ['active', 'on', 'passive'] + members: + description: + - List of members of the link aggregation group. + aggregate: + description: List of link aggregation definitions. + state: + description: + - State of the link aggregation group. + default: present + choices: ['present', 'absent'] + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: no + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Create link aggregation group + community.network.cnos_linkagg: + group: 10 + state: present + +- name: Delete link aggregation group + community.network.cnos_linkagg: + group: 10 + state: absent + +- name: Set link aggregation group to members + community.network.cnos_linkagg: + group: 200 + mode: active + members: + - Ethernet1/33 + - Ethernet1/44 + +- name: Remove link aggregation group from GigabitEthernet0/0 + community.network.cnos_linkagg: + group: 200 + mode: active + members: + - Ethernet1/33 + +- name: Create aggregate of linkagg definitions + community.network.cnos_linkagg: + aggregate: + - { group: 3, mode: on, members: [Ethernet1/33] } + - { group: 100, mode: passive, members: [Ethernet1/44] } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - interface port-channel 30 + - interface Ethernet1/33 + - channel-group 30 mode on + - no interface port-channel 30 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + group = w['group'] + mode = w['mode'] + members = w.get('members') or [] + state = w['state'] + del w['state'] + + obj_in_have = search_obj_in_list(group, have) + + if state == 'absent': + if obj_in_have: + commands.append('no interface port-channel {0}'.format(group)) + + elif state == 'present': + cmd = ['interface port-channel {0}'.format(group), + 'exit'] + if not obj_in_have: + if not group: + module.fail_json(msg='group is a required option') + commands.extend(cmd) + + if members: + for m in members: + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + else: + if members: + if 'members' not in obj_in_have.keys(): + for m in members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + elif set(members) != set(obj_in_have['members']): + missing_members = list(set(members) - set(obj_in_have['members'])) + for m in missing_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + superfluous_members = list(set(obj_in_have['members']) - set(members)) + for m in superfluous_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('no channel-group') + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['group'], want) + if not obj_in_want: + commands.append('no interface port-channel {0}'.format(h['group'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['group'] = str(d['group']) + + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'] + }) + + return obj + + +def parse_mode(module, config, group, member): + mode = None + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = ['interface {0}'.format(member)] + body = netcfg.get_section(parents) + + match_int = re.findall(r'interface {0}\n'.format(member), body, re.M) + if match_int: + match = re.search(r'channel-group {0} mode (\S+)'.format(group), + body, re.M) + if match: + mode = match.group(1) + + return mode + + +def parse_members(module, config, group): + members = [] + + for line in config.strip().split('!'): + l = line.strip() + if l.startswith('interface'): + match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M) + if match_group: + match = re.search(r'interface (\S+)', l, re.M) + if match: + members.append(match.group(1)) + + return members + + +def get_channel(module, config, group): + match = re.findall(r'^interface (\S+)', config, re.M) + + if not match: + return {} + + channel = {} + for item in set(match): + member = item + channel['mode'] = parse_mode(module, config, group, member) + channel['members'] = parse_members(module, config, group) + + return channel + + +def map_config_to_obj(module): + objs = list() + config = get_config(module) + + for line in config.split('\n'): + l = line.strip() + match = re.search(r'interface port-channel(\S+)', l, re.M) + if match: + obj = {} + group = match.group(1) + obj['group'] = group + obj.update(get_channel(module, config, group)) + objs.append(obj) + + return objs + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + group=dict(type='int'), + mode=dict(choices=['active', 'on', 'passive']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True) + + required_one_of = [['group', 'aggregate']] + required_together = [['members', 'mode']] + mutually_exclusive = [['group', 'aggregate']] + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, + required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_lldp.py new file mode 100644 index 00000000..521b9331 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_lldp.py @@ -0,0 +1,135 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_lldp +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage LLDP configuration on Lenovo CNOS network devices. +description: + - This module provides declarative management of LLDP service + on Lenovc CNOS network devices. +notes: + - Tested against CNOS 10.9.1 +options: + state: + description: + - State of the LLDP configuration. If value is I(present) lldp will be + enabled else if it is I(absent) it will be disabled. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Enable LLDP service + community.network.cnos_lldp: + state: present + +- name: Disable LLDP service + community.network.cnos_lldp: + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to + manage the device. + type: list + sample: + - lldp timer 1024 + - lldp trap-interval 330 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, run_commands +from ansible.module_utils.connection import exec_command + + +def get_ethernet_range(module): + output = run_commands(module, ['show interface brief'])[0].split('\n') + maxport = None + last_interface = None + for line in output: + if line.startswith('Ethernet1/'): + last_interface = line.split(' ')[0] + if last_interface is not None: + eths = last_interface.split('/') + maxport = eths[1] + return maxport + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + state=dict(default='present', + choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + maxport = get_ethernet_range(module) + commands = [] + prime_cmd = 'interface ethernet 1/1-' + maxport + + if module.params['state'] == 'absent': + commands.append(prime_cmd) + commands.append('no lldp receive') + commands.append('no lldp transmit') + commands.append('exit') + commands.append('interface mgmt 0') + commands.append('no lldp receive') + commands.append('no lldp transmit') + commands.append('exit') + elif module.params['state'] == 'present': + commands.append(prime_cmd) + commands.append('lldp receive') + commands.append('lldp transmit') + commands.append('exit') + commands.append('interface mgmt 0') + commands.append('lldp receive') + commands.append('lldp transmit') + commands.append('exit') + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_logging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_logging.py new file mode 100644 index 00000000..6aa7cbf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_logging.py @@ -0,0 +1,421 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_logging +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage logging on network devices +description: + - This module provides declarative management of logging + on Cisco Cnos devices. +notes: + - Tested against CNOS 10.9.1 +options: + dest: + description: + - Destination of the logs. Lenovo uses the term server instead of host in + its CLI. + choices: ['server', 'console', 'monitor', 'logfile'] + name: + description: + - If value of C(dest) is I(file) it indicates file-name + and for I(server) indicates the server name to be notified. + size: + description: + - Size of buffer. The acceptable value is in range from 4096 to + 4294967295 bytes. + default: 10485760 + facility: + description: + - Set logging facility. This is applicable only for server logging + level: + description: + - Set logging severity levels. 0-emerg;1-alert;2-crit;3-err;4-warn; + 5-notif;6-inform;7-debug + default: 5 + aggregate: + description: List of logging definitions. + state: + description: + - State of the logging configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure server logging + community.network.cnos_logging: + dest: server + name: 10.241.107.224 + facility: local7 + state: present + +- name: Remove server logging configuration + community.network.cnos_logging: + dest: server + name: 10.241.107.224 + state: absent + +- name: Configure console logging level and facility + community.network.cnos_logging: + dest: console + level: 7 + state: present + +- name: Configure buffer size + community.network.cnos_logging: + dest: logfile + level: 5 + name: testfile + size: 5000 + +- name: Configure logging using aggregate + community.network.cnos_logging: + aggregate: + - { dest: console, level: 6 } + - { dest: logfile, size: 9000 } + +- name: Remove logging using aggregate + community.network.cnos_logging: + aggregate: + - { dest: console, level: 6 } + - { dest: logfile, name: anil, size: 9000 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging console 7 + - logging server 10.241.107.224 +""" + +import re + +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_capabilities +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def validate_size(value, module): + if value: + if not int(4096) <= int(value) <= int(4294967295): + module.fail_json(msg='size must be between 4096 and 4294967295') + else: + return value + + +def map_obj_to_commands(updates, module): + dest_group = ('console', 'monitor', 'logfile', 'server') + commands = list() + want, have = updates + for w in want: + dest = w['dest'] + name = w['name'] + size = w['size'] + facility = w['facility'] + level = w['level'] + state = w['state'] + del w['state'] + + if state == 'absent': + if dest: + + if dest == 'server': + commands.append('no logging server {0}'.format(name)) + + elif dest in dest_group: + commands.append('no logging {0}'.format(dest)) + + else: + module.fail_json(msg='dest must be among console, monitor, logfile, server') + + if state == 'present' and w not in have: + if dest == 'server': + cmd_str = 'logging server {0}'.format(name) + if level is not None and level > 0 and level < 8: + cmd_str = cmd_str + ' ' + level + if facility is not None: + cmd_str = cmd_str + ' facility ' + facility + commands.append(cmd_str) + + elif dest == 'logfile' and size: + present = False + + for entry in have: + if entry['dest'] == 'logfile' and entry['size'] == size and entry['level'] == level: + present = True + + if not present: + cmd_str = 'logging logfile ' + if name is not None: + cmd_str = cmd_str + name + if level and level != '7': + cmd_str = cmd_str + ' ' + level + else: + cmd_str = cmd_str + ' 7' + if size is not None: + cmd_str = cmd_str + ' size ' + size + commands.append(cmd_str) + else: + module.fail_json(msg='Name of the logfile is a mandatory parameter') + + else: + if dest: + dest_cmd = 'logging {0}'.format(dest) + if level: + dest_cmd += ' {0}'.format(level) + commands.append(dest_cmd) + return commands + + +def parse_facility(line, dest): + facility = None + if dest == 'server': + result = line.split() + i = 0 + for x in result: + if x == 'facility': + return result[i + 1] + i = i + 1 + return facility + + +def parse_size(line, dest): + size = None + if dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + i = 0 + for x in result: + if x == 'size': + return result[i + 1] + i = i + 1 + return '10485760' + return size + + +def parse_name(line, dest): + name = None + if dest == 'server': + if 'logging server' in line: + result = line.split() + i = 0 + for x in result: + if x == 'server': + name = result[i + 1] + elif dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + i = 0 + for x in result: + if x == 'logfile': + name = result[i + 1] + else: + name = None + return name + + +def parse_level(line, dest): + level_group = ('0', '1', '2', '3', '4', '5', '6', '7') + level = '7' + if dest == 'server': + if 'logging server' in line: + result = line.split() + if(len(result) > 3): + if result[3].isdigit(): + level = result[3] + else: + if dest == 'logfile': + if 'logging logfile' in line: + result = line.split() + if result[3].isdigit(): + level = result[3] + else: + match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M) + + return level + + +def map_config_to_obj(module): + obj = [] + dest_group = ('console', 'server', 'monitor', 'logfile') + data = get_config(module, flags=['| include logging']) + index = 0 + for line in data.split('\n'): + logs = line.split() + index = len(logs) + if index == 0 or index == 1: + continue + if logs[0] != 'logging': + continue + if logs[1] == 'monitor' or logs[1] == 'console': + obj.append({'dest': logs[1], 'level': logs[2]}) + elif logs[1] == 'logfile': + level = '5' + if index > 3 and logs[3].isdigit(): + level = logs[3] + size = '10485760' + if len(logs) > 4: + size = logs[5] + obj.append({'dest': logs[1], 'name': logs[2], 'size': size, 'level': level}) + elif logs[1] == 'server': + level = '5' + facility = None + + if index > 3 and logs[3].isdigit(): + level = logs[3] + if index > 3 and logs[3] == 'facility': + facility = logs[4] + if index > 4 and logs[4] == 'facility': + facility = logs[5] + obj.append({'dest': logs[1], 'name': logs[2], 'facility': facility, 'level': level}) + else: + continue + return obj + + +def map_params_to_obj(module, required_if=None): + obj = [] + aggregate = module.params.get('aggregate') + + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + module._check_required_if(required_if, item) + + d = item.copy() + if d['dest'] != 'server' and d['dest'] != 'logfile': + d['name'] = None + + if d['dest'] == 'logfile': + if 'size' in d: + d['size'] = str(validate_size(d['size'], module)) + elif 'size' not in d: + d['size'] = str(10485760) + else: + pass + + if d['dest'] != 'logfile': + d['size'] = None + + obj.append(d) + + else: + if module.params['dest'] != 'server' and module.params['dest'] != 'logfile': + module.params['name'] = None + + if module.params['dest'] == 'logfile': + if not module.params['size']: + module.params['size'] = str(10485760) + else: + module.params['size'] = None + + if module.params['size'] is None: + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'size': module.params['size'], + 'facility': module.params['facility'], + 'level': module.params['level'], + 'state': module.params['state'] + }) + + else: + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'size': str(validate_size(module.params['size'], module)), + 'facility': module.params['facility'], + 'level': module.params['level'], + 'state': module.params['state'] + }) + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + dest=dict(type='str', + choices=['server', 'console', 'monitor', 'logfile']), + name=dict(type='str'), + size=dict(type='int', default=10485760), + facility=dict(type='str'), + level=dict(type='str', default='5'), + state=dict(default='present', choices=['present', 'absent']), + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_if = [('dest', 'server', ['name'])] + + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_if=required_if) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_reload.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_reload.py new file mode 100644 index 00000000..61931293 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_reload.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to reload Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_reload +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Perform switch restart on devices running Lenovo CNOS +description: + - This module allows you to restart the switch using the current startup + configuration. The module is usually invoked after the running + configuration has been saved over the startup configuration. + This module uses SSH to manage network device configuration. + The results of the operation can be viewed in results directory. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_reload. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Reload + community.network.cnos_reload: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_reload_{{ inventory_hostname }}_output.txt" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Device is Reloading. Please wait..." +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'reload' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': 'reboot system? (y/n): ', + 'answer': 'y'}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg in "Device Response Timed out"): + module.exit_json(changed=True, + msg="Device is Reloading. Please wait...") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_rollback.py new file mode 100644 index 00000000..9051fdb0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_rollback.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to Rollback Config back to Lenovo Switches +# +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_rollback +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Roll back the running or startup configuration from a remote + server on devices running Lenovo CNOS +description: + - This module allows you to work with switch configurations. It provides a + way to roll back configurations of a switch from a remote server. This is + achieved by using startup or running configurations of the target device + that were previously backed up to a remote server using FTP, SFTP, TFTP, + or SCP. The first step is to create a directory from where the remote + server can be reached. The next step is to provide the full file path of + he backup configuration's location. Authentication details required by the + remote server must be provided as well. + By default, this method overwrites the switch's configuration file with + the newly downloaded file. This module uses SSH to manage network device + configuration. The results of the operation will be placed in a directory + named 'results' that must be created by the user in their local directory + to where the playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + configType: + description: + - This refers to the type of configuration which will be used for + the rolling back process. The choices are the running or startup + configurations. There is no default value, so it will result + in an error if the input is incorrect. + required: Yes + default: Null + choices: [running-config, startup-config] + protocol: + description: + - This refers to the protocol used by the network device to + interact with the remote server from where to download the backup + configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other + protocols will result in error. If this parameter is not + specified, there is no default value to be used. + required: Yes + default: Null + choices: [SFTP, SCP, FTP, TFTP] + rcserverip: + description: + - This specifies the IP Address of the remote server from where the + backup configuration will be downloaded. + required: Yes + default: Null + rcpath: + description: + - This specifies the full file path of the configuration file + located on the remote server. In case the relative path is used as + the variable value, the root folder for the user of the server + needs to be specified. + required: Yes + default: Null + serverusername: + description: + - Specify username for the server relating to the protocol used. + required: Yes + default: Null + serverpassword: + description: + - Specify password for the server relating to the protocol used. + required: Yes + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_rollback. + These are written in the main.yml file of the tasks directory. +--- + +- name: Test Rollback of config - Running config + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Startup config + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "sftp" + serverip: "10.241.106.118" + rcpath: "/root/cnos/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Running config - TFTP + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: running-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-running-config.txt" + serverusername: "root" + serverpassword: "root123" + +- name: Test Rollback of config - Startup config - TFTP + cnos_rolback: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt" + configType: startup-config + protocol: "tftp" + serverip: "10.241.106.118" + rcpath: "/anil/G8272-startup-config.txt" + serverusername: "root" + serverpassword: "root123" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Config file transferred to Device" +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +# Utility Method to rollback the running config or start up config +# This method supports only SCP or SFTP or FTP or TFTP +def doConfigRollBack(module, prompt, answer): + host = module.params['host'] + server = module.params['serverip'] + username = module.params['serverusername'] + password = module.params['serverpassword'] + protocol = module.params['protocol'].lower() + rcPath = module.params['rcpath'] + configType = module.params['configType'] + confPath = rcPath + retVal = '' + + command = "copy " + protocol + " " + protocol + "://" + command = command + username + "@" + server + "/" + confPath + command = command + " " + configType + " vrf management\n" + cnos.debugOutput(command + "\n") + # cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes') + cmd = [] + if(protocol == "scp"): + scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}] + scp_cmd2 = [{'command': '\n', 'prompt': 'Password:', + 'answer': password}] + cmd.extend(scp_cmd1) + cmd.extend(scp_cmd2) + if(configType == 'startup-config'): + scp_cmd3 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(scp_cmd3) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "sftp"): + sftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(sftp_cmd) + # cnos.debugOutput(configType + "\n") + if(configType == 'startup-config'): + sftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(sftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "ftp"): + ftp_cmd = [{'command': command, 'prompt': 'Password:', + 'answer': password}] + cmd.extend(ftp_cmd) + if(configType == 'startup-config'): + ftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(ftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + elif(protocol == "tftp"): + command = "copy " + protocol + " " + protocol + command = command + "://" + server + "/" + confPath + command = command + " " + configType + " vrf management\n" + cnos.debugOutput(command) + tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd) + if(configType == 'startup-config'): + tftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}] + cmd.extend(tftp_cmd2) + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + else: + return "Error-110" + + return retVal +# EOM + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + configType=dict(required=True), + protocol=dict(required=True), + serverip=dict(required=True), + rcpath=dict(required=True), + serverusername=dict(required=False), + serverpassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + protocol = module.params['protocol'].lower() + output = '' + if protocol in ('tftp', 'ftp', 'sftp', 'scp'): + transfer_status = doConfigRollBack(module, None, None) + else: + transfer_status = 'Invalid Protocol option' + output = output + "\n Config Transfer status \n" + transfer_status + + # Save it into the file + if '/' in outputfile: + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # need to add logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Config file transferred to Device") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_save.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_save.py new file mode 100644 index 00000000..9def4a94 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_save.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to save running config to start up config to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_save +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Save the running configuration as the startup configuration + on devices running Lenovo CNOS +description: + - This module allows you to copy the running configuration of a switch over + its startup configuration. It is recommended to use this module shortly + after any major configuration changes so they persist after a switch + restart. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_save. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Save + community.network.cnos_save: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_save_{{ inventory_hostname }}_output.txt" +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Switch Running Config is Saved to Startup Config" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True),), + supports_check_mode=False) + + command = 'write memory' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Switch Running Config is Saved to Startup Config ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_showrun.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_showrun.py new file mode 100644 index 00000000..87757c8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_showrun.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to display running config of Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_showrun +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect the current running configuration on devices running on CNOS +description: + - This module allows you to view the switch running configuration. It + executes the display running-config CLI command on a switch and returns a + file containing the current running configuration of the target network + device. This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: {} + +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_showrun. These are + written in the main.yml file of the tasks directory. +--- +- name: Run show running-config + community.network.cnos_showrun: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + outputfile: "./results/test_showrun_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Running Configuration saved in file" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + + command = 'show running-config' + outputfile = module.params['outputfile'] + output = '' + cmd = [{'command': command, 'prompt': None, 'answer': None}] + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, + msg="Running Configuration saved in file ") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_static_route.py new file mode 100644 index 00000000..53f0d9f4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_static_route.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to work on Link Aggregation with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_static_route +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage static IP routes on Lenovo CNOS network devices +description: + - This module provides declarative management of static + IP routes on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.10.1 +options: + prefix: + description: + - Network prefix of the static route. + mask: + description: + - Network prefix mask of the static route. + next_hop: + description: + - Next hop IP of the static route. + interface: + description: + - Interface of the static route. + description: + description: + - Name of the static route + aliases: ['description'] + admin_distance: + description: + - Admin distance of the static route. + default: 1 + tag: + description: + - Set tag of the static route. + aggregate: + description: List of static route definitions. + state: + description: + - State of the static route configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure static route + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + next_hop: 10.241.106.1 + +- name: Configure ultimate route with name and tag + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + interface: Ethernet1/13 + description: hello world + tag: 100 + +- name: Remove configuration + community.network.cnos_static_route: + prefix: 10.241.107.0 + mask: 255.255.255.0 + next_hop: 10.241.106.0 + state: absent + +- name: Add static route aggregates + community.network.cnos_static_route: + aggregate: + - { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 } + - { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 } + +- name: Remove static route aggregates + community.network.cnos_static_route: + aggregate: + - { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 } + - { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - ip route 10.241.107.0 255.255.255.0 10.241.106.0 +""" +from copy import deepcopy +from re import findall +from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec + + +def map_obj_to_commands(want, have): + commands = list() + + for w in want: + state = w['state'] + command = 'ip route' + prefix = w['prefix'] + mask = w['mask'] + command = ' '.join((command, prefix, mask)) + + for key in ['interface', 'next_hop', 'admin_distance', 'tag', + 'description']: + if w.get(key): + if key == 'description' and len(w.get(key).split()) > 1: + # name with multiple words needs to be quoted + command = ' '.join((command, key, '"%s"' % w.get(key))) + elif key in ('description', 'tag'): + command = ' '.join((command, key, w.get(key))) + else: + command = ' '.join((command, w.get(key))) + + if state == 'absent': + commands.append('no %s' % command) + elif state == 'present': + commands.append(command) + + return commands + + +def map_config_to_obj(module): + obj = [] + + out = get_config(module, flags='| include ip route') + for line in out.splitlines(): + # Split by whitespace but do not split quotes, needed for description + splitted_line = findall(r'[^"\s]\S*|".+?"', line) + route = {} + prefix_with_mask = splitted_line[2] + prefix = None + mask = None + iface = None + nhop = None + if validate_ip_address(prefix_with_mask) is True: + my_net = ipaddress.ip_network(prefix_with_mask) + prefix = str(my_net.network_address) + mask = str(my_net.netmask) + route.update({'prefix': prefix, + 'mask': mask, 'admin_distance': '1'}) + if splitted_line[3] is not None: + if validate_ip_address(splitted_line[3]) is False: + iface = str(splitted_line[3]) + route.update(interface=iface) + if validate_ip_address(splitted_line[4]) is True: + nhop = str(splitted_line[4]) + route.update(next_hop=nhop) + if splitted_line[5].isdigit(): + route.update(admin_distance=str(splitted_line[5])) + elif splitted_line[4].isdigit(): + route.update(admin_distance=str(splitted_line[4])) + else: + if splitted_line[6] is not None and splitted_line[6].isdigit(): + route.update(admin_distance=str(splitted_line[6])) + else: + nhop = str(splitted_line[3]) + route.update(next_hop=nhop) + if splitted_line[4].isdigit(): + route.update(admin_distance=str(splitted_line[4])) + + index = 0 + for word in splitted_line: + if word in ('tag', 'description'): + route.update(word=splitted_line[index + 1]) + index = index + 1 + obj.append(route) + + return obj + + +def map_params_to_obj(module, required_together=None): + keys = ['prefix', 'mask', 'state', 'next_hop', 'interface', 'description', + 'admin_distance', 'tag'] + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + route = item.copy() + for key in keys: + if route.get(key) is None: + route[key] = module.params.get(key) + + route = dict((k, v) for k, v in route.items() if v is not None) + module._check_required_together(required_together, route) + obj.append(route) + else: + module._check_required_together(required_together, module.params) + route = dict() + for key in keys: + if module.params.get(key) is not None: + route[key] = module.params.get(key) + obj.append(route) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + next_hop=dict(type='str'), + interface=dict(type='str'), + description=dict(type='str'), + admin_distance=dict(type='str', default='1'), + tag=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['prefix'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + argument_spec.update(element_spec) + + required_one_of = [['aggregate', 'prefix']] + required_together = [['prefix', 'mask']] + mutually_exclusive = [['aggregate', 'prefix']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + want = map_params_to_obj(module, required_together=required_together) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_system.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_system.py new file mode 100644 index 00000000..eced8ca5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_system.py @@ -0,0 +1,383 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on System Configuration with Lenovo Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_system +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage the system attributes on Lenovo CNOS devices +description: + - This module provides declarative management of node system attributes + on Lenovo CNOS devices. It provides an option to configure host system + parameters or remove those parameters from the device active + configuration. +options: + hostname: + description: + - Configure the device hostname parameter. This option takes an + ASCII string value or keyword 'default' + domain_name: + description: + - Configures the default domain + name suffix to be used when referencing this node by its + FQDN. This argument accepts either a list of domain names or + a list of dicts that configure the domain name and VRF name or + keyword 'default'. See examples. + lookup_enabled: + description: + - Administrative control for enabling or disabling DNS lookups. + When this argument is set to True, lookups are performed and + when it is set to False, lookups are not performed. + type: bool + domain_search: + description: + - Configures a list of domain + name suffixes to search when performing DNS name resolution. + This argument accepts either a list of domain names or + a list of dicts that configure the domain name and VRF name or + keyword 'default'. See examples. + name_servers: + description: + - List of DNS name servers by IP address to use to perform name resolution + lookups. This argument accepts either a list of DNS servers or + a list of hashes that configure the name server and VRF name or + keyword 'default'. See examples. + lookup_source: + description: + - Provides one or more source interfaces to use for performing DNS + lookups. The interface must be a valid interface configured. + on the device. + state: + description: + - State of the configuration + values in the device's current active configuration. When set + to I(present), the values should be configured in the device active + configuration and when set to I(absent) the values should not be + in the device active configuration + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Configure hostname and domain-name + community.network.cnos_system: + hostname: cnos01 + domain_name: test.example.com + +- name: Remove configuration + community.network.cnos_system: + state: absent + +- name: Configure name servers + community.network.cnos_system: + name_servers: + - 8.8.8.8 + - 8.8.4.4 + +- name: Configure DNS Lookup sources + community.network.cnos_system: + lookup_source: MgmtEth0/0/CPU0/0 + lookup_enabled: yes + +- name: Configure name servers with VRF support + nxos_system: + name_servers: + - { server: 8.8.8.8, vrf: mgmt } + - { server: 8.8.4.4, vrf: mgmt } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - hostname cnos01 + - ip domain-name test.example.com vrf default +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import check_args, debugOutput +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList + +_CONFIGURED_VRFS = None + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params['state'] + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def difference(x, y, z): + return [item for item in x[z] if item not in y[z]] + + if state == 'absent': + if have['hostname']: + commands.append('no hostname') + + for item in have['domain_name']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + for item in have['domain_search']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + for item in have['name_servers']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + + if state == 'present': + if needs_update('hostname'): + if want['hostname'] == 'default': + if have['hostname']: + commands.append('no hostname') + else: + commands.append('hostname %s' % want['hostname']) + + if want.get('lookup_enabled') is not None: + if have.get('lookup_enabled') != want.get('lookup_enabled'): + cmd = 'ip domain-lookup' + if want['lookup_enabled'] is False: + cmd = 'no %s' % cmd + commands.append(cmd) + + if want['domain_name']: + if want.get('domain_name')[0]['name'] == 'default': + if have['domain_name']: + for item in have['domain_name']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'domain_name'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'domain_name'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip domain-name {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + if want['domain_search']: + if want.get('domain_search')[0]['name'] == 'default': + if have['domain_search']: + for item in have['domain_search']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'domain_search'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'domain_search'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip domain-list {0} vrf {1}'.format(item['name'], my_vrf) + commands.append(cmd) + + if want['name_servers']: + if want.get('name_servers')[0]['server'] == 'default': + if have['name_servers']: + for item in have['name_servers']: + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + else: + for item in difference(have, want, 'name_servers'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + for item in difference(want, have, 'name_servers'): + my_vrf = 'default' + if item['vrf'] is not None: + my_vrf = item['vrf'] + cmd = 'ip name-server {0} vrf {1}'.format(item['server'], my_vrf) + commands.append(cmd) + + return commands + + +def parse_hostname(config): + match = re.search(r'^hostname (\S+)', config, re.M) + if match: + return match.group(1) + + +def parse_domain_name(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip domain-name' in line: + datas = line.split() + objects.append({'name': datas[2], 'vrf': datas[4]}) + + return objects + + +def parse_domain_search(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip domain-list' in line: + datas = line.split() + objects.append({'name': datas[2], 'vrf': datas[4]}) + + return objects + + +def parse_name_servers(config): + objects = list() + myconf = config.splitlines() + for line in myconf: + if 'ip name-server' in line: + datas = line.split() + objects.append({'server': datas[2], 'vrf': datas[4]}) + + return objects + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=2, contents=config) + + return { + 'hostname': parse_hostname(config), + 'lookup_enabled': 'no ip domain-lookup' not in config, + 'domain_name': parse_domain_name(config), + 'domain_search': parse_domain_search(config), + 'name_servers': parse_name_servers(config), + } + + +def map_params_to_obj(module): + obj = { + 'hostname': module.params['hostname'], + 'lookup_enabled': module.params['lookup_enabled'], + } + + domain_name = ComplexList(dict( + name=dict(key=True), + vrf=dict() + ), module) + + domain_search = ComplexList(dict( + name=dict(key=True), + vrf=dict() + ), module) + + name_servers = ComplexList(dict( + server=dict(key=True), + vrf=dict() + ), module) + + for arg, cast in [('domain_name', domain_name), + ('domain_search', domain_search), + ('name_servers', name_servers)]: + if module.params[arg] is not None: + obj[arg] = cast(module.params[arg]) + else: + obj[arg] = None + + return obj + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + hostname=dict(), + lookup_enabled=dict(type='bool'), + + # { name: , vrf: } + domain_name=dict(type='list'), + + # {name: , vrf: } + domain_search=dict(type='list'), + + # { server: ; vrf: } + name_servers=dict(type='list'), + + lookup_source=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_template.py new file mode 100644 index 00000000..07d61116 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_template.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send CLI templates to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_template +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage switch configuration using templates on devices running Lenovo CNOS +description: + - This module allows you to work with the running configuration of a switch. It provides a way + to execute a set of CNOS commands on a switch by evaluating the current running configuration + and executing the commands only if the specific settings have not been already configured. + The configuration source can be a set of commands or a template written in the Jinja2 templating language. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + commandfile: + description: + - This specifies the path to the CNOS command file which needs to be applied. This usually + comes from the commands folder. Generally this file is the output of the variables applied + on a template file. So this command is preceded by a template module. + Note The command file must contain the Ansible keyword {{ inventory_hostname }} in its + filename to ensure that the command file is unique for each switch and condition. + If this is omitted, the command file will be overwritten during iteration. For example, + commandfile=./commands/clos_leaf_bgp_{{ inventory_hostname }}_commands.txt + required: true + default: Null +''' +EXAMPLES = ''' +Tasks : The following are examples of using the module cnos_template. These are written in the main.yml file of the tasks directory. +--- +- name: Replace Config CLI command template with values + template: + src: demo_template.j2 + dest: "./commands/demo_template_{{ inventory_hostname }}_commands.txt" + vlanid1: 13 + slot_chassis_number1: "1/2" + portchannel_interface_number1: 100 + portchannel_mode1: "active" + +- name: Applying CLI commands on Switches + community.network.cnos_template: + deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}" + commandfile: "./commands/demo_template_{{ inventory_hostname }}_commands.txt" + outputfile: "./results/demo_template_command_{{ inventory_hostname }}_output.txt" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "Template Applied." +''' + +import sys +import time +import socket +import array +import json +import time +import re +import os +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def main(): + module = AnsibleModule( + argument_spec=dict( + commandfile=dict(required=True), + outputfile=dict(required=True), + host=dict(required=False), + deviceType=dict(required=True), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True),), + supports_check_mode=False) + commandfile = module.params['commandfile'] + outputfile = module.params['outputfile'] + output = '' + + # Send commands one by one to the device + f = open(commandfile, "r") + cmd = [] + for line in f: + # Omit the comment lines in template file + if not line.startswith("#"): + command = line.strip() + inner_cmd = [{'command': command, 'prompt': None, 'answer': None}] + cmd.extend(inner_cmd) + # Write to memory + save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}] + cmd.extend(save_cmd) + output = output + str(cnos.run_cnos_commands(module, cmd)) + # Write output to file + path = outputfile.rsplit('/', 1) + # cnos.debugOutput(path[0]) + if not os.path.exists(path[0]): + os.makedirs(path[0]) + file = open(outputfile, "a") + file.write(output) + file.close() + + # Logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="Template Applied") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_user.py new file mode 100644 index 00000000..a32aecb7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_user.py @@ -0,0 +1,386 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on management of local users on Lenovo CNOS Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_user +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage the collection of local users on Lenovo CNOS devices +description: + - This module provides declarative management of the local usernames + configured on Lenovo CNOS devices. It allows playbooks to manage + either individual usernames or the collection of usernames in the + current running config. It also supports purging usernames from the + configuration that are not explicitly defined. +options: + aggregate: + description: + - The set of username objects to be configured on the remote + Lenovo CNOS device. The list entries can either be the username + or a hash of username and properties. This argument is mutually + exclusive with the C(name) argument. + aliases: ['users', 'collection'] + name: + description: + - The username to be configured on the remote Lenovo CNOS + device. This argument accepts a string value and is mutually + exclusive with the C(aggregate) argument. + configured_password: + description: + - The password to be configured on the network device. The + password needs to be provided in cleartext and it will be encrypted + on the device. + Please note that this option is not same as C(provider password). + update_password: + description: + - Since passwords are encrypted in the device running config, this + argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + default: always + choices: ['on_create', 'always'] + role: + description: + - The C(role) argument configures the role for the username in the + device running configuration. The argument accepts a string value + defining the role name. This argument does not check if the role + has been configured on the device. + aliases: ['roles'] + sshkey: + description: + - The C(sshkey) argument defines the SSH public key to configure + for the username. This argument accepts a valid SSH key value. + purge: + description: + - The C(purge) argument instructs the module to consider the + resource definition absolute. It will remove any previously + configured usernames on the device with the exception of the + `admin` user which cannot be deleted per cnos constraints. + type: bool + default: 'no' + state: + description: + - The C(state) argument configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create a new user + community.network.cnos_user: + name: ansible + sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + state: present + +- name: Remove all users except admin + community.network.cnos_user: + purge: yes + +- name: Set multiple users role + aggregate: + - name: Netop + - name: Netend + role: network-operator + state: present +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - name ansible + - name ansible password password +start: + description: The time the job started + returned: always + type: str + sample: "2016-11-16 10:38:15.126146" +end: + description: The time the job ended + returned: always + type: str + sample: "2016-11-16 10:38:25.595612" +delta: + description: The time elapsed to perform all operations + returned: always + type: str + sample: "0:00:10.469466" +""" +import re + +from copy import deepcopy +from functools import partial + +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import run_commands, load_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_config +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import string_types, iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import get_user_roles + + +def validate_roles(value, module): + for item in value: + if item not in get_user_roles(): + module.fail_json(msg='invalid role specified') + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + update_password = module.params['update_password'] + + for update in updates: + want, have = update + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def add(x): + return commands.append('username %s %s' % (want['name'], x)) + + def remove(x): + return commands.append('no username %s %s' % (want['name'], x)) + + if want['state'] == 'absent': + commands.append('no username %s' % want['name']) + continue + + if want['state'] == 'present' and not have: + commands.append('username %s' % want['name']) + + if needs_update('configured_password'): + if update_password == 'always' or not have: + add('password %s' % want['configured_password']) + + if needs_update('sshkey'): + add('sshkey %s' % want['sshkey']) + + if want['roles']: + if have: + for item in set(have['roles']).difference(want['roles']): + remove('role %s' % item) + + for item in set(want['roles']).difference(have['roles']): + add('role %s' % item) + else: + for item in want['roles']: + add('role %s' % item) + + return commands + + +def parse_password(data): + if 'no password set' in data: + return None + return '' + + +def parse_roles(data): + roles = list() + if 'role:' in data: + items = data.split() + my_item = items[items.index('role:') + 1] + roles.append(my_item) + return roles + + +def parse_username(data): + name = data.split(' ', 1)[0] + username = name[1:] + return username + + +def parse_sshkey(data): + key = None + if 'sskkey:' in data: + items = data.split() + key = items[items.index('sshkey:') + 1] + return key + + +def map_config_to_obj(module): + out = run_commands(module, ['show user-account']) + data = out[0] + objects = list() + datum = data.split('User') + + for item in datum: + objects.append({ + 'name': parse_username(item), + 'configured_password': parse_password(item), + 'sshkey': parse_sshkey(item), + 'roles': parse_roles(item), + 'state': 'present' + }) + return objects + + +def get_param_value(key, item, module): + # if key doesn't exist in the item, get it from module.params + if not item.get(key): + value = module.params[key] + + # if key does exist, do a type check on it to validate it + else: + value_type = module.argument_spec[key].get('type', 'str') + type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] + type_checker(item[key]) + value = item[key] + + return value + + +def map_params_to_obj(module): + aggregate = module.params['aggregate'] + if not aggregate: + if not module.params['name'] and module.params['purge']: + return list() + elif not module.params['name']: + module.fail_json(msg='username is required') + else: + collection = [{'name': module.params['name']}] + else: + collection = list() + for item in aggregate: + if not isinstance(item, dict): + collection.append({'name': item}) + elif 'name' not in item: + module.fail_json(msg='name is required') + else: + collection.append(item) + + objects = list() + + for item in collection: + get_value = partial(get_param_value, item=item, module=module) + item.update({ + 'configured_password': get_value('configured_password'), + 'sshkey': get_value('sshkey'), + 'roles': get_value('roles'), + 'state': get_value('state') + }) + + for key, value in iteritems(item): + if value: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if all((value, validator)): + validator(value, module) + + objects.append(item) + + return objects + + +def update_objects(want, have): + updates = list() + for entry in want: + item = next((i for i in have if i['name'] == entry['name']), None) + if all((item is None, entry['state'] == 'present')): + updates.append((entry, {})) + elif item: + for key, value in iteritems(entry): + if value and value != item[key]: + updates.append((entry, item)) + return updates + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + configured_password=dict(no_log=True), + update_password=dict(default='always', choices=['on_create', 'always']), + roles=dict(type='list', aliases=['role']), + sshkey=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', + options=aggregate_spec, aliases=['collection', 'users']), + purge=dict(type='bool', default=False) + ) + + argument_spec.update(element_spec) + + mutually_exclusive = [('name', 'aggregate')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(update_objects(want, have), module) + + if module.params['purge']: + want_users = [x['name'] for x in want] + have_users = [x['name'] for x in have] + for item in set(have_users).difference(want_users): + if item != 'admin': + if not item.strip(): + continue + item = item.replace("\\", "\\\\") + commands.append('no username %s' % item) + + result['commands'] = commands + + # the cnos cli prevents this by rule but still + if 'no username admin' in commands: + module.fail_json(msg='Cannot delete the `admin` account') + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlag.py new file mode 100644 index 00000000..aa3ffa20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlag.py @@ -0,0 +1,441 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2017 Lenovo, 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 . +# +# Module to send VLAG commands to Lenovo Switches +# Lenovo Networking +# + +DOCUMENTATION = ''' +--- +module: cnos_vlag +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage VLAG resources and attributes on devices running + Lenovo CNOS +description: + - This module allows you to work with virtual Link Aggregation Groups + (vLAG) related configurations. The operators used are overloaded to ensure + control over switch vLAG configurations. Apart from the regular device + connection related attributes, there are four vLAG arguments which are + overloaded variables that will perform further configurations. They are + vlagArg1, vlagArg2, vlagArg3, and vlagArg4. For more details on how to use + these arguments, see [Overloaded Variables]. + This module uses SSH to manage network device configuration. + The results of the operation will be placed in a directory named 'results' + that must be created by the user in their local directory to where the + playbook is run. +extends_documentation_fragment: +- community.network.cnos + +options: + vlagArg1: + description: + - This is an overloaded vlag first argument. Usage of this argument can + be found is the User Guide referenced above. + required: Yes + default: Null + choices: [enable, auto-recovery,config-consistency,isl,mac-address-table, + peer-gateway,priority,startup-delay,tier-id,vrrp,instance,hlthchk] + vlagArg2: + description: + - This is an overloaded vlag second argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [Interval in seconds,disable or strict,Port Aggregation Number, + VLAG priority,Delay time in seconds,VLAG tier-id value, + VLAG instance number,keepalive-attempts,keepalive-interval, + retry-interval,peer-ip] + vlagArg3: + description: + - This is an overloaded vlag third argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [enable or port-aggregation,Number of keepalive attempts, + Interval in seconds,Interval in seconds, + VLAG health check peer IP4 address] + vlagArg4: + description: + - This is an overloaded vlag fourth argument. Usage of this argument can + be found is the User Guide referenced above. + required: No + default: Null + choices: [Port Aggregation Number,default or management] + +''' +EXAMPLES = ''' + +Tasks : The following are examples of using the module cnos_vlag. These are + written in the main.yml file of the tasks directory. +--- +- name: Test Vlag - enable + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "enable" + +- name: Test Vlag - autorecovery + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "auto-recovery" + vlagArg2: 266 + +- name: Test Vlag - config-consistency + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "config-consistency" + vlagArg2: "strict" + +- name: Test Vlag - isl + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "isl" + vlagArg2: 23 + +- name: Test Vlag - mac-address-table + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "mac-address-table" + +- name: Test Vlag - peer-gateway + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "peer-gateway" + +- name: Test Vlag - priority + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "priority" + vlagArg2: 1313 + +- name: Test Vlag - startup-delay + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "startup-delay" + vlagArg2: 323 + +- name: Test Vlag - tier-id + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "tier-id" + vlagArg2: 313 + +- name: Test Vlag - vrrp + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "vrrp" + +- name: Test Vlag - instance + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "instance" + vlagArg2: 33 + vlagArg3: 333 + +- name: Test Vlag - instance2 + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "instance" + vlagArg2: "33" + +- name: Test Vlag - keepalive-attempts + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "keepalive-attempts" + vlagArg3: 13 + +- name: Test Vlag - keepalive-interval + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "keepalive-interval" + vlagArg3: 131 + +- name: Test Vlag - retry-interval + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "retry-interval" + vlagArg3: 133 + +- name: Test Vlag - peer ip + community.network.cnos_vlag: + deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}" + outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt" + vlagArg1: "hlthchk" + vlagArg2: "peer-ip" + vlagArg3: "1.2.3.4" + +''' +RETURN = ''' +msg: + description: Success or failure message + returned: always + type: str + sample: "vLAG configurations accomplished" +''' + +import sys +import time +import socket +import array +import json +import time +import re +try: + from ansible_collections.community.network.plugins.module_utils.network.cnos import cnos + HAS_LIB = True +except Exception: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from collections import defaultdict + + +def vlagConfig(module, prompt, answer): + + retVal = '' + # vlag config command happens here. + command = 'vlag ' + + vlagArg1 = module.params['vlagArg1'] + vlagArg2 = module.params['vlagArg2'] + vlagArg3 = module.params['vlagArg3'] + vlagArg4 = module.params['vlagArg4'] + deviceType = module.params['deviceType'] + + if(vlagArg1 == "enable"): + # debugOutput("enable") + command = command + vlagArg1 + " " + + elif(vlagArg1 == "auto-recovery"): + # debugOutput("auto-recovery") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_auto_recovery", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-160" + return retVal + + elif(vlagArg1 == "config-consistency"): + # debugOutput("config-consistency") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_config_consistency", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-161" + return retVal + + elif(vlagArg1 == "isl"): + # debugOutput("isl") + command = command + vlagArg1 + " port-channel " + value = cnos.checkSanityofVariable( + deviceType, "vlag_port_aggregation", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-162" + return retVal + + elif(vlagArg1 == "mac-address-table"): + # debugOutput("mac-address-table") + command = command + vlagArg1 + " refresh" + + elif(vlagArg1 == "peer-gateway"): + # debugOutput("peer-gateway") + command = command + vlagArg1 + " " + + elif(vlagArg1 == "priority"): + # debugOutput("priority") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_priority", + vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-163" + return retVal + + elif(vlagArg1 == "startup-delay"): + # debugOutput("startup-delay") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_startup_delay", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-164" + return retVal + + elif(vlagArg1 == "tier-id"): + # debugOutput("tier-id") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_tier_id", vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + else: + retVal = "Error-165" + return retVal + + elif(vlagArg1 == "vrrp"): + # debugOutput("vrrp") + command = command + vlagArg1 + " active" + + elif(vlagArg1 == "instance"): + # debugOutput("instance") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable(deviceType, "vlag_instance", + vlagArg2) + if(value == "ok"): + command = command + vlagArg2 + if(vlagArg3 is not None): + command = command + " port-channel " + value = cnos.checkSanityofVariable( + deviceType, "vlag_port_aggregation", vlagArg3) + if(value == "ok"): + command = command + vlagArg3 + else: + retVal = "Error-162" + return retVal + else: + command = command + " enable " + else: + retVal = "Error-166" + return retVal + + elif(vlagArg1 == "hlthchk"): + # debugOutput("hlthchk") + command = command + vlagArg1 + " " + value = cnos.checkSanityofVariable( + deviceType, "vlag_hlthchk_options", vlagArg2) + if(value == "ok"): + if(vlagArg2 == "keepalive-attempts"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_keepalive_attempts", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-167" + return retVal + elif(vlagArg2 == "keepalive-interval"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_keepalive_interval", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-168" + return retVal + elif(vlagArg2 == "retry-interval"): + value = cnos.checkSanityofVariable( + deviceType, "vlag_retry_interval", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + else: + retVal = "Error-169" + return retVal + elif(vlagArg2 == "peer-ip"): + # Here I am not taking care of IPV6 option. + value = cnos.checkSanityofVariable( + deviceType, "vlag_peerip", vlagArg3) + if(value == "ok"): + command = command + vlagArg2 + " " + vlagArg3 + if(vlagArg4 is not None): + value = cnos.checkSanityofVariable( + deviceType, "vlag_peerip_vrf", vlagArg4) + if(value == "ok"): + command = command + " vrf " + vlagArg4 + else: + retVal = "Error-170" + return retVal + else: + retVal = "Error-171" + return retVal + + else: + retVal = "Error-172" + return retVal + + # debugOutput(command) + cmd = [{'command': command, 'prompt': None, 'answer': None}] + retVal = retVal + str(cnos.run_cnos_commands(module, cmd)) + return retVal +# EOM + + +def main(): + # + # Define parameters for vlag creation entry + # + module = AnsibleModule( + argument_spec=dict( + outputfile=dict(required=True), + host=dict(required=False), + username=dict(required=False), + password=dict(required=False, no_log=True), + enablePassword=dict(required=False, no_log=True), + deviceType=dict(required=True), + vlagArg1=dict(required=True), + vlagArg2=dict(required=False), + vlagArg3=dict(required=False), + vlagArg4=dict(required=False),), + supports_check_mode=False) + + outputfile = module.params['outputfile'] + output = "" + + # Send the CLi command + output = output + str(vlagConfig(module, '(config)#', None)) + + # Save it into the file + file = open(outputfile, "a") + file.write(output) + file.close() + + # need to add logic to check when changes occur or not + errorMsg = cnos.checkOutputForError(output) + if(errorMsg is None): + module.exit_json(changed=True, msg="VLAG configurations accomplished") + else: + module.fail_json(msg=errorMsg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlan.py new file mode 100644 index 00000000..d150b2e9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vlan.py @@ -0,0 +1,405 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (C) 2017 Lenovo, Inc. +# (c) 2017, Ansible by 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 . +# +# Module to send VLAN commands to Lenovo Switches +# Overloading aspect of vlan creation in a range is pending +# Lenovo Networking + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cnos_vlan +author: "Anil Kumar Mureleedharan(@amuraleedhar)" +short_description: Manage VLANs on CNOS network devices +description: + - This module provides declarative management of VLANs + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.8.1 +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + interfaces: + description: + - List of interfaces that should be associated to the VLAN. + required: true + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for + given vlan C(name) for associated interfaces. If the value in the + C(associated_interfaces) does not match with the operational state of + vlan interfaces on device it will result in failure. + delay: + description: + - Delay the play should wait to check for declarative intent params + values. + default: 10 + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the VLAN configuration. + default: present + choices: ['present', 'absent', 'active', 'suspend'] + provider: + description: + - B(Deprecated) + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html). + - HORIZONTALLINE + - A dict object containing connection details. + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when building the connection to the remote device. + default: 22 + username: + description: + - Configures the username to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote device. This value is used to authenticate + the SSH session. If the value is not specified in the task, the + value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. + timeout: + description: + - Specifies the timeout in seconds for communicating with the network device + for either connecting or sending commands. If the timeout is + exceeded before the operation is completed, the module will error. + default: 10 + ssh_keyfile: + description: + - Specifies the SSH key to use to authenticate the connection to + the remote device. This value is the path to the + key used to authenticate the SSH session. If the value is not specified + in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) + will be used instead. + authorize: + description: + - Instructs the module to enter privileged mode on the remote device + before sending any commands. If not specified, the device will + attempt to execute all commands in non-privileged mode. If the value + is not specified in the task, the value of environment variable + C(ANSIBLE_NET_AUTHORIZE) will be used instead. + type: bool + default: 'no' + auth_pass: + description: + - Specifies the password to use if required to enter privileged mode + on the remote device. If I(authorize) is false, then this argument + does nothing. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead. +''' + +EXAMPLES = """ +- name: Create vlan + community.network.cnos_vlan: + vlan_id: 100 + name: test-vlan + state: present + +- name: Add interfaces to VLAN + community.network.cnos_vlan: + vlan_id: 100 + interfaces: + - Ethernet1/33 + - Ethernet1/44 + +- name: Check if interfaces is assigned to VLAN + community.network.cnos_vlan: + vlan_id: 100 + associated_interfaces: + - Ethernet1/33 + - Ethernet1/44 + +- name: Delete vlan + community.network.cnos_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import debugOutput, check_args +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec +from ansible.module_utils._text import to_text + + +def search_obj_in_list(vlan_id, lst): + obj = list() + for o in lst: + if o['vlan_id'] == vlan_id: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + interfaces = w['interfaces'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + commands.append('no vlan {0}'.format(vlan_id)) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('name {0}'.format(name)) + + if interfaces: + for i in interfaces: + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + else: + if name: + if name != obj_in_have['name']: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('name {0}'.format(name)) + + if interfaces: + if not obj_in_have['interfaces']: + for i in interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + elif set(interfaces) != set(obj_in_have['interfaces']): + missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces'])) + for i in missing_interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('switchport access vlan {0}'.format(vlan_id)) + + superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) + for i in superfluous_interfaces: + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) + commands.append('switchport mode access') + commands.append('no switchport access vlan') + else: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('name {0}'.format(name)) + commands.append('state {0}'.format(state)) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + if not obj_in_want and h['vlan_id'] != '1': + commands.append('no vlan {0}'.format(h['vlan_id'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['vlan_id'] = str(d['vlan_id']) + + obj.append(d) + else: + obj.append({ + 'vlan_id': str(module.params['vlan_id']), + 'name': module.params['name'], + 'interfaces': module.params['interfaces'], + # 'associated_interfaces': module.params['associated_interfaces'], + 'state': module.params['state'] + }) + + return obj + + +def parse_to_logical_rows(out): + relevant_data = False + cur_row = [] + for line in out.splitlines(): + if not line: + """Skip empty lines.""" + continue + if '0' < line[0] <= '9': + """Line starting with a number.""" + if len(cur_row) > 0: + yield cur_row + cur_row = [] # Reset it to hold a next chunk + relevant_data = True + if relevant_data: + data = line.strip().split('(') + cur_row.append(data[0]) + yield cur_row + + +def parse_to_obj(logical_rows): + first_row = logical_rows[0] + rest_rows = logical_rows[1:] + vlan_data = first_row.split() + obj = {} + obj['vlan_id'] = vlan_data[0] + obj['name'] = vlan_data[1] + obj['state'] = vlan_data[2] + obj['interfaces'] = rest_rows + return obj + + +def parse_vlan_brief(vlan_out): + return [parse_to_obj(r) for r in parse_to_logical_rows(vlan_out)] + + +def map_config_to_obj(module): + return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0]) + + +def check_declarative_intent_params(want, module, result): + + have = None + is_delay = False + + for w in want: + if w.get('associated_interfaces') is None: + continue + + if result['changed'] and not is_delay: + time.sleep(module.params['delay']) + is_delay = True + + if have is None: + have = map_config_to_obj(module) + + for i in w['associated_interfaces']: + obj_in_have = search_obj_in_list(w['vlan_id'], have) + if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']: + module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='list'), + associated_interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'active', 'suspend']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + argument_spec.update(cnos_argument_spec) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + check_declarative_intent_params(want, module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vrf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vrf.py new file mode 100644 index 00000000..60bb23dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cnos/cnos_vrf.py @@ -0,0 +1,365 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by 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 . +# +# Module to work on management of local users on Lenovo CNOS Switches +# Lenovo Networking +# +DOCUMENTATION = ''' +--- +module: cnos_vrf +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage VRFs on Lenovo CNOS network devices +description: + - This module provides declarative management of VRFs + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.9.1 +options: + name: + description: + - Name of the VRF. + required: true + rd: + description: + - Route distinguisher of the VRF + interfaces: + description: + - Identifies the set of interfaces that + should be configured in the VRF. Interfaces must be routed + interfaces in order to be placed into a VRF. The name of interface + should be in expanded format and not abbreviated. + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for + given vrf C(name) for associated interfaces. If the value in the + C(associated_interfaces) does not match with the operational state of + vrf interfaces on device it will result in failure. + aggregate: + description: List of VRFs contexts + purge: + description: + - Purge VRFs not defined in the I(aggregate) parameter. + default: no + type: bool + delay: + description: + - Time in seconds to wait before checking for the operational state on + remote device. This wait is applicable for operational state arguments. + default: 10 + state: + description: + - State of the VRF configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vrf + community.network.cnos_vrf: + name: test + rd: 1:200 + interfaces: + - Ethernet1/33 + state: present + +- name: Delete VRFs + community.network.cnos_vrf: + name: test + state: absent + +- name: Create aggregate of VRFs with purge + community.network.cnos_vrf: + aggregate: + - { name: test4, rd: "1:204" } + - { name: test5, rd: "1:205" } + state: present + purge: yes + +- name: Delete aggregate of VRFs + community.network.cnos_vrf: + aggregate: + - name: test2 + - name: test3 + - name: test4 + - name: test5 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vrf context test + - rd 1:100 + - interface Ethernet1/44 + - vrf member test +""" +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.cnos.cnos import cnos_argument_spec, check_args + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, + ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Switchport : enabled', config) + return bool(match) + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + purge = module.params['purge'] + + for w in want: + name = w['name'] + rd = w['rd'] + interfaces = w['interfaces'] + + obj_in_have = search_obj_in_list(name, have) + + if name == 'default': + module.fail_json(msg='VRF context default is reserved') + elif len(name) > 63: + module.fail_json(msg='VRF name is too long') + if state == 'absent': + if name == 'management': + module.fail_json(msg='Management VRF context cannot be deleted') + if obj_in_have: + commands.append('no vrf context %s' % name) + elif state == 'present': + if not obj_in_have: + commands.append('vrf context %s' % name) + + if rd is not None: + commands.append('rd %s' % rd) + + if w['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + else: + if w['rd'] is not None and w['rd'] != obj_in_have['rd']: + commands.append('vrf context %s' % w['name']) + commands.append('rd %s' % w['rd']) + + if w['interfaces']: + if not obj_in_have['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + elif set(w['interfaces']) != obj_in_have['interfaces']: + missing_interfaces = list(set(w['interfaces']) - set(obj_in_have['interfaces'])) + + for i in missing_interfaces: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['name'], want) + if not obj_in_want: + commands.append('no vrf context %s' % h['name']) + + return commands + + +def map_config_to_obj(module): + objs = [] + output = run_commands(module, {'command': 'show vrf'}) + if output is not None: + vrfText = output[0].strip() + vrfList = vrfText.split('VRF') + for vrfItem in vrfList: + if 'FIB ID' in vrfItem: + obj = dict() + list_of_words = vrfItem.split() + vrfName = list_of_words[0] + obj['name'] = vrfName[:-1] + obj['rd'] = list_of_words[list_of_words.index('RD') + 1] + start = False + obj['interfaces'] = [] + for intName in list_of_words: + if 'Interfaces' in intName: + start = True + if start is True: + if '!' not in intName and 'Interfaces' not in intName: + obj['interfaces'].append(intName.strip().lower()) + objs.append(obj) + else: + module.fail_json(msg='Could not fetch VRF details from device') + return objs + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + if item.get('interfaces'): + item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf] + + if item.get('associated_interfaces'): + item['associated_interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('associated_interfaces') if intf] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'state': module.params['state'], + 'rd': module.params['rd'], + 'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else [], + 'associated_interfaces': [intf.replace(" ", "").lower() for intf in + module.params['associated_interfaces']] if module.params['associated_interfaces'] else [] + + }) + + return obj + + +def check_declarative_intent_params(want, module, result): + have = None + is_delay = False + + for w in want: + if w.get('associated_interfaces') is None: + continue + + if result['changed'] and not is_delay: + time.sleep(module.params['delay']) + is_delay = True + + if have is None: + have = map_config_to_obj(module) + + for i in w['associated_interfaces']: + obj_in_have = search_obj_in_list(w['name'], have) + + if obj_in_have: + interfaces = obj_in_have.get('interfaces') + if interfaces is not None and i not in interfaces: + module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w['name'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + interfaces=dict(type='list'), + associated_interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + rd=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + name = name.lower() + if is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L3' + '\nport first before using this module. You can use' + '\nthe cnos_interface module for this.') + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + check_declarative_intent_params(want, module, result) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cumulus/nclu.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cumulus/nclu.py new file mode 100644 index 00000000..22537b5b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/cumulus/nclu.py @@ -0,0 +1,250 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016-2018, Cumulus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nclu +author: "Cumulus Networks (@isharacomix)" +short_description: Configure network interfaces using NCLU +description: + - Interface to the Network Command Line Utility, developed to make it easier + to configure operating systems running ifupdown2 and Quagga, such as + Cumulus Linux. Command documentation is available at + U(https://docs.cumulusnetworks.com/cumulus-linux/System-Configuration/Network-Command-Line-Utility-NCLU/) +options: + commands: + description: + - A list of strings containing the net commands to run. Mutually + exclusive with I(template). + template: + description: + - A single, multi-line string with jinja2 formatting. This string + will be broken by lines, and each line will be run through net. + Mutually exclusive with I(commands). + commit: + description: + - When true, performs a 'net commit' at the end of the block. + Mutually exclusive with I(atomic). + default: false + type: bool + abort: + description: + - Boolean. When true, perform a 'net abort' before the block. + This cleans out any uncommitted changes in the buffer. + Mutually exclusive with I(atomic). + default: false + type: bool + atomic: + description: + - When true, equivalent to both I(commit) and I(abort) being true. + Mutually exclusive with I(commit) and I(atomic). + default: false + type: bool + description: + description: + - Commit description that will be recorded to the commit log if + I(commit) or I(atomic) are true. + default: "Ansible-originated commit" +''' + +EXAMPLES = ''' + +- name: Add two interfaces without committing any changes + community.network.nclu: + commands: + - add int swp1 + - add int swp2 + +- name: Modify hostname to Cumulus-1 and commit the change + community.network.nclu: + commands: + - add hostname Cumulus-1 + commit: true + +- name: Add 48 interfaces and commit the change. + community.network.nclu: + template: | + {% for iface in range(1,49) %} + add int swp{{iface}} + {% endfor %} + commit: true + description: "Ansible - add swps1-48" + +- name: Fetch Status Of Interface + community.network.nclu: + commands: + - show interface swp1 + register: output + +- name: Print Status Of Interface + ansible.builtin.debug: + var: output + +- name: Fetch Details From All Interfaces In JSON Format + community.network.nclu: + commands: + - show interface json + register: output + +- name: Print Interface Details + ansible.builtin.debug: + var: output["msg"] + +- name: Atomically add an interface + community.network.nclu: + commands: + - add int swp1 + atomic: true + description: "Ansible - add swp1" + +- name: Remove IP address from interface swp1 + community.network.nclu: + commands: + - del int swp1 ip address 1.1.1.1/24 + +- name: Configure BGP AS and add 2 EBGP neighbors using BGP Unnumbered + community.network.nclu: + commands: + - add bgp autonomous-system 65000 + - add bgp neighbor swp51 interface remote-as external + - add bgp neighbor swp52 interface remote-as external + commit: true + +- name: Configure BGP AS and Add 2 EBGP neighbors Using BGP Unnumbered via Template + community.network.nclu: + template: | + {% for neighbor in range(51,53) %} + add bgp neighbor swp{{neighbor}} interface remote-as external + add bgp autonomous-system 65000 + {% endfor %} + atomic: true + +- name: Check BGP Status + community.network.nclu: + commands: + - show bgp summary json + register: output + +- name: Print BGP Status In JSON + ansible.builtin.debug: + var: output["msg"] +''' + +RETURN = ''' +changed: + description: whether the interface was changed + returned: changed + type: bool + sample: True +msg: + description: human-readable report of success or failure + returned: always + type: str + sample: "interface bond0 config updated" +''' + +from ansible.module_utils.basic import AnsibleModule + + +def command_helper(module, command, errmsg=None): + """Run a command, catch any nclu errors""" + (_rc, output, _err) = module.run_command("/usr/bin/net %s" % command) + if _rc or 'ERROR' in output or 'ERROR' in _err: + module.fail_json(msg=errmsg or output) + return str(output) + + +def check_pending(module): + """Check the pending diff of the nclu buffer.""" + pending = command_helper(module, "pending", "Error in pending config. You may want to view `net pending` on this target.") + + delimeter1 = "net add/del commands since the last 'net commit'" + color1 = '\x1b[94m' + if delimeter1 in pending: + pending = pending.split(delimeter1)[0] + pending = pending.replace(color1, '') + return pending.strip() + + +def run_nclu(module, command_list, command_string, commit, atomic, abort, description): + _changed = False + + commands = [] + if command_list: + commands = command_list + elif command_string: + commands = command_string.splitlines() + + do_commit = False + do_abort = abort + if commit or atomic: + do_commit = True + if atomic: + do_abort = True + + if do_abort: + command_helper(module, "abort") + + # First, look at the staged commands. + before = check_pending(module) + # Run all of the net commands + output_lines = [] + for line in commands: + if line.strip(): + output_lines += [command_helper(module, line.strip(), "Failed on line %s" % line)] + output = "\n".join(output_lines) + + # If pending changes changed, report a change. + after = check_pending(module) + if before == after: + _changed = False + else: + _changed = True + + # Do the commit. + if do_commit: + result = command_helper(module, "commit description '%s'" % description) + if "commit ignored" in result: + _changed = False + command_helper(module, "abort") + elif command_helper(module, "show commit last") == "": + _changed = False + + return _changed, output + + +def main(testing=False): + module = AnsibleModule(argument_spec=dict( + commands=dict(required=False, type='list'), + template=dict(required=False, type='str'), + description=dict(required=False, type='str', default="Ansible-originated commit"), + abort=dict(required=False, type='bool', default=False), + commit=dict(required=False, type='bool', default=False), + atomic=dict(required=False, type='bool', default=False)), + mutually_exclusive=[('commands', 'template'), + ('commit', 'atomic'), + ('abort', 'atomic')] + ) + command_list = module.params.get('commands', None) + command_string = module.params.get('template', None) + commit = module.params.get('commit') + atomic = module.params.get('atomic') + abort = module.params.get('abort') + description = module.params.get('description') + + _changed, output = run_nclu(module, command_list, command_string, commit, atomic, abort, description) + if not testing: + module.exit_json(changed=_changed, msg=output) + elif testing: + return {"changed": _changed, "msg": output} + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_command.py new file mode 100644 index 00000000..cc4a73b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_command.py @@ -0,0 +1,172 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +module: edgeos_command +author: + - Chad Norgan (@beardymcbeards) + - Sam Doran (@samdoran) +short_description: Run one or more commands on EdgeOS devices +description: + - This command module allows running one or more commands on a remote + device running EdgeOS, such as the Ubiquiti EdgeRouter. + - This module does not support running commands in configuration mode. + - Certain C(show) commands in EdgeOS produce many lines of output and + use a custom pager that can cause this module to hang. If the + value of the environment variable C(ANSIBLE_EDGEOS_TERMINAL_LENGTH) + is not set, the default number of 10000 is used. + - "This is a network module and requires C(connection: network_cli) + in order to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). +options: + commands: + description: + - The commands or ordered set of commands that should be run against the + remote device. The output of the command is returned to the playbook. + If the C(wait_for) argument is provided, the module is not returned + until the condition is met or the number of retries is exceeded. + required: True + wait_for: + description: + - Causes the task to wait for a specific condition to be met before + moving forward. If the condition is not met before the specified + number of retries is exceeded, the task will fail. + required: False + match: + description: + - Used in conjunction with C(wait_for) to create match policy. If set to + C(all), then all conditions in C(wait_for) must be met. If set to + C(any), then only one condition must match. + required: False + default: 'all' + choices: ['any', 'all'] + retries: + description: + - Number of times a command should be tried before it is considered failed. + The command is run on the target device and evaluated against the + C(wait_for) conditionals. + required: False + default: 10 + interval: + description: + - The number of seconds to wait between C(retries) of the command. + required: False + default: 1 + +notes: + - Tested against EdgeOS 1.9.7 + - Running C(show system boot-messages all) will cause the module to hang since + EdgeOS is using a custom pager setting to display the output of that command. +''' + +EXAMPLES = """ +tasks: + - name: Reboot the device + community.network.edgeos_command: + commands: reboot now + + - name: Show the configuration for eth0 and eth1 + community.network.edgeos_command: + commands: show interfaces ethernet {{ item }} + loop: + - eth0 + - eth1 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +""" +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands, to_lines +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import run_commands + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + if module.check_mode: + for item in list(commands): + if not item['command'].startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not ' + 'executing %s' % item['command'] + ) + commands.remove(item) + + return commands + + +def main(): + spec = dict( + commands=dict(type='list', required=True), + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + + warnings = list() + result = {'changed': False, 'warnings': warnings} + commands = parse_commands(module, warnings) + wait_for = module.params['wait_for'] or list() + + try: + conditionals = [Conditional(c) for c in wait_for] + except AttributeError as exc: + module.fail_json(msg=to_text(exc)) + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)), + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_config.py new file mode 100644 index 00000000..0bcb1cb3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_config.py @@ -0,0 +1,314 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeos_config +author: + - "Nathaniel Case (@Qalthos)" + - "Sam Doran (@samdoran)" +short_description: Manage EdgeOS configuration on remote device +description: + - This module provides configuration file management of EdgeOS + devices. It provides arguments for managing both the + configuration file and state of the active configuration. All + configuration statements are based on `set` and `delete` commands + in the device configuration. + - "This is a network module and requires the C(connection: network_cli) in order + to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). +notes: + - Tested against EdgeOS 1.9.7 + - Setting C(ANSIBLE_PERSISTENT_COMMAND_TIMEOUT) to 30 is recommended since + the save command can take longer than the default of 10 seconds on + some EdgeOS hardware. +options: + lines: + description: + - The ordered set of configuration lines to be managed and + compared with the existing configuration on the remote + device. + src: + description: + - The C(src) argument specifies the path to the source config + file to load. The source config file can either be in + bracket format or set format. The source file can include + Jinja2 template variables. + match: + description: + - The C(match) argument controls the method used to match + against the current active configuration. By default, the + desired config is matched against the active config and the + deltas are loaded. If the C(match) argument is set to C(none) + the active configuration is ignored and the configuration is + always loaded. + default: line + choices: ['line', 'none'] + backup: + description: + - The C(backup) argument will backup the current device's active + configuration to the Ansible control host prior to making any + changes. If the C(backup_options) value is not given, the backup + file will be located in the backup folder in the playbook root + directory or role root directory if the playbook is part of an + ansible role. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by edgeos_config' + config: + description: + - The C(config) argument specifies the base configuration to use + to compare against the desired configuration. If this value + is not specified, the module will automatically retrieve the + current active configuration from the remote device. + save: + description: + - The C(save) argument controls whether or not changes made + to the active configuration are saved to disk. This is + independent of committing the config. When set to C(True), the + active configuration is saved. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure the remote device + community.network.edgeos_config: + lines: + - set system host-name {{ inventory_hostname }} + - set service lldp + - delete service dhcp-server + +- name: Backup and load from file + community.network.edgeos_config: + src: edgeos.cfg + backup: yes + +- name: Configurable backup path + community.network.edgeos_config: + src: edgeos.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The list of configuration commands sent to the device + returned: always + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/edgeos_config.2016-07-16@22:28:34 +""" + +import re + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import load_config, get_config, run_commands + + +DEFAULT_COMMENT = 'configured by edgeos_config' + + +def config_to_commands(config): + set_format = config.startswith('set') or config.startswith('delete') + candidate = NetworkConfig(indent=4, contents=config) + if not set_format: + candidate = [c.line for c in candidate.items] + commands = list() + # this filters out less specific lines + for item in candidate: + for index, entry in enumerate(commands): + if item.startswith(entry): + del commands[index] + break + commands.append(item) + + commands = ['set %s' % cmd.replace(' {', '') for cmd in commands] + + else: + commands = to_native(candidate).split('\n') + + return commands + + +def get_candidate(module): + contents = module.params['src'] or module.params['lines'] + + if module.params['lines']: + contents = '\n'.join(contents) + + return config_to_commands(contents) + + +def check_command(module, command): + """Tests against a command line to be valid otherwise raise errors + + Error on uneven single quote which breaks ansible waiting for further input. Ansible + will handle even single quote failures correctly. + + :param command: the command line from current or new config + :type command: string + :raises ValueError: + * if contains odd number of single quotes + :return: command string unchanged + :rtype: string + """ + if command.count("'") % 2 != 0: + module.fail_json(msg="Unmatched single (') quote found in command: " + command) + + return command + + +def diff_config(module, commands, config): + config = [to_native(check_command(module, c)) for c in config.splitlines()] + + updates = list() + visited = set() + delete_commands = [line for line in commands if line.startswith('delete')] + + for line in commands: + item = to_native(check_command(module, line)) + + if not item.startswith('set') and not item.startswith('delete'): + raise ValueError('line must start with either `set` or `delete`') + + elif item.startswith('set'): + + if item not in config: + updates.append(line) + + # If there is a corresponding delete command in the desired config, make sure to append + # the set command even though it already exists in the running config + else: + ditem = re.sub('set', 'delete', item) + for line in delete_commands: + if ditem.startswith(line): + updates.append(item) + + elif item.startswith('delete'): + if not config: + updates.append(line) + else: + item = re.sub(r'delete', 'set', item) + for entry in config: + if entry.startswith(item) and line not in visited: + updates.append(line) + visited.add(line) + + return list(updates) + + +def run(module, result): + # get the current active config from the node or passed in via + # the config param + config = module.params['config'] or get_config(module) + + # create the candidate config object from the arguments + candidate = get_candidate(module) + + # create loadable config that includes only the configuration updates + commands = diff_config(module, candidate, config) + + result['commands'] = commands + + commit = not module.check_mode + comment = module.params['comment'] + + if commands: + load_config(module, commands, commit=commit, comment=comment) + + result['changed'] = True + + +def main(): + + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + spec = dict( + src=dict(type='path'), + lines=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + comment=dict(default=DEFAULT_COMMENT), + + config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + mutually_exclusive = [('lines', 'src')] + + module = AnsibleModule( + argument_spec=spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True + ) + + warnings = list() + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module=module) + + if any((module.params['src'], module.params['lines'])): + run(module, result) + + if module.params['save']: + diff = run_commands(module, commands=['configure', 'compare saved'])[1] + if diff != '[edit]': + if not module.check_mode: + run_commands(module, commands=['save']) + result['changed'] = True + run_commands(module, commands=['exit']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_facts.py new file mode 100644 index 00000000..e82af2ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeos/edgeos_facts.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeos_facts +author: + - Nathaniel Case (@Qalthos) + - Sam Doran (@samdoran) +short_description: Collect facts from remote devices running EdgeOS +description: + - Collects a base set of device facts from a remote device that + is running EdgeOS. This module prepends all of the + base network fact keys with U(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against EdgeOS 1.9.7 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, default, config, and neighbors. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: "!config" +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.edgeos_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.edgeos_facts: + gather_subset: config + +- name: Collect everything exception the config + community.network.edgeos_facts: + gather_subset: "!config" +""" + +RETURN = """ +ansible_net_config: + description: The running-config from the device + returned: when config is configured + type: str +ansible_net_commits: + description: The set of available configuration revisions + returned: when present + type: list +ansible_net_hostname: + description: The configured system hostname + returned: always + type: str +ansible_net_model: + description: The device model string + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the device + returned: always + type: str +ansible_net_version: + description: The version of the software running + returned: always + type: str +ansible_net_neighbors: + description: The set of LLDP neighbors + returned: when interface is configured + type: list +ansible_net_gather_subset: + description: The list of subsets gathered by the module + returned: always + type: list +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.edgeos.edgeos import run_commands + + +class FactsBase(object): + + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS)) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show host name', + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + + self.facts['hostname'] = self.responses[1] + + def parse_version(self, data): + match = re.search(r'Version:\s*v(\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'HW model:\s*([A-Za-z0-9- ]+)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'HW S/N:\s+(\S+)', data) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = [ + 'show configuration commands|cat', + 'show system commit', + ] + + def populate(self): + super(Config, self).populate() + + self.facts['config'] = self.responses + + commits = self.responses[1] + entries = list() + entry = None + + for line in commits.split('\n'): + match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line) + if match: + if entry: + entries.append(entry) + + entry = dict(revision=match.group(1), + datetime=match.group(2), + by=str(match.group(3)).strip(), + via=str(match.group(4)).strip(), + comment=None) + elif entry: + entry['comment'] = line.strip() + + self.facts['commits'] = entries + + +class Neighbors(FactsBase): + + COMMANDS = [ + 'show lldp neighbors', + 'show lldp neighbors detail', + ] + + def populate(self): + super(Neighbors, self).populate() + + all_neighbors = self.responses[0] + if 'LLDP not configured' not in all_neighbors: + neighbors = self.parse( + self.responses[1] + ) + self.facts['neighbors'] = self.parse_neighbors(neighbors) + + def parse(self, data): + parsed = list() + values = None + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + values += '\n%s' % line + elif line.startswith('Interface'): + if values: + parsed.append(values) + values = line + if values: + parsed.append(values) + return parsed + + def parse_neighbors(self, data): + facts = dict() + for item in data: + interface = self.parse_interface(item) + host = self.parse_host(item) + port = self.parse_port(item) + if interface not in facts: + facts[interface] = list() + facts[interface].append(dict(host=host, port=port)) + return facts + + def parse_interface(self, data): + match = re.search(r'^Interface:\s+(\S+),', data) + return match.group(1) + + def parse_host(self, data): + match = re.search(r'SysName:\s+(.+)$', data, re.M) + if match: + return match.group(1) + + def parse_port(self, data): + match = re.search(r'PortDescr:\s+(.+)$', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + neighbors=Neighbors, + config=Config +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=spec, + supports_check_mode=True) + + warnings = list() + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Subset must be one of [%s], got %s' % + (', '.join(VALID_SUBSETS), subset)) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_facts.py new file mode 100644 index 00000000..e7b3e9cf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_facts.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeswitch_facts +author: "Frederic Bor (@f-bor)" +short_description: Collect facts from remote devices running Edgeswitch +description: + - Collects a base set of device facts from a remote device that + is running Ubiquiti Edgeswitch. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against Edgeswitch 1.7.4 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.edgeswitch_facts: + gather_subset: all + +- name: Collect only the running config and default facts + community.network.edgeswitch_facts: + gather_subset: + - config + +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# config +ansible_net_startupconfig: + description: The startup config from the device + returned: when config is configured + type: str + version_added: 1.2.0 + +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show sysinfo'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(self.responses[1]) + + def parse_version(self, data): + match = re.search(r'Software Version\.+ (.*)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'System Name\.+ (.*)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'Machine Model\.+ (.*)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial Number\.+ (.*)', data) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class StartupConfig(FactsBase): + + COMMANDS = ['show startup-config'] + + def populate(self): + super(StartupConfig, self).populate() + data = self.responses[0] + if data: + self.facts['startupconfig'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces description', + 'show interfaces status all' + ] + + def populate(self): + super(Interfaces, self).populate() + + interfaces = {} + + data = self.responses[0] + self.parse_interfaces_description(data, interfaces) + + data = self.responses[1] + self.parse_interfaces_status(data, interfaces) + + self.facts['interfaces'] = interfaces + + def parse_interfaces_description(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)\s+(\w+)\s+(\w+)', line) + if match: + name = match.group(1) + interface = {} + interface['operstatus'] = match.group(2) + interface['lineprotocol'] = match.group(3) + interface['description'] = line[30:] + interfaces[name] = interface + + def parse_interfaces_status(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)', line) + if match: + name = match.group(1) + interface = interfaces[name] + interface['physicalstatus'] = line[61:71].strip() + interface['mediatype'] = line[73:91].strip() + + +FACT_SUBSETS = dict( + default=Default, + config=Config, + startupconfig=StartupConfig, + interfaces=Interfaces, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_vlan.py new file mode 100644 index 00000000..4520309f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/edgeswitch/edgeswitch_vlan.py @@ -0,0 +1,493 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: edgeswitch_vlan +author: "Frederic Bor (@f-bor)" +short_description: Manage VLANs on Ubiquiti Edgeswitch network devices +description: + - This module provides declarative management of VLANs + on Ubiquiti Edgeswitch network devices. +notes: + - Tested against edgeswitch 1.7.4 + - This module use native Ubiquiti vlan syntax and does not support switchport compatibility syntax. + For clarity, it is strongly advised to not use both syntaxes on the same interface. + - Edgeswitch does not support deleting or changing name of VLAN 1 + - As auto_tag, auto_untag and auto_exclude are a kind of default setting for all interfaces, they are mutually exclusive + +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4093. + tagged_interfaces: + description: + - List of interfaces that should accept and transmit tagged frames for the VLAN. + Accept range of interfaces. + untagged_interfaces: + description: + - List of interfaces that should accept untagged frames and transmit them tagged for the VLAN. + Accept range of interfaces. + excluded_interfaces: + description: + - List of interfaces that should be excluded of the VLAN. + Accept range of interfaces. + auto_tag: + description: + - Each of the switch interfaces will be set to accept and transmit + untagged frames for I(vlan_id) unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + auto_untag: + description: + - Each of the switch interfaces will be set to accept untagged frames and + transmit them tagged for I(vlan_id) unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + auto_exclude: + description: + - Each of the switch interfaces will be excluded from I(vlan_id) + unless defined in I(*_interfaces). + This is a default setting for all switch interfaces. + type: bool + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - action on the VLAN configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vlan + community.network.edgeswitch_vlan: + vlan_id: 100 + name: voice + action: present + +- name: Add interfaces to VLAN + community.network.edgeswitch_vlan: + vlan_id: 100 + tagged_interfaces: + - 0/1 + - 0/4-0/6 + +- name: Setup three vlans and delete the rest + community.network.edgeswitch_vlan: + purge: true + aggregate: + - { vlan_id: 1, name: default, auto_untag: true, excluded_interfaces: 0/45-0/48 } + - { vlan_id: 100, name: voice, auto_tag: true } + - { vlan_id: 200, name: video, auto_exclude: true, untagged_interfaces: 0/45-0/48, tagged_interfaces: 0/49 } + +- name: Delete vlan + community.network.edgeswitch_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan database + - vlan 100 + - vlan name 100 "test vlan" + - exit + - interface 0/1 + - vlan pvid 50 + - vlan participation include 50,100 + - vlan tagging 100 + - vlan participation exclude 200 + - no vlan tagging 200 +""" + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import load_config, run_commands +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch import build_aggregate_spec, map_params_to_obj +from ansible_collections.community.network.plugins.module_utils.network.edgeswitch.edgeswitch_interface import InterfaceConfiguration, merge_interfaces + + +def search_obj_in_list(vlan_id, lst): + for o in lst: + if o['vlan_id'] == vlan_id: + return o + + +def map_vlans_to_commands(want, have, module): + commands = [] + vlans_added = [] + vlans_removed = [] + vlans_names = [] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + vlans_removed.append(vlan_id) + + elif state == 'present': + if not obj_in_have: + vlans_added.append(vlan_id) + if name: + vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name)) + else: + if name: + if name != obj_in_have['name']: + vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name)) + + if module.params['purge']: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + # you can't delete vlan 1 on Edgeswitch + if not obj_in_want and h['vlan_id'] != '1': + vlans_removed.append(h['vlan_id']) + + if vlans_removed: + commands.append('no vlan {0}'.format(','.join(vlans_removed))) + + if vlans_added: + commands.append('vlan {0}'.format(','.join(vlans_added))) + + if vlans_names: + commands.extend(vlans_names) + + if commands: + commands.insert(0, 'vlan database') + commands.append('exit') + + return commands + + +class VlanInterfaceConfiguration(InterfaceConfiguration): + """ class holding vlan definitions for a given interface + """ + def __init__(self): + InterfaceConfiguration.__init__(self) + self.tagged = [] + self.untagged = [] + self.excluded = [] + + def set_vlan(self, vlan_id, type): + try: + self.tagged.remove(vlan_id) + except ValueError: + pass + + try: + self.untagged.remove(vlan_id) + except ValueError: + pass + + try: + self.excluded.remove(vlan_id) + except ValueError: + pass + + f = getattr(self, type) + f.append(vlan_id) + + def gen_commands(self, port, module): + """ to reduce commands generated by this module + we group vlans changes to have a max of 5 vlan commands by interface + """ + exclude = [] + include = [] + tag = [] + untag = [] + pvid = [] + + for vlan_id in self.excluded: + if vlan_id not in port['forbidden_vlans']: + exclude.append(vlan_id) + + if vlan_id in port['tagged_vlans']: + untag.append(vlan_id) + + for vlan_id in self.untagged: + if vlan_id in port['forbidden_vlans'] or vlan_id not in port['untagged_vlans'] and vlan_id not in port['tagged_vlans']: + include.append(vlan_id) + + if vlan_id in port['tagged_vlans']: + untag.append(vlan_id) + + if vlan_id != port['pvid_mode']: + pvid.append(vlan_id) + + for vlan_id in self.tagged: + if vlan_id not in port['tagged_vlans']: + tag.append(vlan_id) + include.append(vlan_id) + + if include: + self.commands.append('vlan participation include {0}'.format(','.join(include))) + + if pvid: + if len(pvid) > 1: + module.fail_json(msg='{0} can\'t have more than one untagged vlan') + return + self.commands.append('vlan pvid {0}'.format(pvid[0])) + + if untag: + self.commands.append('no vlan tagging {0}'.format(','.join(untag))) + + if tag: + self.commands.append('vlan tagging {0}'.format(','.join(tag))) + + if exclude: + self.commands.append('vlan participation exclude {0}'.format(','.join(exclude))) + + +def set_interfaces_vlan(interfaces_param, interfaces, vlan_id, type): + """ set vlan_id type for each interface in interfaces_param on interfaces + unrange interfaces_param if needed + """ + if interfaces_param: + for i in interfaces_param: + match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i) + if match: + group = match.group(1) + start = int(match.group(2)) + end = int(match.group(4)) + for x in range(start, end + 1): + key = '{0}/{1}'.format(group, x) + interfaces[key].set_vlan(vlan_id, type) + else: + interfaces[i].set_vlan(vlan_id, type) + + +def map_interfaces_to_commands(want, ports, module): + commands = list() + + # generate a configuration for each interface + interfaces = {} + for key, value in ports.items(): + interfaces[key] = VlanInterfaceConfiguration() + + for w in want: + state = w['state'] + if state != 'present': + continue + + auto_tag = w['auto_tag'] + auto_untag = w['auto_untag'] + auto_exclude = w['auto_exclude'] + vlan_id = w['vlan_id'] + tagged_interfaces = w['tagged_interfaces'] + untagged_interfaces = w['untagged_interfaces'] + excluded_interfaces = w['excluded_interfaces'] + + # set the default type, if any + for key, value in ports.items(): + if auto_tag: + interfaces[key].tagged.append(vlan_id) + elif auto_exclude: + interfaces[key].excluded.append(vlan_id) + elif auto_untag: + interfaces[key].untagged.append(vlan_id) + + # set explicit definitions + set_interfaces_vlan(tagged_interfaces, interfaces, vlan_id, 'tagged') + set_interfaces_vlan(untagged_interfaces, interfaces, vlan_id, 'untagged') + set_interfaces_vlan(excluded_interfaces, interfaces, vlan_id, 'excluded') + + # generate commands for each interface + for i, interface in interfaces.items(): + port = ports[i] + interface.gen_commands(port, module) + + # reduce them using range syntax when possible + interfaces = merge_interfaces(interfaces) + + # final output + for i, interface in interfaces.items(): + if len(interface.commands) > 0: + commands.append('interface {0}'.format(i)) + commands.extend(interface.commands) + + return commands + + +def parse_vlan_brief(vlan_out): + have = [] + for line in vlan_out.split('\n'): + obj = re.match(r'(?P\d+)\s+(?P[^\s]+)\s+', line) + if obj: + have.append(obj.groupdict()) + return have + + +def unrange(vlans): + res = [] + for vlan in vlans: + match = re.match(r'(\d+)-(\d+)', vlan) + if match: + start = int(match.group(1)) + end = int(match.group(2)) + for vlan_id in range(start, end + 1): + res.append(str(vlan_id)) + else: + res.append(vlan) + return res + + +def parse_interfaces_switchport(cmd_out): + ports = dict() + objs = re.findall( + r'Port: (\d+\/\d+)\n' + 'VLAN Membership Mode:(.*)\n' + 'Access Mode VLAN:(.*)\n' + 'General Mode PVID:(.*)\n' + 'General Mode Ingress Filtering:(.*)\n' + 'General Mode Acceptable Frame Type:(.*)\n' + 'General Mode Dynamically Added VLANs:(.*)\n' + 'General Mode Untagged VLANs:(.*)\n' + 'General Mode Tagged VLANs:(.*)\n' + 'General Mode Forbidden VLANs:(.*)\n', cmd_out) + for o in objs: + port = { + 'interface': o[0], + 'pvid_mode': o[3].replace("(default)", "").strip(), + 'untagged_vlans': unrange(o[7].strip().split(',')), + 'tagged_vlans': unrange(o[8].strip().split(',')), + 'forbidden_vlans': unrange(o[9].strip().split(',')) + } + ports[port['interface']] = port + return ports + + +def map_ports_to_obj(module): + return parse_interfaces_switchport(run_commands(module, ['show interfaces switchport'])[0]) + + +def map_config_to_obj(module): + return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0]) + + +def check_params(module, want): + """ Deeper checks on parameters + """ + def check_parmams_interface(interfaces): + if interfaces: + for i in interfaces: + match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i) + if match: + if match.group(1) != match.group(3): + module.fail_json(msg="interface range must be within same group: " + i) + else: + match = re.search(r'(\d+)\/(\d+)', i) + if not match: + module.fail_json(msg="wrong interface format: " + i) + + for w in want: + auto_tag = w['auto_tag'] + auto_untag = w['auto_untag'] + auto_exclude = w['auto_exclude'] + + c = 0 + if auto_tag: + c = c + 1 + + if auto_untag: + c = c + 1 + + if auto_exclude: + c = c + 1 + + if c > 1: + module.fail_json(msg="parameters are mutually exclusive: auto_tag, auto_untag, auto_exclude") + return + + check_parmams_interface(w['tagged_interfaces']) + check_parmams_interface(w['untagged_interfaces']) + check_parmams_interface(w['excluded_interfaces']) + w['vlan_id'] = str(w['vlan_id']) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + tagged_interfaces=dict(type='list'), + untagged_interfaces=dict(type='list'), + excluded_interfaces=dict(type='list'), + auto_tag=dict(type='bool'), + auto_exclude=dict(type='bool'), + auto_untag=dict(type='bool'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + argument_spec = build_aggregate_spec( + element_spec, + ['vlan_id'], + dict(purge=dict(default=False, type='bool')) + ) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [ + ['vlan_id', 'aggregate'], + ['auto_tag', 'auto_untag', 'auto_exclude']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + result = {'changed': False} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + check_params(module, want) + + # vlans are not created/deleted in configure mode + commands = map_vlans_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + run_commands(module, commands, check_rc=False) + result['changed'] = True + + ports = map_ports_to_obj(module) + + # interfaces vlan are set in configure mode + commands = map_interfaces_to_commands(want, ports, module) + result['commands'].extend(commands) + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_command.py new file mode 100644 index 00000000..61fcf1a7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_command.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to execute ENOS Commands on Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_command +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Run arbitrary commands on Lenovo ENOS devices +description: + - Sends arbitrary commands to an ENOS node and returns the results + read from the device. The C(enos_command) module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +extends_documentation_fragment: +- community.network.enos + +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + port: 22 + username: admin + password: admin + timeout: 30 + +--- +- name: Test contains operator + community.network.enos_command: + commands: + - show version + - show system memory + wait_for: + - "result[0] contains 'Lenovo'" + - "result[1] contains 'MemFree'" + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for single command + community.network.enos_command: + commands: ['show version'] + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: Get output for multiple commands + community.network.enos_command: + commands: + - show version + - show interface information + provider: "{{ cli }}" + register: result + +- ansible.builtin.assert: + that: + - "result.changed == false" + - "result.stdout is defined" + - "result.stdout | length == 2" +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import run_commands, check_args +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import enos_argument_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + spec.update(enos_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_config.py new file mode 100644 index 00000000..86a8497d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_config.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to configure Lenovo Switches. +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_config +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage Lenovo ENOS configuration sections +description: + - Lenovo ENOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with ENOS configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.enos + +notes: + - Tested against ENOS 8.4.1 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is + mutually exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block', 'config'] + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + comment: + description: + - Allows a commit description to be specified to be included + when the configuration is committed. If the configuration is + not changed or committed, this argument is ignored. + default: 'configured by enos_config' + admin: + description: + - Enters into administration configuration mode for making config + changes to the device. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.enos_config: + "lines: hostname {{ inventory_hostname }}" + +- name: Configure interface settings + community.network.enos_config: + lines: + - enable + - ip ospf enable + parents: interface ip 13 + +- name: Load a config from disk and replace the current config + community.network.enos_config: + src: config.cfg + backup: yes + +- name: Configurable backup path + community.network.enos_config: + src: config.cfg + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when lines is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/enos01.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import load_config, get_config +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import enos_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import check_args +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +DEFAULT_COMMIT_COMMENT = 'configured by enos_config' + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + replace_config = replace == 'config' + path = module.params['parents'] + comment = module.params['comment'] + admin = module.params['admin'] + check_mode = module.check_mode + + candidate = get_candidate(module) + + if match != 'none' and replace != 'config': + contents = get_running_config(module) + configobj = NetworkConfig(contents=contents, indent=1) + commands = candidate.difference(configobj, path=path, match=match, + replace=replace) + else: + commands = candidate.items + + if commands: + commands = dumps(commands, 'commands').split('\n') + + if any((module.params['lines'], module.params['src'])): + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + + diff = load_config(module, commands) + if diff: + result['diff'] = dict(prepared=diff) + result['changed'] = True + + +def main(): + """main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block', 'config']), + + config=dict(), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + comment=dict(default=DEFAULT_COMMIT_COMMENT), + admin=dict(type='bool', default=False) + ) + + argument_spec.update(enos_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('replace', 'config', ['src'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_facts.py new file mode 100644 index 00000000..e5f972ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/enos/enos_facts.py @@ -0,0 +1,503 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Module to Collect facts from Lenovo Switches running Lenovo ENOS commands +# Lenovo Networking +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: enos_facts +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Collect facts from remote devices running Lenovo ENOS +description: + - Collects a base set of device facts from a remote Lenovo device + running on ENOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +extends_documentation_fragment: +- community.network.enos + +notes: + - Tested against ENOS 8.4.1 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' +EXAMPLES = ''' +Tasks: The following are examples of using the module enos_facts. +--- +- name: Test Enos Facts + community.network.enos_facts: + provider={{ cli }} + + vars: + cli: + host: "{{ inventory_hostname }}" + port: 22 + username: admin + password: admin + transport: cli + timeout: 30 + authorize: True + auth_pass: + +--- +# Collect all facts from the device +- community.network.enos_facts: + gather_subset: all + provider: "{{ cli }}" + +# Collect only the config and default facts +- community.network.enos_facts: + gather_subset: + - config + provider: "{{ cli }}" + +# Do not collect hardware facts +- community.network.enos_facts: + gather_subset: + - "!hardware" + provider: "{{ cli }}" + +''' +RETURN = ''' + ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +# default + ansible_net_model: + description: The model name returned from the Lenovo ENOS device + returned: always + type: str + ansible_net_serialnum: + description: The serial number of the Lenovo ENOS device + returned: always + type: str + ansible_net_version: + description: The ENOS operating system version running on the remote device + returned: always + type: str + ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + ansible_net_image: + description: Indicates the active image for the device + returned: always + type: str +# hardware + ansible_net_memfree_mb: + description: The available free memory on the remote device in MB + returned: when hardware is configured + type: int +# config + ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces + ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list + ansible_net_interfaces: + description: A hash of all interfaces running on the system. + This gives information on description, mac address, mtu, speed, + duplex and operstatus + returned: when interfaces is configured + type: dict + ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +''' + +import re + +from ansible_collections.community.network.plugins.module_utils.network.enos.enos import run_commands, enos_argument_spec, check_args +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self.PERSISTENT_COMMAND_TIMEOUT = 60 + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, + check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show run'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + data_run = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + if data_run: + self.facts['hostname'] = self.parse_hostname(data_run) + + def parse_version(self, data): + match = re.search(r'^Software Version (.*?) ', data, re.M | re.I) + if match: + return match.group(1) + + def parse_hostname(self, data_run): + for line in data_run.split('\n'): + line = line.strip() + match = re.match(r'hostname (.*?)', line, re.M | re.I) + if match: + hosts = line.split() + hostname = hosts[1].strip('\"') + return hostname + return "NA" + + def parse_model(self, data): + match = re.search(r'^Lenovo RackSwitch (\S+)', data, re.M | re.I) + if match: + return match.group(1) + + def parse_image(self, data): + match = re.search(r'(.*) image1(.*)', data, re.M | re.I) + if match: + return "Image1" + else: + return "Image2" + + def parse_serialnum(self, data): + match = re.search(r'^Switch Serial No: (\S+)', data, re.M | re.I) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show system memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.run(['show system memory']) + data = to_text(data, errors='surrogate_or_strict').strip() + data = data.replace(r"\n", "\n") + if data: + self.facts['memtotal_mb'] = self.parse_memtotal(data) + self.facts['memfree_mb'] = self.parse_memfree(data) + + def parse_memtotal(self, data): + match = re.search(r'^MemTotal:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + def parse_memfree(self, data): + match = re.search(r'^MemFree:\s*(.*) kB', data, re.M | re.I) + if match: + return int(match.group(1)) / 1024 + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interface status'] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data1 = self.run(['show interface status']) + data1 = to_text(data1, errors='surrogate_or_strict').strip() + data1 = data1.replace(r"\n", "\n") + data2 = self.run(['show lldp port']) + data2 = to_text(data2, errors='surrogate_or_strict').strip() + data2 = data2.replace(r"\n", "\n") + lines1 = None + lines2 = None + if data1: + lines1 = self.parse_interfaces(data1) + if data2: + lines2 = self.parse_interfaces(data2) + if lines1 is not None and lines2 is not None: + self.facts['interfaces'] = self.populate_interfaces(lines1, lines2) + data3 = self.run(['show lldp remote-device port']) + data3 = to_text(data3, errors='surrogate_or_strict').strip() + data3 = data3.replace(r"\n", "\n") + + lines3 = None + if data3: + lines3 = self.parse_neighbors(data3) + if lines3 is not None: + self.facts['neighbors'] = self.populate_neighbors(lines3) + + data4 = self.run(['show interface ip']) + data4 = data4[0].split('\n') + lines4 = None + if data4: + lines4 = self.parse_ipaddresses(data4) + ipv4_interfaces = self.set_ipv4_interfaces(lines4) + self.facts['all_ipv4_addresses'] = ipv4_interfaces + ipv6_interfaces = self.set_ipv6_interfaces(lines4) + self.facts['all_ipv6_addresses'] = ipv6_interfaces + + def parse_ipaddresses(self, data4): + parsed = list() + for line in data4: + if len(line) == 0: + continue + else: + line = line.strip() + if len(line) == 0: + continue + match = re.search(r'IP4', line, re.M | re.I) + if match: + key = match.group() + parsed.append(line) + match = re.search(r'IP6', line, re.M | re.I) + if match: + key = match.group() + parsed.append(line) + return parsed + + def set_ipv4_interfaces(self, line4): + ipv4_addresses = list() + for line in line4: + ipv4Split = line.split() + if ipv4Split[1] == "IP4": + ipv4_addresses.append(ipv4Split[2]) + return ipv4_addresses + + def set_ipv6_interfaces(self, line4): + ipv6_addresses = list() + for line in line4: + ipv6Split = line.split() + if ipv6Split[1] == "IP6": + ipv6_addresses.append(ipv6Split[2]) + return ipv6_addresses + + def populate_neighbors(self, lines3): + neighbors = dict() + for line in lines3: + neighborSplit = line.split("|") + innerData = dict() + innerData['Remote Chassis ID'] = neighborSplit[2].strip() + innerData['Remote Port'] = neighborSplit[3].strip() + sysName = neighborSplit[4].strip() + if sysName is not None: + innerData['Remote System Name'] = neighborSplit[4].strip() + else: + innerData['Remote System Name'] = "NA" + neighbors[neighborSplit[0].strip()] = innerData + return neighbors + + def populate_interfaces(self, lines1, lines2): + interfaces = dict() + for line1, line2 in zip(lines1, lines2): + line = line1 + " " + line2 + intfSplit = line.split() + innerData = dict() + innerData['description'] = intfSplit[6].strip() + innerData['macaddress'] = intfSplit[8].strip() + innerData['mtu'] = intfSplit[9].strip() + innerData['speed'] = intfSplit[1].strip() + innerData['duplex'] = intfSplit[2].strip() + innerData['operstatus'] = intfSplit[5].strip() + if("up" not in intfSplit[5].strip()) and ("down" not in intfSplit[5].strip()): + innerData['description'] = intfSplit[7].strip() + innerData['macaddress'] = intfSplit[9].strip() + innerData['mtu'] = intfSplit[10].strip() + innerData['operstatus'] = intfSplit[6].strip() + interfaces[intfSplit[0].strip()] = innerData + return interfaces + + def parse_neighbors(self, neighbors): + parsed = list() + for line in neighbors.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^([0-9]+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(INT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(EXT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(MGT+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + def parse_interfaces(self, data): + parsed = list() + for line in data.split('\n'): + if len(line) == 0: + continue + else: + line = line.strip() + match = re.match(r'^([0-9]+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(INT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(EXT+)', line) + if match: + key = match.group(1) + parsed.append(line) + match = re.match(r'^(MGT+)', line) + if match: + key = match.group(1) + parsed.append(line) + return parsed + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +PERSISTENT_COMMAND_TIMEOUT = 60 + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + argument_spec.update(enos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + check_args(module, warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/eric_eccli/eric_eccli_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/eric_eccli/eric_eccli_command.py new file mode 100644 index 00000000..40266b3a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/eric_eccli/eric_eccli_command.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# Copyright (c) 2019 Ericsson AB. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: eric_eccli_command +author: Ericsson IPOS OAM team (@itercheng) +short_description: Run commands on remote devices running ERICSSON ECCLI +description: + - Sends arbitrary commands to an ERICSSON eccli node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module also support running commands in configuration mode + in raw command style. +options: + commands: + description: + - List of commands to send to the remote ECCLI device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. If a command sent to the + device requires answering a prompt, it is possible to pass + a dict containing I(command), I(answer) and I(prompt). + Common answers are 'y' or "\\r" (carriage return, must be + double quotes). See examples. + type: list + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + type: str + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + type: int + default: 1 +notes: + - Tested against IPOS 19.3 + - For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide ` + - For more information on using Ansible to manage Ericsson devices see the Ericsson documents. + - "Starting with Ansible 2.5 we recommend using C(connection: network_cli)." + - For more information please see the L(ERIC_ECCLI Platform Options guide,../network/user_guide/platform_eric_eccli.html). +""" + +EXAMPLES = r""" +tasks: + - name: Run show version on remote devices + community.network.eric_eccli_command: + commands: show version + + - name: Run show version and check to see if output contains IPOS + community.network.eric_eccli_command: + commands: show version + wait_for: result[0] contains IPOS + + - name: Run multiple commands on remote nodes + community.network.eric_eccli_command: + commands: + - show version + - show running-config interfaces + + - name: Run multiple commands and evaluate the output + community.network.eric_eccli_command: + commands: + - show version + - show running-config interfaces + wait_for: + - result[0] contains IPOS + - result[1] contains management +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.eric_eccli.eric_eccli import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + for item in list(commands): + if module.check_mode: + if item['command'].startswith('conf'): + warnings.append( + 'only non-config commands are supported when using check mode, not ' + 'executing %s' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list() + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_command.py new file mode 100644 index 00000000..eeea2f8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_command.py @@ -0,0 +1,215 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: exos_command +author: "Rafael D. Vencioneck (@rdvencioneck)" +short_description: Run commands on remote devices running Extreme EXOS +description: + - Sends arbitrary commands to an Extreme EXOS device and returns the results + read from the device. This module includes an argument that will cause the + module to wait for a specific condition before returning or timing out if + the condition is not met. + - This module does not support running configuration commands. + Please use M(community.network.exos_config) to configure EXOS devices. +notes: + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote EXOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.exos_command: + commands: show version + - name: Run show version and check to see if output contains ExtremeXOS + community.network.exos_command: + commands: show version + wait_for: result[0] contains ExtremeXOS + - name: Run multiple commands on remote nodes + community.network.exos_command: + commands: + - show version + - show ports no-refresh + - name: Run multiple commands and evaluate the output + community.network.exos_command: + commands: + - show version + - show ports no-refresh + wait_for: + - result[0] contains ExtremeXOS + - result[1] contains 20 + - name: Run command that requires answering a prompt + community.network.exos_command: + commands: + - command: 'clear license-info' + prompt: 'Are you sure.*' + answer: 'Yes' +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + command_split = re.match(r'^(\w*)(.*)$', item['command']) + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + elif command_split and command_split.group(1) not in ('check', 'clear', 'debug', 'history', + 'ls', 'mrinfo', 'mtrace', 'nslookup', + 'ping', 'rtlookup', 'show', 'traceroute'): + module.fail_json( + msg='some commands were not recognized. exos_command can only run read-only' + 'commands. For configuration commands, please use exos_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not be satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_config.py new file mode 100644 index 00000000..77e5f585 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_config.py @@ -0,0 +1,431 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: exos_config +author: "Lance Richardson (@hlrichardson)" +short_description: Manage Extreme Networks EXOS configuration sections +description: + - Extreme EXOS configurations use a simple flat text file syntax. + This module provides an implementation for working with EXOS + configuration lines in a deterministic way. +notes: + - Tested against EXOS version 22.6.0b19 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that behavior. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config. If the argument is set to I(changed), then the running-config + will only be copied to the startup-config if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + default: running + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure SNMP system name + community.network.exos_config: + lines: configure snmp sysName "{{ inventory_hostname }}" + +- name: Configure interface settings + community.network.exos_config: + lines: + - configure ports 2 description-string "Master Uplink" + backup: yes + +- name: Check the running-config against master config + community.network.exos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.exos_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: Save running to startup when modified + community.network.exos_config: + save_when: modified + +- name: Configurable backup path + community.network.exos_config: + lines: + - configure ports 2 description-string "Master Uplink" + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['create vlan "foo"', 'configure snmp sysName "x620-red"'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/x870_config.2018-08-08@15:00:21 + +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.exos.exos import run_commands, get_config, load_config, get_diff +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +__metaclass__ = type + + +def get_running_config(module, current_config=None, flags=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module, flags=flags) + return contents + + +def get_startup_config(module, flags=None): + reply = run_commands(module, {'command': 'show switch', 'output': 'text'}) + match = re.search(r'Config Selected: +(\S+)\.cfg', to_text(reply, errors='surrogate_or_strict').strip(), re.MULTILINE) + if match: + cfgname = match.group(1).strip() + command = ' '.join(['debug cfgmgr show configuration file', cfgname]) + if flags: + command += ' '.join(to_list(flags)).strip() + reply = run_commands(module, {'command': command, 'output': 'text'}) + data = reply[0] + else: + data = '' + return data + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + candidate.add(module.params['lines']) + candidate = dumps(candidate, 'raw') + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "save configuration", + "prompt": "Do you want to save configuration", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `save configuration` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running'], default='running'), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + if warnings: + result['warnings'] = warnings + + config = None + flags = ['detail'] if module.params['defaults'] else [] + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + + candidate = get_candidate(module) + running = get_running_config(module, config) + + try: + response = get_diff(module, candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response.get('config_diff') + + if config_diff: + commands = config_diff.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + running = get_running_config(module) + startup = get_startup_config(module) + + running_config = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=startup, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + contents = get_running_config(module) + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + contents = get_startup_config(module) + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_facts.py new file mode 100644 index 00000000..174463dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_facts.py @@ -0,0 +1,184 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: exos_facts +author: + - "Lance Richardson (@hlrichardson)" + - "Ujwal Koamrla (@ujwalkomarla)" +short_description: Collect facts from devices running Extreme EXOS +description: + - Collects a base set of device facts from a remote device that + is running EXOS. This module prepends all of the base network + fact keys with C(ansible_net_). The facts module will + always collect a base set of facts from the device and can + enable or disable collection of additional facts. +notes: + - Tested against EXOS 22.5.1.7 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + type: list + default: ['!config'] + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like interfaces, vlans etc. + Can specify a list of values to include a larger subset. + Values can also be used with an initial C(!) to specify that + a specific subset should not be collected. + Valid subsets are 'all', 'lldp_global'. + type: list +''' + +EXAMPLES = """ + - name: Gather all legacy facts + community.network.exos_facts: + gather_subset: all + + - name: Gather only the config and default facts + community.network.exos_facts: + gather_subset: config + + - name: Do not gather hardware facts + community.network.exos_facts: + gather_subset: "!hardware" + + - name: Gather legacy and resource facts + community.network.exos_facts: + gather_subset: all + gather_network_resources: all + + - name: Gather only the lldp global resource facts and no legacy facts + community.network.exos_facts: + gather_subset: + - '!all' + - '!min' + gather_network_resource: + - lldp_global + + - name: Gather lldp global resource and minimal legacy facts + community.network.exos_facts: + gather_subset: min + gather_network_resource: lldp_global +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +ansible_net_gather_network_resources: + description: The list of fact for network resource subsets collected from the device + returned: when the resource is configured + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.facts.facts import FactsArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.facts.facts import Facts + + +def main(): + """Main entry point for AnsibleModule + """ + argument_spec = FactsArgs.argument_spec + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = ['default value for `gather_subset` ' + 'will be changed to `min` from `!config` in community.network 2.0.0 onwards'] + + result = Facts(module).get_facts() + + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_l2_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_l2_interfaces.py new file mode 100644 index 00000000..2fc82fef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_l2_interfaces.py @@ -0,0 +1,1131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for exos_l2_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_l2_interfaces +version_added: '0.2.0' +short_description: Manage L2 interfaces on Extreme Networks EXOS devices. +description: This module provides declarative management of L2 interfaces on Extreme Networks EXOS network devices. +author: Jayalakshmi Viswanathan (@jayalakshmiV) +notes: + - Tested against EXOS 30.2.1.8 + - This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of L2 interfaces options + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface + type: str + required: True + access: + description: + - Switchport mode access command to configure the interface as a layer 2 access. + type: dict + suboptions: + vlan: + description: + - Configure given VLAN in access port. It's used as the access VLAN ID. + type: int + trunk: + description: + - Switchport mode trunk command to configure the interface as a Layer 2 trunk. + type: dict + suboptions: + native_vlan: + description: + - Native VLAN to be configured in trunk port. It's used as the trunk native VLAN ID. + type: int + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. These are the only VLANs that will be configured on the trunk. + type: list + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Delete L2 interface configuration for the given arguments + community.network.exos_l2_interfaces: + config: + - name: '3' + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using deleted without any config passed +#"(NOTE: This will delete all of configured resource module attributes from each configured interface)" + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Delete L2 interface configuration for the given arguments + community.network.exos_l2_interfaces: + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using merged + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# }, +# } +# } +# }, +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 10 + name: '1' + - name: '2' + trunk: + trunk_allowed_vlans: 10 + - name: '3' + trunk: + native_vlan: 10 + trunk_allowed_vlans: 20 + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 10, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "trunk-vlans": [10], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "native-vlan": 10, +# "trunk-vlans": [20], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20 +# ] +# } +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20 +# ] +# } +# } +# } +# }, +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Overrride device configuration of all L2 interfaces with provided configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 10 + name: '2' + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 10, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 1, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 1 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 10 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 1 +# }, +# "name": "3", +# "trunk": null +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 1 +# } +# } +# } +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 10 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 20 +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 1, +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# } +# ] +# } +# } + +- name: Replace device configuration of listed L2 interfaces with provided configuration + community.network.exos_l2_interfaces: + config: + - access: + vlan: 20 + name: '1' + - name: '2' + trunk: + trunk_allowed_vlans: 10 + - name: '3' + trunk: + native_vlan: 10 + trunk_allowed_vlan: 20,30 + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "access": { +# "vlan": 10 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": { +# "vlan": 20 +# }, +# "name": "2", +# "trunk": null +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 1, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:config": { +# "access-vlan": 20, +# "interface-mode": "ACCESS" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=1/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "trunk-vlans": [10], +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=2/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# }, +# { +# "data": { +# "openconfig-vlan:config": { +# "native-vlan": 10, +# "trunk-vlans": [20, 30] +# "interface-mode": "TRUNK" +# } +# } +# "method": "PATCH", +# "path": "rest/restconf/data/openconfig-interfaces:interfaces/interface=3/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" +# } +# ], +# +# "after": [ +# { +# "access": { +# "vlan": 20 +# }, +# "name": "1", +# "trunk": null +# }, +# { +# "access": null, +# "name": "2", +# "trunk": { +# "native_vlan": null, +# "trunk_allowed_vlans": [ +# 10 +# ] +# } +# }, +# { +# "access": null, +# "name": "3", +# "trunk": { +# "native_vlan": 10, +# "trunk_allowed_vlans": [ +# 20, +# 30 +# ] +# } +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-interfaces:interfaces/ +# method: GET +# data: +# { +# "openconfig-interfaces:interfaces": { +# "interface": [ +# { +# "name": "1", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "ACCESS", +# "access-vlan": 20 +# } +# } +# } +# }, +# { +# "name": "2", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "trunk-vlans": [ +# 10 +# ] +# } +# } +# } +# }, +# { +# "name": "3", +# "openconfig-if-ethernet:ethernet": { +# "openconfig-vlan:switched-vlan": { +# "config": { +# "interface-mode": "TRUNK", +# "native-vlan": 10, +# "trunk-vlans": [ +# 20, +# 30 +# ] +# } +# } +# } +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.l2_interfaces.l2_interfaces import L2_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config', )), + ('state', 'replaced', ('config', ))] + module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = L2_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_global.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_global.py new file mode 100644 index 00000000..94c6f1ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_global.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for exos_lldp_global +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_lldp_global +short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on EXOS platforms. +description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on Extreme Networks EXOS platforms. +author: Ujwal Komarla (@ujwalkomarla) +notes: +- Tested against Extreme Networks EXOS version 30.2.1.8 on x460g2. +- This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of LLDP options + type: dict + suboptions: + interval: + description: + - Frequency at which LLDP advertisements are sent (in seconds). By default - 30 seconds. + type: int + default: 30 + tlv_select: + description: + - This attribute can be used to specify the TLVs that need to be sent in the LLDP packets. By default, only system name and system description is sent + type: dict + suboptions: + management_address: + description: + - Used to specify the management address in TLV messages + type: bool + port_description: + description: + - Used to specify the port description TLV + type: bool + system_capabilities: + description: + - Used to specify the system capabilities TLV + type: bool + system_description: + description: + - Used to specify the system description TLV + type: bool + default: true + system_name: + description: + - Used to specify the system name TLV + type: bool + default: true + + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - deleted + default: merged +''' +EXAMPLES = """ +# Using merged + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Merge provided LLDP configuration with device configuration + community.network.exos_lldp_global: + config: + interval: 10000 + tlv_select: + system_capabilities: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": false, +# "management_address": false, +# "system_capabilities": true +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +# Using replaced + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "PORT_DESCRIPTION", +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Replace device configuration with provided LLDP configuration + community.network.exos_lldp_global: + config: + interval: 10000 + tlv_select: + system_capabilities: true + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_NAME", +# "SYSTEM_DESCRIPTION", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": false, +# "system_description": false, +# "port_description": false, +# "management_address": false, +# "system_capabilities": true +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_NAME", +# "SYSTEM_DESCRIPTION", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +# Using deleted + + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 10000, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + +- name: Delete attributes of given LLDP service (This won't delete the LLDP service itself) + community.network.exos_lldp_global: + config: + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": [ +# { +# "interval": 10000, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": true, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] +# +# "requests": [ +# { +# "data": { +# "openconfig_lldp:config": { +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ] +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig_lldp:lldp/config" +# } +# ] +# +# "after": [ +# { +# "interval": 30, +# "tlv_select": { +# "system_name": true, +# "system_description": true, +# "port_description": false, +# "management_address": false, +# "system_capabilities": false +# } +# } +# ] + + +# After state: +# ------------- +# path: /rest/restconf/data/openconfig_lldp:lldp/config +# method: GET +# data: +# { +# "openconfig_lldp:config": { +# "enabled": true, +# "hello-timer": 30, +# "suppress-tlv-advertisement": [ +# "SYSTEM_CAPABILITIES", +# "PORT_DESCRIPTION", +# "MANAGEMENT_ADDRESS" +# ], +# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8" +# "system-name": "X460G2-24t-10G4" +# } +# } + + +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The configuration as structured data after module completion. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_global.lldp_global import Lldp_globalArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.lldp_global.lldp_global import Lldp_global + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',))] + module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = Lldp_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_interfaces.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_interfaces.py new file mode 100644 index 00000000..01a7a760 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_lldp_interfaces.py @@ -0,0 +1,674 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for exos_lldp_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_lldp_interfaces +version_added: '0.2.0' +short_description: Manage link layer discovery protocol (LLDP) attributes of interfaces on EXOS platforms. +description: + - This module manages link layer discovery protocol (LLDP) attributes of interfaces on Extreme Networks EXOS platforms. +author: Jayalakshmi Viswanathan (@JayalakshmiV) +options: + config: + description: The list of link layer discovery protocol interface attribute configurations + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface LLDP needs to be configured on. + type: str + required: True + enabled: + description: + - This is a boolean value to control disabling of LLDP on the interface C(name) + type: bool + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_lldp_interfaces: + config: + - name: '2' + enabled: false + - name: '5' + enabled: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": false, +# "name": "2" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=2/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "5" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=5/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: False +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: True + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "5" +# } +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Replaces device configuration of listed lldp_interfaces with provided configuration + community.network.exos_lldp_interfaces: + config: + - name: '1' + enabled: false + - name: '3' + enabled: true + state: merged + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": false, +# "name": "1" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=1/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: False +# - name: '2' +# enabled: True +# - name: '3' +# enabled: True +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": false, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + + +# Using deleted + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": false, +# "name": "1" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# }, +# } +# ] +# } +# } + +- name: Delete lldp interface configuration (this will not delete other lldp configuration) + community.network.exos_lldp_interfaces: + config: + - name: '1' + - name: '3' + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: False +# - name: '2' +# enabled: False +# - name: '3' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "1" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=1/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: False +# - name: '3' +# enabled: True +# +# After state: +# ------------- +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# }, +# }, +# { +# "config": { +# "enabled": false, +# "name": "2" +# }, +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# }, +# } +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": false, +# "name": "5" +# } +# } +# ] +# } +# } + +- name: Override device configuration of all lldp_interfaces with provided configuration + community.network.exos_lldp_interfaces: + config: + - name: '3' + enabled: true + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "before": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: False +# - name: '4' +# enabled: True +# - name: '5' +# enabled: False +# +# "requests": [ +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "5" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=5/config" +# }, +# { +# "data": | +# { +# "openconfig-lldp:config": { +# "enabled": true, +# "name": "3" +# } +# } +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface=3/config" +# } +# ] +# +# "after": +# - name: '1' +# enabled: True +# - name: '2' +# enabled: True +# - name: '3' +# enabled: True +# - name: '4' +# enabled: True +# - name: '5' +# enabled: True + +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4 +# method: GET +# data: +# { +# "openconfig-lldp:interfaces": { +# "interface": [ +# { +# "config": { +# "enabled": true, +# "name": "1" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "2" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "3" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "4" +# } +# }, +# { +# "config": { +# "enabled": true, +# "name": "5" +# } +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.lldp_interfaces.lldp_interfaces import Lldp_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config', )), + ('state', 'replaced', ('config', ))] + module = AnsibleModule(argument_spec=Lldp_interfacesArgs.argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = Lldp_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_vlans.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_vlans.py new file mode 100644 index 00000000..276ba92d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/exos/exos_vlans.py @@ -0,0 +1,753 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for exos_vlans +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: exos_vlans +version_added: '0.2.0' +short_description: Manage VLANs on Extreme Networks EXOS devices. +description: This module provides declarative management of VLANs on Extreme Networks EXOS network devices. +author: Jayalakshmi Viswanathan (@jayalakshmiV) +notes: + - Tested against EXOS 30.2.1.8 + - This module works with connection C(httpapi). + See L(EXOS Platform Options,../network/user_guide/platform_exos.html) +options: + config: + description: A dictionary of VLANs options + type: list + elements: dict + suboptions: + name: + description: + - Ascii name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094 + type: int + required: True + state: + description: + - Operational state of the VLAN + type: str + choices: + - active + - suspend + default: active + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +''' +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Delete attributes of given VLANs + community.network.exos_vlans: + config: + - vlan_id: 10 + - vlan_id: 20 + - vlan_id: 30 + state: deleted + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# } +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=10" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=20" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=30" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# } +# ] +# } +# } + + +# Using merged + +# Before state: +# ------------- +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# } +# ] +# } +# } + +- name: Merge provided configuration with device configuration + community.network.exos_vlans: + config: + - name: vlan_10 + vlan_id: 10 + state: active + - name: vlan_20 + vlan_id: 20 + state: active + - name: vlan_30 + vlan_id: 30 + state: active + state: merged + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# { +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# "data": { +# "openconfig-vlan:vlan": [ +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# } +# } +# ] +# }, +# "method": "POST", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + + +# Using overridden + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Override device configuration of all VLANs with provided configuration + community.network.exos_vlans: + config: + - name: TEST_VLAN10 + vlan_id: 10 + state: overridden + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "TEST_VLAN10", +# "state": "active", +# "vlan_id": 10 +# }, +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": { +# "vlan": [ +# { +# "config": { +# "name": "TEST_VLAN10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# } +# } +# ] +# } +# } +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=20" +# }, +# { +# "data": null, +# "method": "DELETE", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/vlan=30" +# } +# ] +# +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# } +# ] +# } +# } + + +# Using replaced + +# Before state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "vlan_20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "vlan_30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + +- name: Replaces device configuration of listed VLANs with provided configuration + community.network.exos_vlans: + config: + - name: Test_VLAN20 + vlan_id: 20 + - name: Test_VLAN30 + vlan_id: 30 + state: replaced + +# Module Execution Results: +# ------------------------- +# +# "after": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "TEST_VLAN20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "TEST_VLAN30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "before": [ +# { +# "name": "Default", +# "state": "active", +# "vlan_id": 1 +# }, +# { +# "name": "vlan_10", +# "state": "active", +# "vlan_id": 10 +# }, +# { +# "name": "vlan_20", +# "state": "active", +# "vlan_id": 20 +# }, +# { +# "name": "vlan_30", +# "state": "active", +# "vlan_id": 30 +# } +# ], +# +# "requests": [ +# { +# "data": { +# "openconfig-vlan:vlan": { +# "vlan": [ +# { +# "config": { +# "name": "TEST_VLAN20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# } +# "config": { +# "name": "TEST_VLAN30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# } +# } +# ] +# }, +# "method": "PATCH", +# "path": "/rest/restconf/data/openconfig-vlan:vlans/" +# } +# ] +# +# After state: +# ------------- +# +# path: /rest/restconf/data/openconfig-vlan:vlans/ +# method: GET +# data: +# { +# "openconfig-vlan:vlans": { +# "vlan": [ +# { +# "config": { +# "name": "Default", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 1 +# }, +# }, +# { +# "config": { +# "name": "vlan_10", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 10 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN20", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 20 +# }, +# }, +# { +# "config": { +# "name": "TEST_VLAN30", +# "status": "ACTIVE", +# "tpid": "oc-vlan-types:TPID_0x8100", +# "vlan-id": 30 +# }, +# } +# ] +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +requests: + description: The set of requests pushed to the remote device. + returned: always + type: list + sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.exos.argspec.vlans.vlans import VlansArgs +from ansible_collections.community.network.plugins.module_utils.network.exos.config.vlans.vlans import Vlans + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',))] + module = AnsibleModule(argument_spec=VlansArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = Vlans(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortianalyzer/faz_device.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortianalyzer/faz_device.py new file mode 100644 index 00000000..b6dff85c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortianalyzer/faz_device.py @@ -0,0 +1,432 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: faz_device +author: Luke Weighall (@lweighall) +short_description: Add or remove device +description: + - Add or remove a device or list of devices to FortiAnalyzer Device Manager. ADOM Capable. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + default: root + type: str + + mode: + description: + - Add or delete devices. Or promote unregistered devices that are in the FortiAnalyzer "waiting pool" + required: false + default: add + choices: ["add", "delete", "promote"] + type: str + + device_username: + description: + - The username of the device being added to FortiAnalyzer. + required: false + type: str + + device_password: + description: + - The password of the device being added to FortiAnalyzer. + required: false + type: str + + device_ip: + description: + - The IP of the device being added to FortiAnalyzer. + required: false + type: str + + device_unique_name: + description: + - The desired "friendly" name of the device being added to FortiAnalyzer. + required: false + type: str + + device_serial: + description: + - The serial number of the device being added to FortiAnalyzer. + required: false + type: str + + os_type: + description: + - The os type of the device being added (default 0). + required: true + choices: ["unknown", "fos", "fsw", "foc", "fml", "faz", "fwb", "fch", "fct", "log", "fmg", "fsa", "fdd", "fac"] + type: str + + mgmt_mode: + description: + - Management Mode of the device you are adding. + choices: ["unreg", "fmg", "faz", "fmgfaz"] + required: true + type: str + + os_minor_vers: + description: + - Minor OS rev of the device. + required: true + type: str + + os_ver: + description: + - Major OS rev of the device + required: true + choices: ["unknown", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0"] + type: str + + platform_str: + description: + - Required for determine the platform for VM platforms. ie FortiGate-VM64 + required: false + type: str + + faz_quota: + description: + - Specifies the quota for the device in FAZ + required: False + type: str +''' + +EXAMPLES = ''' +- name: DISCOVER AND ADD DEVICE A PHYSICAL FORTIGATE + community.network.faz_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.201" + device_unique_name: "FGT1" + device_serial: "FGVM000000117994" + state: "present" + mgmt_mode: "faz" + os_type: "fos" + os_ver: "5.0" + minor_rev: 6 + + +- name: DISCOVER AND ADD DEVICE A VIRTUAL FORTIGATE + community.network.faz_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.202" + device_unique_name: "FGT2" + mgmt_mode: "faz" + os_type: "fos" + os_ver: "5.0" + minor_rev: 6 + state: "present" + platform_str: "FortiGate-VM64" + +- name: DELETE DEVICE FGT01 + community.network.faz_device: + adom: "root" + device_unique_name: "ansible-fgt01" + mode: "delete" + +- name: DELETE DEVICE FGT02 + community.network.faz_device: + adom: "root" + device_unique_name: "ansible-fgt02" + mode: "delete" + +- name: PROMOTE FGT01 IN FAZ BY IP + community.network.faz_device: + adom: "root" + device_password: "fortinet" + device_ip: "10.7.220.151" + device_username: "ansible" + mgmt_mode: "faz" + mode: "promote" + + +- name: PROMOTE FGT02 IN FAZ + community.network.faz_device: + adom: "root" + device_password: "fortinet" + device_unique_name: "ansible-fgt02" + device_username: "ansible" + mgmt_mode: "faz" + mode: "promote" + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.fortianalyzer import FortiAnalyzerHandler +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZBaseException +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZCommon +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAZMethods +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import DEFAULT_RESULT_OBJ +from ansible_collections.community.network.plugins.module_utils.network.fortianalyzer.common import FAIL_SOCKET_MSG + + +def faz_add_device(faz, paramgram): + """ + This method is used to add devices to the faz or delete them + """ + + datagram = { + "adom": paramgram["adom"], + "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"], + "ip": paramgram["ip"], "name": paramgram["device_unique_name"], + "mgmt_mode": paramgram["mgmt_mode"], "os_type": paramgram["os_type"], + "mr": paramgram["os_minor_vers"]} + } + + if paramgram["platform_str"] is not None: + datagram["device"]["platform_str"] = paramgram["platform_str"] + + if paramgram["sn"] is not None: + datagram["device"]["sn"] = paramgram["sn"] + + if paramgram["device_action"] is not None: + datagram["device"]["device_action"] = paramgram["device_action"] + + if paramgram["faz.quota"] is not None: + datagram["device"]["faz.quota"] = paramgram["faz.quota"] + + url = '/dvm/cmd/add/device/' + response = faz.process_request(url, datagram, FAZMethods.EXEC) + return response + + +def faz_delete_device(faz, paramgram): + """ + This method deletes a device from the FAZ + """ + datagram = { + "adom": paramgram["adom"], + "device": paramgram["device_unique_name"], + } + + url = '/dvm/cmd/del/device/' + response = faz.process_request(url, datagram, FAZMethods.EXEC) + return response + + +def faz_get_unknown_devices(faz): + """ + This method gets devices with an unknown management type field + """ + + faz_filter = ["mgmt_mode", "==", "0"] + + datagram = { + "filter": faz_filter + } + + url = "/dvmdb/device" + response = faz.process_request(url, datagram, FAZMethods.GET) + + return response + + +def faz_approve_unregistered_device_by_ip(faz, paramgram): + """ + This method approves unregistered devices by ip. + """ + # TRY TO FIND DETAILS ON THIS UNREGISTERED DEVICE + unknown_devices = faz_get_unknown_devices(faz) + target_device = None + if unknown_devices[0] == 0: + for device in unknown_devices[1]: + if device["ip"] == paramgram["ip"]: + target_device = device + else: + return "No devices are waiting to be registered!" + + # now that we have the target device details...fill out the datagram and make the call to promote it + if target_device is not None: + target_device_paramgram = { + "adom": paramgram["adom"], + "ip": target_device["ip"], + "device_username": paramgram["device_username"], + "device_password": paramgram["device_password"], + "device_unique_name": paramgram["device_unique_name"], + "sn": target_device["sn"], + "os_type": target_device["os_type"], + "mgmt_mode": paramgram["mgmt_mode"], + "os_minor_vers": target_device["mr"], + "os_ver": target_device["os_ver"], + "platform_str": target_device["platform_str"], + "faz.quota": target_device["faz.quota"], + "device_action": paramgram["device_action"] + } + + add_device = faz_add_device(faz, target_device_paramgram) + return add_device + + return str("Couldn't find the desired device with ip: " + str(paramgram["device_ip"])) + + +def faz_approve_unregistered_device_by_name(faz, paramgram): + # TRY TO FIND DETAILS ON THIS UNREGISTERED DEVICE + unknown_devices = faz_get_unknown_devices(faz) + target_device = None + if unknown_devices[0] == 0: + for device in unknown_devices[1]: + if device["name"] == paramgram["device_unique_name"]: + target_device = device + else: + return "No devices are waiting to be registered!" + + # now that we have the target device details...fill out the datagram and make the call to promote it + if target_device is not None: + target_device_paramgram = { + "adom": paramgram["adom"], + "ip": target_device["ip"], + "device_username": paramgram["device_username"], + "device_password": paramgram["device_password"], + "device_unique_name": paramgram["device_unique_name"], + "sn": target_device["sn"], + "os_type": target_device["os_type"], + "mgmt_mode": paramgram["mgmt_mode"], + "os_minor_vers": target_device["mr"], + "os_ver": target_device["os_ver"], + "platform_str": target_device["platform_str"], + "faz.quota": target_device["faz.quota"], + "device_action": paramgram["device_action"] + } + + add_device = faz_add_device(faz, target_device_paramgram) + return add_device + + return str("Couldn't find the desired device with name: " + str(paramgram["device_unique_name"])) + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "delete", "promote"], type="str", default="add"), + + device_ip=dict(required=False, type="str"), + device_username=dict(required=False, type="str"), + device_password=dict(required=False, type="str", no_log=True), + device_unique_name=dict(required=False, type="str"), + device_serial=dict(required=False, type="str"), + + os_type=dict(required=False, type="str", choices=["unknown", "fos", "fsw", "foc", "fml", + "faz", "fwb", "fch", "fct", "log", "fmg", + "fsa", "fdd", "fac"]), + mgmt_mode=dict(required=False, type="str", choices=["unreg", "fmg", "faz", "fmgfaz"]), + os_minor_vers=dict(required=False, type="str"), + os_ver=dict(required=False, type="str", choices=["unknown", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0"]), + platform_str=dict(required=False, type="str"), + faz_quota=dict(required=False, type="str") + ) + + required_if = [ + ['mode', 'delete', ['device_unique_name']], + ['mode', 'add', ['device_serial', 'device_username', + 'device_password', 'device_unique_name', 'device_ip', 'mgmt_mode', 'platform_str']] + + ] + + module = AnsibleModule(argument_spec, supports_check_mode=True, required_if=required_if, ) + + # START SESSION LOGIC + paramgram = { + "adom": module.params["adom"], + "mode": module.params["mode"], + "ip": module.params["device_ip"], + "device_username": module.params["device_username"], + "device_password": module.params["device_password"], + "device_unique_name": module.params["device_unique_name"], + "sn": module.params["device_serial"], + "os_type": module.params["os_type"], + "mgmt_mode": module.params["mgmt_mode"], + "os_minor_vers": module.params["os_minor_vers"], + "os_ver": module.params["os_ver"], + "platform_str": module.params["platform_str"], + "faz.quota": module.params["faz_quota"], + "device_action": None + } + # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO + + if paramgram["mode"] == "add": + paramgram["device_action"] = "add_model" + elif paramgram["mode"] == "promote": + paramgram["device_action"] = "promote_unreg" + module.paramgram = paramgram + + # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS + faz = None + if module._socket_path: + connection = Connection(module._socket_path) + faz = FortiAnalyzerHandler(connection, module) + faz.tools = FAZCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["mode"] == "add": + results = faz_add_device(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to add the device. Error: " + str(err)) + + try: + if paramgram["mode"] == "promote": + if paramgram["ip"] is not None: + results = faz_approve_unregistered_device_by_ip(faz, paramgram) + elif paramgram["device_unique_name"] is not None: + results = faz_approve_unregistered_device_by_name(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to promote the device. Error: " + str(err)) + + try: + if paramgram["mode"] == "delete": + results = faz_delete_device(faz, paramgram) + except BaseException as err: + raise FAZBaseException(msg="An error occurred trying to delete the device. Error: " + str(err)) + + # PROCESS RESULTS + try: + faz.govern_response(module=module, results=results, + ansible_facts=faz.construct_ansible_facts(results, module.params, paramgram)) + except BaseException as err: + raise FAZBaseException(msg="An error occurred with govern_response(). Error: " + str(err)) + + # This should only be hit if faz.govern_response is missed or failed somehow. In fact. It should never be hit. + # But it's here JIC. + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device.py new file mode 100644 index 00000000..701ff882 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Add or remove device from FortiManager. +description: + - Add or remove a device or list of devices from FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + default: root + + mode: + description: + - The desired mode of the specified object. + required: false + default: add + choices: ["add", "delete"] + + blind_add: + description: + - When adding a device, module will check if it exists, and skip if it does. + - If enabled, this option will stop the module from checking if it already exists, and blindly add the device. + required: false + default: "disable" + choices: ["enable", "disable"] + + device_username: + description: + - The username of the device being added to FortiManager. + required: false + + device_password: + description: + - The password of the device being added to FortiManager. + required: false + + device_ip: + description: + - The IP of the device being added to FortiManager. Supports both IPv4 and IPv6. + required: false + + device_unique_name: + description: + - The desired "friendly" name of the device being added to FortiManager. + required: false + + device_serial: + description: + - The serial number of the device being added to FortiManager. + required: false +''' + +EXAMPLES = ''' +- name: DISCOVER AND ADD DEVICE FGT1 + community.network.fmgr_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.201" + device_unique_name: "FGT1" + device_serial: "FGVM000000117994" + mode: "add" + blind_add: "enable" + +- name: DISCOVER AND ADD DEVICE FGT2 + community.network.fmgr_device: + adom: "root" + device_username: "admin" + device_password: "admin" + device_ip: "10.10.24.202" + device_unique_name: "FGT2" + device_serial: "FGVM000000117992" + mode: "delete" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def discover_device(fmgr, paramgram): + """ + This method is used to discover devices before adding them to FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "odd_request_form": "True", + "device": {"adm_usr": paramgram["device_username"], + "adm_pass": paramgram["device_password"], + "ip": paramgram["device_ip"]} + } + + url = '/dvm/cmd/discover/device/' + + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def add_device(fmgr, paramgram): + """ + This method is used to add devices to the FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "adom": paramgram["adom"], + "flags": ["create_task", "nonblocking"], + "odd_request_form": "True", + "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"], + "ip": paramgram["device_ip"], "name": paramgram["device_unique_name"], + "sn": paramgram["device_serial"], "mgmt_mode": "fmgfaz", "flags": 24} + } + + url = '/dvm/cmd/add/device/' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def delete_device(fmgr, paramgram): + """ + This method deletes a device from the FMGR + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "adom": paramgram["adom"], + "flags": ["create_task", "nonblocking"], + "device": paramgram["device_unique_name"], + } + + url = '/dvm/cmd/del/device/' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def get_device(fmgr, paramgram): + """ + This method attempts to find the firewall on FortiManager to see if it already exists. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "adom": paramgram["adom"], + "filter": ["name", "==", paramgram["device_unique_name"]], + } + + url = '/dvmdb/adom/{adom}/device/{name}'.format(adom=paramgram["adom"], + name=paramgram["device_unique_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "delete"], type="str", default="add"), + blind_add=dict(choices=["enable", "disable"], type="str", default="disable"), + device_ip=dict(required=False, type="str"), + device_username=dict(required=False, type="str"), + device_password=dict(required=False, type="str", no_log=True), + device_unique_name=dict(required=True, type="str"), + device_serial=dict(required=False, type="str") + ) + + # BUILD MODULE OBJECT SO WE CAN BUILD THE PARAMGRAM + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + + # BUILD THE PARAMGRAM + paramgram = { + "device_ip": module.params["device_ip"], + "device_username": module.params["device_username"], + "device_password": module.params["device_password"], + "device_unique_name": module.params["device_unique_name"], + "device_serial": module.params["device_serial"], + "adom": module.params["adom"], + "mode": module.params["mode"] + } + + # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO + module.paramgram = paramgram + + # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["mode"] == "add": + # CHECK IF DEVICE EXISTS + if module.params["blind_add"] == "disable": + exists_results = get_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=exists_results, good_codes=(0, -3), changed=False, + ansible_facts=fmgr.construct_ansible_facts(exists_results, + module.params, paramgram)) + + discover_results = discover_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=discover_results, stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(discover_results, + module.params, paramgram)) + + if discover_results[0] == 0: + results = add_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=discover_results, stop_on_success=True, + changed_if_success=True, + ansible_facts=fmgr.construct_ansible_facts(discover_results, + module.params, paramgram)) + + if paramgram["mode"] == "delete": + results = delete_device(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_config.py new file mode 100644 index 00000000..f9183d9c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_config.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_config +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Edit device configurations +description: + - Edit device configurations from FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + device_unique_name: + description: + - The unique device's name that you are editing. A.K.A. Friendly name of the device in FortiManager. + required: True + + device_hostname: + description: + - The device's new hostname. + required: false + + install_config: + description: + - Tells FMGR to attempt to install the config after making it. + required: false + default: disable + + interface: + description: + - The interface/port number you are editing. + required: false + + interface_ip: + description: + - The IP and subnet of the interface/port you are editing. + required: false + + interface_allow_access: + description: + - Specify what protocols are allowed on the interface, comma-separated list (see examples). + required: false +''' + +EXAMPLES = ''' +- name: CHANGE HOSTNAME + community.network.fmgr_device_config: + device_hostname: "ChangedbyAnsible" + device_unique_name: "FGT1" + +- name: EDIT INTERFACE INFORMATION + community.network.fmgr_device_config: + adom: "root" + device_unique_name: "FGT2" + interface: "port3" + interface_ip: "10.1.1.1/24" + interface_allow_access: "ping, telnet, https" + +- name: INSTALL CONFIG + community.network.fmgr_device_config: + adom: "root" + device_unique_name: "FGT1" + install_config: "enable" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods + + +def update_device_hostname(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "hostname": paramgram["device_hostname"] + } + + url = "pm/config/device/{device_name}/global/system/global".format(device_name=paramgram["device_unique_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + return response + + +def update_device_interface(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + access_list = list() + allow_access_list = paramgram["interface_allow_access"].replace(' ', '') + access_list = allow_access_list.split(',') + + datagram = { + "allowaccess": access_list, + "ip": paramgram["interface_ip"] + } + + url = "/pm/config/device/{device_name}/global/system/interface" \ + "/{interface}".format(device_name=paramgram["device_unique_name"], interface=paramgram["interface"]) + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + return response + + +def exec_config(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + datagram = { + "scope": { + "name": paramgram["device_unique_name"] + }, + "adom": paramgram["adom"], + "flags": "none" + } + + url = "/securityconsole/install/device" + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + device_unique_name=dict(required=True, type="str"), + device_hostname=dict(required=False, type="str"), + interface=dict(required=False, type="str"), + interface_ip=dict(required=False, type="str"), + interface_allow_access=dict(required=False, type="str"), + install_config=dict(required=False, type="str", default="disable"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "device_unique_name": module.params["device_unique_name"], + "device_hostname": module.params["device_hostname"], + "interface": module.params["interface"], + "interface_ip": module.params["interface_ip"], + "interface_allow_access": module.params["interface_allow_access"], + "install_config": module.params["install_config"], + "adom": module.params["adom"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["device_hostname"] is not None: + results = update_device_hostname(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["interface_ip"] is not None or paramgram["interface_allow_access"] is not None: + results = update_device_interface(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["install_config"] == "enable": + results = exec_config(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_group.py new file mode 100644 index 00000000..148e38dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_group.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_group +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Alter FortiManager device groups. +description: + - Add or edit device groups and assign devices to device groups FortiManager Device Manager using JSON RPC API. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + vdom: + description: + - The VDOM of the Fortigate you want to add, must match the device in FMGR. Usually root. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + grp_name: + description: + - The name of the device group. + required: false + + grp_desc: + description: + - The description of the device group. + required: false + + grp_members: + description: + - A comma separated list of device names or device groups to be added as members to the device group. + - If Group Members are defined, and mode="delete", only group members will be removed. + - If you want to delete a group itself, you must omit this parameter from the task in playbook. + required: false + +''' + + +EXAMPLES = ''' +- name: CREATE DEVICE GROUP + community.network.fmgr_device_group: + grp_name: "TestGroup" + grp_desc: "CreatedbyAnsible" + adom: "ansible" + mode: "add" + +- name: CREATE DEVICE GROUP 2 + community.network.fmgr_device_group: + grp_name: "AnsibleGroup" + grp_desc: "CreatedbyAnsible" + adom: "ansible" + mode: "add" + +- name: ADD DEVICES TO DEVICE GROUP + community.network.fmgr_device_group: + mode: "add" + grp_name: "TestGroup" + grp_members: "FGT1,FGT2" + adom: "ansible" + vdom: "root" + +- name: REMOVE DEVICES TO DEVICE GROUP + community.network.fmgr_device_group: + mode: "delete" + grp_name: "TestGroup" + grp_members: "FGT1,FGT2" + adom: "ansible" + +- name: DELETE DEVICE GROUP + community.network.fmgr_device_group: + grp_name: "AnsibleGroup" + grp_desc: "CreatedbyAnsible" + mode: "delete" + adom: "ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def get_groups(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "method": "get" + } + + url = '/dvmdb/adom/{adom}/group'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def add_device_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + mode = paramgram["mode"] + + datagram = { + "name": paramgram["grp_name"], + "desc": paramgram["grp_desc"], + "os_type": "fos" + } + + url = '/dvmdb/adom/{adom}/group'.format(adom=paramgram["adom"]) + + # IF MODE = SET -- USE THE 'SET' API CALL MODE + if mode == "set": + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + # IF MODE = UPDATE -- USER THE 'UPDATE' API CALL MODE + elif mode == "update": + response = fmgr.process_request(url, datagram, FMGRMethods.UPDATE) + # IF MODE = ADD -- USE THE 'ADD' API CALL MODE + elif mode == "add": + response = fmgr.process_request(url, datagram, FMGRMethods.ADD) + + return response + + +def delete_device_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + + datagram = { + "adom": paramgram["adom"], + "name": paramgram["grp_name"] + } + + url = '/dvmdb/adom/{adom}/group/{grp_name}'.format(adom=paramgram["adom"], grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + return response + + +def add_group_member(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + device_member_list = paramgram["grp_members"].replace(' ', '') + device_member_list = device_member_list.split(',') + + for dev_name in device_member_list: + datagram = {'name': dev_name, 'vdom': paramgram["vdom"]} + + url = '/dvmdb/adom/{adom}/group/{grp_name}/object member'.format(adom=paramgram["adom"], + grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.ADD) + + return response + + +def delete_group_member(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + device_member_list = paramgram["grp_members"].replace(' ', '') + device_member_list = device_member_list.split(',') + + for dev_name in device_member_list: + datagram = {'name': dev_name, 'vdom': paramgram["vdom"]} + + url = '/dvmdb/adom/{adom}/group/{grp_name}/object member'.format(adom=paramgram["adom"], + grp_name=paramgram["grp_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + vdom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + grp_desc=dict(required=False, type="str"), + grp_name=dict(required=True, type="str"), + grp_members=dict(required=False, type="str"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "mode": module.params["mode"], + "grp_name": module.params["grp_name"], + "grp_desc": module.params["grp_desc"], + "grp_members": module.params["grp_members"], + "adom": module.params["adom"], + "vdom": module.params["vdom"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + # PROCESS THE GROUP ADDS FIRST + if paramgram["grp_name"] is not None and paramgram["mode"] in ["add", "set", "update"]: + # add device group + results = add_device_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP MEMBER ADDS + if paramgram["grp_members"] is not None and paramgram["mode"] in ["add", "set", "update"]: + # assign devices to device group + results = add_group_member(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP MEMBER DELETES + if paramgram["grp_members"] is not None and paramgram["mode"] == "delete": + # remove devices grom a group + results = delete_group_member(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE GROUP DELETES, ONLY IF GRP_MEMBERS IS NOT NULL TOO + if paramgram["grp_name"] is not None and paramgram["mode"] == "delete" and paramgram["grp_members"] is None: + # delete device group + results = delete_device_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_provision_template.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_provision_template.py new file mode 100644 index 00000000..52f4323a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_device_provision_template.py @@ -0,0 +1,1546 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_device_provision_template +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages Device Provisioning Templates in FortiManager. +description: + - Allows the editing and assignment of device provisioning templates in FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: true + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + device_unique_name: + description: + - The unique device's name that you are editing. + required: True + + provisioning_template: + description: + - The provisioning template you want to apply (default = default). + required: True + + provision_targets: + description: + - The friendly names of devices in FortiManager to assign the provisioning template to. CSV separated list. + required: True + + snmp_status: + description: + - Enables or disables SNMP globally. + required: False + choices: ["enable", "disable"] + + snmp_v2c_query_port: + description: + - Sets the snmp v2c community query port. + required: False + + snmp_v2c_trap_port: + description: + - Sets the snmp v2c community trap port. + required: False + + snmp_v2c_status: + description: + - Enables or disables the v2c community specified. + required: False + choices: ["enable", "disable"] + + snmp_v2c_trap_status: + description: + - Enables or disables the v2c community specified for traps. + required: False + choices: ["enable", "disable"] + + snmp_v2c_query_status: + description: + - Enables or disables the v2c community specified for queries. + required: False + choices: ["enable", "disable"] + + snmp_v2c_name: + description: + - Specifies the v2c community name. + required: False + + snmp_v2c_id: + description: + - Primary key for the snmp community. this must be unique! + required: False + + snmp_v2c_trap_src_ipv4: + description: + - Source ip the traps should come from IPv4. + required: False + + snmp_v2c_trap_hosts_ipv4: + description: > + - IPv4 addresses of the hosts that should get SNMP v2c traps, comma separated, must include mask + ("10.7.220.59 255.255.255.255, 10.7.220.60 255.255.255.255"). + required: False + + snmp_v2c_query_hosts_ipv4: + description: > + - IPv4 addresses or subnets that are allowed to query SNMP v2c, comma separated + ("10.7.220.59 255.255.255.0, 10.7.220.0 255.255.255.0"). + required: False + + snmpv3_auth_proto: + description: + - SNMPv3 auth protocol. + required: False + choices: ["md5", "sha"] + + snmpv3_auth_pwd: + description: + - SNMPv3 auth pwd __ currently not encrypted! ensure this file is locked down permissions wise! + required: False + + snmpv3_name: + description: + - SNMPv3 user name. + required: False + + snmpv3_notify_hosts: + description: + - List of ipv4 hosts to send snmpv3 traps to. Comma separated IPv4 list. + required: False + + snmpv3_priv_proto: + description: + - SNMPv3 priv protocol. + required: False + choices: ["aes", "des", "aes256", "aes256cisco"] + + snmpv3_priv_pwd: + description: + - SNMPv3 priv pwd currently not encrypted! ensure this file is locked down permissions wise! + required: False + + snmpv3_queries: + description: + - Allow snmpv3_queries. + required: False + choices: ["enable", "disable"] + + snmpv3_query_port: + description: + - SNMPv3 query port. + required: False + + snmpv3_security_level: + description: + - SNMPv3 security level. + required: False + choices: ["no-auth-no-priv", "auth-no-priv", "auth-priv"] + + snmpv3_source_ip: + description: + - SNMPv3 source ipv4 address for traps. + required: False + + snmpv3_status: + description: + - SNMPv3 user is enabled or disabled. + required: False + choices: ["enable", "disable"] + + snmpv3_trap_rport: + description: + - SNMPv3 trap remote port. + required: False + + snmpv3_trap_status: + description: + - SNMPv3 traps is enabled or disabled. + required: False + choices: ["enable", "disable"] + + syslog_port: + description: + - Syslog port that will be set. + required: False + + syslog_server: + description: + - Server the syslogs will be sent to. + required: False + + syslog_status: + description: + - Enables or disables syslogs. + required: False + choices: ["enable", "disable"] + + syslog_mode: + description: + - Remote syslog logging over UDP/Reliable TCP. + - choice | udp | Enable syslogging over UDP. + - choice | legacy-reliable | Enable legacy reliable syslogging by RFC3195 (Reliable Delivery for Syslog). + - choice | reliable | Enable reliable syslogging by RFC6587 (Transmission of Syslog Messages over TCP). + required: false + choices: ["udp", "legacy-reliable", "reliable"] + default: "udp" + + syslog_filter: + description: + - Sets the logging level for syslog. + required: False + choices: ["emergency", "alert", "critical", "error", "warning", "notification", "information", "debug"] + + syslog_facility: + description: + - Remote syslog facility. + - choice | kernel | Kernel messages. + - choice | user | Random user-level messages. + - choice | mail | Mail system. + - choice | daemon | System daemons. + - choice | auth | Security/authorization messages. + - choice | syslog | Messages generated internally by syslog. + - choice | lpr | Line printer subsystem. + - choice | news | Network news subsystem. + - choice | uucp | Network news subsystem. + - choice | cron | Clock daemon. + - choice | authpriv | Security/authorization messages (private). + - choice | ftp | FTP daemon. + - choice | ntp | NTP daemon. + - choice | audit | Log audit. + - choice | alert | Log alert. + - choice | clock | Clock daemon. + - choice | local0 | Reserved for local use. + - choice | local1 | Reserved for local use. + - choice | local2 | Reserved for local use. + - choice | local3 | Reserved for local use. + - choice | local4 | Reserved for local use. + - choice | local5 | Reserved for local use. + - choice | local6 | Reserved for local use. + - choice | local7 | Reserved for local use. + required: false + choices: ["kernel", "user", "mail", "daemon", "auth", "syslog", + "lpr", "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit", + "alert", "clock", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"] + default: "syslog" + + syslog_enc_algorithm: + description: + - Enable/disable reliable syslogging with TLS encryption. + - choice | high | SSL communication with high encryption algorithms. + - choice | low | SSL communication with low encryption algorithms. + - choice | disable | Disable SSL communication. + - choice | high-medium | SSL communication with high and medium encryption algorithms. + required: false + choices: ["high", "low", "disable", "high-medium"] + default: "disable" + + syslog_certificate: + description: + - Certificate used to communicate with Syslog server if encryption on. + required: false + + ntp_status: + description: + - Enables or disables ntp. + required: False + choices: ["enable", "disable"] + + ntp_sync_interval: + description: + - Sets the interval in minutes for ntp sync. + required: False + + ntp_type: + description: + - Enables fortiguard servers or custom servers are the ntp source. + required: False + choices: ["fortiguard", "custom"] + + ntp_server: + description: + - Only used with custom ntp_type -- specifies IP of server to sync to -- comma separated ip addresses for multiples. + required: False + + ntp_auth: + description: + - Enables or disables ntp authentication. + required: False + choices: ["enable", "disable"] + + ntp_auth_pwd: + description: + - Sets the ntp auth password. + required: False + + ntp_v3: + description: + - Enables or disables ntpv3 (default is ntpv4). + required: False + choices: ["enable", "disable"] + + admin_https_redirect: + description: + - Enables or disables https redirect from http. + required: False + choices: ["enable", "disable"] + + admin_https_port: + description: + - SSL admin gui port number. + required: False + + admin_http_port: + description: + - Non-SSL admin gui port number. + required: False + + admin_timeout: + description: + - Admin timeout in minutes. + required: False + + admin_language: + description: + - Sets the admin gui language. + required: False + choices: ["english", "simch", "japanese", "korean", "spanish", "trach", "french", "portuguese"] + + admin_switch_controller: + description: + - Enables or disables the switch controller. + required: False + choices: ["enable", "disable"] + + admin_gui_theme: + description: + - Changes the admin gui theme. + required: False + choices: ["green", "red", "blue", "melongene", "mariner"] + + admin_enable_fortiguard: + description: + - Enables FortiGuard security updates to their default settings. + required: False + choices: ["none", "direct", "this-fmg"] + + admin_fortianalyzer_target: + description: + - Configures faz target. + required: False + + admin_fortiguard_target: + description: + - Configures fortiguard target. + - admin_enable_fortiguard must be set to "direct". + required: False + + smtp_username: + description: + - SMTP auth username. + required: False + + smtp_password: + description: + - SMTP password. + required: False + + smtp_port: + description: + - SMTP port number. + required: False + + smtp_replyto: + description: + - SMTP reply to address. + required: False + + smtp_conn_sec: + description: + - defines the ssl level for smtp. + required: False + choices: ["none", "starttls", "smtps"] + + smtp_server: + description: + - SMTP server ipv4 address. + required: False + + smtp_source_ipv4: + description: + - SMTP source ip address. + required: False + + smtp_validate_cert: + description: + - Enables or disables valid certificate checking for smtp. + required: False + choices: ["enable", "disable"] + + dns_suffix: + description: + - Sets the local dns domain suffix. + required: False + + dns_primary_ipv4: + description: + - primary ipv4 dns forwarder. + required: False + + dns_secondary_ipv4: + description: + - secondary ipv4 dns forwarder. + required: False + + delete_provisioning_template: + description: + - If specified, all other options are ignored. The specified provisioning template will be deleted. + required: False + +''' + + +EXAMPLES = ''' +- name: SET SNMP SYSTEM INFO + community.network.fmgr_device_provision_template: + provisioning_template: "default" + snmp_status: "enable" + mode: "set" + +- name: SET SNMP SYSTEM INFO ANSIBLE ADOM + community.network.fmgr_device_provision_template: + provisioning_template: "default" + snmp_status: "enable" + mode: "set" + adom: "ansible" + +- name: SET SNMP SYSTEM INFO different template (SNMPv2) + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + snmp_status: "enable" + mode: "set" + adom: "ansible" + snmp_v2c_query_port: "162" + snmp_v2c_trap_port: "161" + snmp_v2c_status: "enable" + snmp_v2c_trap_status: "enable" + snmp_v2c_query_status: "enable" + snmp_v2c_name: "ansibleV2c" + snmp_v2c_id: "1" + snmp_v2c_trap_src_ipv4: "10.7.220.41" + snmp_v2c_trap_hosts_ipv4: "10.7.220.59 255.255.255.255, 10.7.220.60 255.255.255.255" + snmp_v2c_query_hosts_ipv4: "10.7.220.59 255.255.255.255, 10.7.220.0 255.255.255.0" + +- name: SET SNMP SYSTEM INFO different template (SNMPv3) + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + snmp_status: "enable" + mode: "set" + adom: "ansible" + snmpv3_auth_proto: "sha" + snmpv3_auth_pwd: "fortinet" + snmpv3_name: "ansibleSNMPv3" + snmpv3_notify_hosts: "10.7.220.59,10.7.220.60" + snmpv3_priv_proto: "aes256" + snmpv3_priv_pwd: "fortinet" + snmpv3_queries: "enable" + snmpv3_query_port: "161" + snmpv3_security_level: "auth_priv" + snmpv3_source_ip: "0.0.0.0" + snmpv3_status: "enable" + snmpv3_trap_rport: "162" + snmpv3_trap_status: "enable" + +- name: SET SYSLOG INFO + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + syslog_server: "10.7.220.59" + syslog_port: "514" + syslog_mode: "disable" + syslog_status: "enable" + syslog_filter: "information" + +- name: SET NTP TO FORTIGUARD + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + ntp_status: "enable" + ntp_sync_interval: "60" + type: "fortiguard" + +- name: SET NTP TO CUSTOM SERVER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + ntp_status: "enable" + ntp_sync_interval: "60" + ntp_type: "custom" + ntp_server: "10.7.220.32,10.7.220.1" + ntp_auth: "enable" + ntp_auth_pwd: "fortinet" + ntp_v3: "disable" + +- name: SET ADMIN GLOBAL SETTINGS + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + admin_https_redirect: "enable" + admin_https_port: "4433" + admin_http_port: "8080" + admin_timeout: "30" + admin_language: "english" + admin_switch_controller: "enable" + admin_gui_theme: "blue" + admin_enable_fortiguard: "direct" + admin_fortiguard_target: "10.7.220.128" + admin_fortianalyzer_target: "10.7.220.61" + +- name: SET CUSTOM SMTP SERVER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + smtp_username: "ansible" + smtp_password: "fortinet" + smtp_port: "25" + smtp_replyto: "ansible@do-not-reply.com" + smtp_conn_sec: "starttls" + smtp_server: "10.7.220.32" + smtp_source_ipv4: "0.0.0.0" + smtp_validate_cert: "disable" + +- name: SET DNS SERVERS + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + dns_suffix: "ansible.local" + dns_primary_ipv4: "8.8.8.8" + dns_secondary_ipv4: "4.4.4.4" + +- name: SET PROVISIONING TEMPLATE DEVICE TARGETS IN FORTIMANAGER + community.network.fmgr_device_provision_template: + provisioning_template: "ansibleTest" + mode: "set" + adom: "ansible" + provision_targets: "FGT1, FGT2" + +- name: DELETE ENTIRE PROVISIONING TEMPLATE + community.network.fmgr_device_provision_template: + delete_provisioning_template: "ansibleTest" + mode: "delete" + adom: "ansible" + +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def get_devprof(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + datagram = {} + + url = "/pm/devprof/adom/{adom}/{name}".format(adom=paramgram["adom"], name=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + + return response + + +def set_devprof(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + elif paramgram["mode"] == "delete": + datagram = {} + + url = "/pm/devprof/adom/{adom}/{name}".format(adom=paramgram["adom"], + name=paramgram["delete_provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def get_devprof_scope(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + + return response + + +def set_devprof_scope(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + } + + targets = [] + for target in paramgram["provision_targets"].split(","): + # split the host on the space to get the mask out + new_target = {"name": target.strip()} + targets.append(new_target) + + datagram["scope member"] = targets + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + elif paramgram["mode"] == "delete": + datagram = { + "name": paramgram["provisioning_template"], + "type": "devprof", + "description": "CreatedByAnsible", + "scope member": paramgram["targets_to_add"] + } + + url = "/pm/devprof/adom/{adom}".format(adom=paramgram["adom"]) + + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def set_devprof_snmp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "status": paramgram["snmp_status"] + } + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp/sysinfo".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def set_devprof_snmp_v2c(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = { + "query-v2c-port": paramgram["snmp_v2c_query_port"], + "trap-v2c-rport": paramgram["snmp_v2c_trap_port"], + "status": paramgram["snmp_v2c_status"], + "trap-v2c-status": paramgram["snmp_v2c_trap_status"], + "query-v2c-status": paramgram["snmp_v2c_query_status"], + "name": paramgram["snmp_v2c_name"], + "id": paramgram["snmp_v2c_id"], + "meta fields": dict(), + "hosts": list(), + "events": 411578417151, + "query-v1-status": 0, + "query-v1-port": 161, + "trap-v1-status": 0, + "trap-v1-lport": 162, + "trap-v1-rport": 162, + "trap-v2c-lport": 162, + } + + # BUILD THE HOST STRINGS + id_counter = 1 + if paramgram["snmp_v2c_trap_hosts_ipv4"] or paramgram["snmp_v2c_query_hosts_ipv4"]: + hosts = [] + if paramgram["snmp_v2c_query_hosts_ipv4"]: + for ipv4_host in paramgram["snmp_v2c_query_hosts_ipv4"].strip().split(","): + # split the host on the space to get the mask out + new_ipv4_host = {"ha-direct": "enable", + "host-type": "query", + "id": id_counter, + "ip": ipv4_host.strip().split(), + "meta fields": {}, + "source-ip": "0.0.0.0"} + hosts.append(new_ipv4_host) + id_counter += 1 + + if paramgram["snmp_v2c_trap_hosts_ipv4"]: + for ipv4_host in paramgram["snmp_v2c_trap_hosts_ipv4"].strip().split(","): + # split the host on the space to get the mask out + new_ipv4_host = {"ha-direct": "enable", + "host-type": "trap", + "id": id_counter, + "ip": ipv4_host.strip().split(), + "meta fields": {}, + "source-ip": paramgram["snmp_v2c_trap_src_ipv4"]} + hosts.append(new_ipv4_host) + id_counter += 1 + datagram["hosts"] = hosts + + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp/community".format(adom=adom, + provisioning_template=paramgram[ + "provisioning_template"]) + elif paramgram["mode"] == "delete": + datagram = { + "confirm": 1 + } + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "system/snmp/community/{snmp_v2c_id}".format(adom=adom, + provisioning_template=paramgram["provisioning_template"], + snmp_v2c_id=paramgram["snmp_v2c_id"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_snmp_v3(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add', 'update']: + datagram = {} + datagram["auth-pwd"] = paramgram["snmpv3_auth_pwd"] + datagram["priv-pwd"] = paramgram["snmpv3_priv_pwd"] + datagram["trap-rport"] = paramgram["snmpv3_trap_rport"] + datagram["query-port"] = paramgram["snmpv3_query_port"] + datagram["name"] = paramgram["snmpv3_name"] + datagram["notify-hosts"] = paramgram["snmpv3_notify_hosts"].strip().split(",") + datagram["events"] = 1647387997183 + datagram["trap-lport"] = 162 + + datagram["source-ip"] = paramgram["snmpv3_source_ip"] + datagram["ha-direct"] = 0 + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "system/snmp/user".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + elif paramgram["mode"] == "delete": + datagram = { + "confirm": 1 + } + + url = "/pm/config/adom/{adom}/devprof/" \ + "{provisioning_template}/system/snmp" \ + "/user/{snmpv3_name}".format(adom=adom, + provisioning_template=paramgram["provisioning_template"], + snmpv3_name=paramgram["snmpv3_name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_syslog(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + + datagram = { + "status": paramgram["syslog_status"], + "port": paramgram["syslog_port"], + "server": paramgram["syslog_server"], + "mode": paramgram["syslog_mode"], + "facility": paramgram["syslog_facility"] + } + + if paramgram["mode"] in ['set', 'add', 'update']: + if paramgram["syslog_enc_algorithm"] in ["high", "low", "high-medium"] \ + and paramgram["syslog_certificate"] is not None: + datagram["certificate"] = paramgram["certificate"] + datagram["enc-algorithm"] = paramgram["syslog_enc_algorithm"] + + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "log/syslogd/setting".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + elif paramgram["mode"] == "delete": + url = "/pm/config/adom/{adom}/" \ + "devprof/{provisioning_template}/" \ + "log/syslogd/setting".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_syslog_filter(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + datagram = { + "severity": paramgram["syslog_filter"] + } + response = DEFAULT_RESULT_OBJ + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/log/syslogd/filter".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_ntp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + + # IF SET TO FORTIGUARD, BUILD A STRING SPECIFIC TO THAT + if paramgram["ntp_type"] == "fortiguard": + datagram = {} + if paramgram["ntp_status"] == "enable": + datagram["ntpsync"] = 1 + if paramgram["ntp_status"] == "disable": + datagram["ntpsync"] = 0 + if paramgram["ntp_sync_interval"] is None: + datagram["syncinterval"] = 1 + else: + datagram["syncinterval"] = paramgram["ntp_sync_interval"] + + datagram["type"] = 0 + + # IF THE NTP TYPE IS CUSTOM BUILD THE SERVER LIST + if paramgram["ntp_type"] == "custom": + id_counter = 0 + key_counter = 0 + ntpservers = [] + datagram = {} + if paramgram["ntp_status"] == "enable": + datagram["ntpsync"] = 1 + if paramgram["ntp_status"] == "disable": + datagram["ntpsync"] = 0 + try: + datagram["syncinterval"] = paramgram["ntp_sync_interval"] + except BaseException: + datagram["syncinterval"] = 1 + datagram["type"] = 1 + + for server in paramgram["ntp_server"].strip().split(","): + id_counter += 1 + server_fields = dict() + + key_counter += 1 + if paramgram["ntp_auth"] == "enable": + server_fields["authentication"] = 1 + server_fields["key"] = paramgram["ntp_auth_pwd"] + server_fields["key-id"] = key_counter + else: + server_fields["authentication"] = 0 + server_fields["key"] = "" + server_fields["key-id"] = key_counter + + if paramgram["ntp_v3"] == "enable": + server_fields["ntp_v3"] = 1 + else: + server_fields["ntp_v3"] = 0 + + # split the host on the space to get the mask out + new_ntp_server = {"authentication": server_fields["authentication"], + "id": id_counter, "key": server_fields["key"], + "key-id": id_counter, "ntpv3": server_fields["ntp_v3"], + "server": server} + ntpservers.append(new_ntp_server) + datagram["ntpserver"] = ntpservers + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/ntp".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_admin(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "admin-https-redirect": paramgram["admin_https_redirect"], + "admin-port": paramgram["admin_http_port"], + "admin-sport": paramgram["admin_https_port"], + "admintimeout": paramgram["admin_timeout"], + "language": paramgram["admin_language"], + "gui-theme": paramgram["admin_gui_theme"], + "switch-controller": paramgram["admin_switch_controller"], + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/global".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_smtp(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "port": paramgram["smtp_port"], + "reply-to": paramgram["smtp_replyto"], + "server": paramgram["smtp_server"], + "source-ip": paramgram["smtp_source_ipv4"] + } + + if paramgram["smtp_username"]: + datagram["authenticate"] = 1 + datagram["username"] = paramgram["smtp_username"] + datagram["password"] = paramgram["smtp_password"] + + if paramgram["smtp_conn_sec"] == "none": + datagram["security"] = 0 + if paramgram["smtp_conn_sec"] == "starttls": + datagram["security"] = 1 + if paramgram["smtp_conn_sec"] == "smtps": + datagram["security"] = 2 + + if paramgram["smtp_validate_cert"] == "enable": + datagram["validate-server"] = 1 + else: + datagram["validate-server"] = 0 + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/email-server".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_dns(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "domain": paramgram["dns_suffix"], + "primary": paramgram["dns_primary_ipv4"], + "secondary": paramgram["dns_secondary_ipv4"], + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/dns".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_toggle_fg(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + response = DEFAULT_RESULT_OBJ + datagram = {} + if paramgram["admin_enable_fortiguard"] in ["direct", "this-fmg"]: + datagram["include-default-servers"] = "enable" + elif paramgram["admin_enable_fortiguard"] == "none": + datagram["include-default-servers"] = "disable" + + datagram["server-list"] = list() + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/system/central-management".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + + return response + + +def set_devprof_fg(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + datagram = { + "target": paramgram["admin_enable_fortiguard"], + "target-ip": None + } + + if paramgram["mode"] in ['set', 'add', 'update']: + if paramgram["admin_fortiguard_target"] is not None and datagram["target"] == "direct": + datagram["target-ip"] = paramgram["admin_fortiguard_target"] + + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/device/profile/fortiguard".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def set_devprof_faz(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + paramgram["mode"] = paramgram["mode"] + adom = paramgram["adom"] + response = DEFAULT_RESULT_OBJ + datagram = { + "target-ip": paramgram["admin_fortianalyzer_target"], + "target": "others", + } + url = "/pm/config/adom/{adom}" \ + "/devprof/{provisioning_template}" \ + "/device/profile/fortianalyzer".format(adom=adom, + provisioning_template=paramgram["provisioning_template"]) + if paramgram["mode"] == "delete": + datagram["hastarget"] = "False" + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + provisioning_template=dict(required=False, type="str"), + provision_targets=dict(required=False, type="str"), + + device_unique_name=dict(required=False, type="str"), + snmp_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_query_port=dict(required=False, type="int"), + snmp_v2c_trap_port=dict(required=False, type="int"), + snmp_v2c_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_trap_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_query_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmp_v2c_name=dict(required=False, type="str", no_log=True), + snmp_v2c_id=dict(required=False, type="int"), + snmp_v2c_trap_src_ipv4=dict(required=False, type="str"), + snmp_v2c_trap_hosts_ipv4=dict(required=False, type="str"), + snmp_v2c_query_hosts_ipv4=dict(required=False, type="str"), + + snmpv3_auth_proto=dict(required=False, type="str", choices=["md5", "sha"]), + snmpv3_auth_pwd=dict(required=False, type="str", no_log=True), + snmpv3_name=dict(required=False, type="str"), + snmpv3_notify_hosts=dict(required=False, type="str"), + snmpv3_priv_proto=dict(required=False, type="str", choices=["aes", "des", "aes256", "aes256cisco"]), + snmpv3_priv_pwd=dict(required=False, type="str", no_log=True), + snmpv3_queries=dict(required=False, type="str", choices=["enable", "disable"]), + snmpv3_query_port=dict(required=False, type="int"), + snmpv3_security_level=dict(required=False, type="str", + choices=["no-auth-no-priv", "auth-no-priv", "auth-priv"]), + snmpv3_source_ip=dict(required=False, type="str"), + snmpv3_status=dict(required=False, type="str", choices=["enable", "disable"]), + snmpv3_trap_rport=dict(required=False, type="int"), + snmpv3_trap_status=dict(required=False, type="str", choices=["enable", "disable"]), + + syslog_port=dict(required=False, type="int"), + syslog_server=dict(required=False, type="str"), + syslog_mode=dict(required=False, type="str", choices=["udp", "legacy-reliable", "reliable"], default="udp"), + syslog_status=dict(required=False, type="str", choices=["enable", "disable"]), + syslog_filter=dict(required=False, type="str", choices=["emergency", "alert", "critical", "error", + "warning", "notification", "information", "debug"]), + syslog_enc_algorithm=dict(required=False, type="str", choices=["high", "low", "disable", "high-medium"], + default="disable"), + syslog_facility=dict(required=False, type="str", choices=["kernel", "user", "mail", "daemon", "auth", + "syslog", "lpr", "news", "uucp", "cron", "authpriv", + "ftp", "ntp", "audit", "alert", "clock", "local0", + "local1", "local2", "local3", "local4", "local5", + "local6", "local7"], default="syslog"), + syslog_certificate=dict(required=False, type="str"), + + ntp_status=dict(required=False, type="str", choices=["enable", "disable"]), + ntp_sync_interval=dict(required=False, type="int"), + ntp_type=dict(required=False, type="str", choices=["fortiguard", "custom"]), + ntp_server=dict(required=False, type="str"), + ntp_auth=dict(required=False, type="str", choices=["enable", "disable"]), + ntp_auth_pwd=dict(required=False, type="str", no_log=True), + ntp_v3=dict(required=False, type="str", choices=["enable", "disable"]), + + admin_https_redirect=dict(required=False, type="str", choices=["enable", "disable"]), + admin_https_port=dict(required=False, type="int"), + admin_http_port=dict(required=False, type="int"), + admin_timeout=dict(required=False, type="int"), + admin_language=dict(required=False, type="str", + choices=["english", "simch", "japanese", "korean", + "spanish", "trach", "french", "portuguese"]), + admin_switch_controller=dict(required=False, type="str", choices=["enable", "disable"]), + admin_gui_theme=dict(required=False, type="str", choices=["green", "red", "blue", "melongene", "mariner"]), + admin_enable_fortiguard=dict(required=False, type="str", choices=["none", "direct", "this-fmg"]), + admin_fortianalyzer_target=dict(required=False, type="str"), + admin_fortiguard_target=dict(required=False, type="str"), + + smtp_username=dict(required=False, type="str"), + smtp_password=dict(required=False, type="str", no_log=True), + smtp_port=dict(required=False, type="int"), + smtp_replyto=dict(required=False, type="str"), + smtp_conn_sec=dict(required=False, type="str", choices=["none", "starttls", "smtps"]), + smtp_server=dict(required=False, type="str"), + smtp_source_ipv4=dict(required=False, type="str"), + smtp_validate_cert=dict(required=False, type="str", choices=["enable", "disable"]), + + dns_suffix=dict(required=False, type="str"), + dns_primary_ipv4=dict(required=False, type="str"), + dns_secondary_ipv4=dict(required=False, type="str"), + delete_provisioning_template=dict(required=False, type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "adom": module.params["adom"], + "mode": module.params["mode"], + "provision_targets": module.params["provision_targets"], + "provisioning_template": module.params["provisioning_template"], + + "snmp_status": module.params["snmp_status"], + "snmp_v2c_query_port": module.params["snmp_v2c_query_port"], + "snmp_v2c_trap_port": module.params["snmp_v2c_trap_port"], + "snmp_v2c_status": module.params["snmp_v2c_status"], + "snmp_v2c_trap_status": module.params["snmp_v2c_trap_status"], + "snmp_v2c_query_status": module.params["snmp_v2c_query_status"], + "snmp_v2c_name": module.params["snmp_v2c_name"], + "snmp_v2c_id": module.params["snmp_v2c_id"], + "snmp_v2c_trap_src_ipv4": module.params["snmp_v2c_trap_src_ipv4"], + "snmp_v2c_trap_hosts_ipv4": module.params["snmp_v2c_trap_hosts_ipv4"], + "snmp_v2c_query_hosts_ipv4": module.params["snmp_v2c_query_hosts_ipv4"], + + "snmpv3_auth_proto": module.params["snmpv3_auth_proto"], + "snmpv3_auth_pwd": module.params["snmpv3_auth_pwd"], + "snmpv3_name": module.params["snmpv3_name"], + "snmpv3_notify_hosts": module.params["snmpv3_notify_hosts"], + "snmpv3_priv_proto": module.params["snmpv3_priv_proto"], + "snmpv3_priv_pwd": module.params["snmpv3_priv_pwd"], + "snmpv3_queries": module.params["snmpv3_queries"], + "snmpv3_query_port": module.params["snmpv3_query_port"], + "snmpv3_security_level": module.params["snmpv3_security_level"], + "snmpv3_source_ip": module.params["snmpv3_source_ip"], + "snmpv3_status": module.params["snmpv3_status"], + "snmpv3_trap_rport": module.params["snmpv3_trap_rport"], + "snmpv3_trap_status": module.params["snmpv3_trap_status"], + + "syslog_port": module.params["syslog_port"], + "syslog_server": module.params["syslog_server"], + "syslog_mode": module.params["syslog_mode"], + "syslog_status": module.params["syslog_status"], + "syslog_filter": module.params["syslog_filter"], + "syslog_facility": module.params["syslog_facility"], + "syslog_enc_algorithm": module.params["syslog_enc_algorithm"], + "syslog_certificate": module.params["syslog_certificate"], + + "ntp_status": module.params["ntp_status"], + "ntp_sync_interval": module.params["ntp_sync_interval"], + "ntp_type": module.params["ntp_type"], + "ntp_server": module.params["ntp_server"], + "ntp_auth": module.params["ntp_auth"], + "ntp_auth_pwd": module.params["ntp_auth_pwd"], + "ntp_v3": module.params["ntp_v3"], + + "admin_https_redirect": module.params["admin_https_redirect"], + "admin_https_port": module.params["admin_https_port"], + "admin_http_port": module.params["admin_http_port"], + "admin_timeout": module.params["admin_timeout"], + "admin_language": module.params["admin_language"], + "admin_switch_controller": module.params["admin_switch_controller"], + "admin_gui_theme": module.params["admin_gui_theme"], + "admin_enable_fortiguard": module.params["admin_enable_fortiguard"], + "admin_fortianalyzer_target": module.params["admin_fortianalyzer_target"], + "admin_fortiguard_target": module.params["admin_fortiguard_target"], + + "smtp_username": module.params["smtp_username"], + "smtp_password": module.params["smtp_password"], + "smtp_port": module.params["smtp_port"], + "smtp_replyto": module.params["smtp_replyto"], + "smtp_conn_sec": module.params["smtp_conn_sec"], + "smtp_server": module.params["smtp_server"], + "smtp_source_ipv4": module.params["smtp_source_ipv4"], + "smtp_validate_cert": module.params["smtp_validate_cert"], + + "dns_suffix": module.params["dns_suffix"], + "dns_primary_ipv4": module.params["dns_primary_ipv4"], + "dns_secondary_ipv4": module.params["dns_secondary_ipv4"], + "delete_provisioning_template": module.params["delete_provisioning_template"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + try: + # CHECK IF WE ARE DELETING AN ENTIRE TEMPLATE. IF THAT'S THE CASE DO IT FIRST AND IGNORE THE REST. + if paramgram["delete_provisioning_template"] is not None: + results = set_devprof(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10, -1], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram), + stop_on_success=True) + except Exception as err: + raise FMGBaseException(err) + + try: + # CHECK TO SEE IF THE DEVPROF TEMPLATE EXISTS + devprof = get_devprof(fmgr, paramgram) + if devprof[0] != 0: + results = set_devprof(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SNMP SETTINGS IF THE SNMP_STATUS VARIABLE IS SET + if paramgram["snmp_status"] is not None: + results = set_devprof_snmp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE SNMP V2C COMMUNITY SETTINGS IF THEY ARE ALL HERE + if all(v is not None for v in (paramgram["snmp_v2c_query_port"], paramgram["snmp_v2c_trap_port"], + paramgram["snmp_v2c_status"], paramgram["snmp_v2c_trap_status"], + paramgram["snmp_v2c_query_status"], paramgram["snmp_v2c_name"], + paramgram["snmp_v2c_id"])): + results = set_devprof_snmp_v2c(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033], stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + # PROCESS THE SNMPV3 USER IF THERE + if all(v is not None for v in ( + [paramgram["snmpv3_auth_proto"], paramgram["snmpv3_auth_pwd"], paramgram["snmpv3_name"], + paramgram["snmpv3_notify_hosts"], paramgram["snmpv3_priv_proto"], + paramgram["snmpv3_priv_pwd"], + paramgram["snmpv3_queries"], + paramgram["snmpv3_query_port"], paramgram["snmpv3_security_level"], + paramgram["snmpv3_source_ip"], + paramgram["snmpv3_status"], paramgram["snmpv3_trap_rport"], paramgram["snmpv3_trap_status"]])): + + results = set_devprof_snmp_v3(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SYSLOG SETTINGS IF THE ALL THE NEEDED SYSLOG VARIABLES ARE PRESENT + if all(v is not None for v in [paramgram["syslog_port"], paramgram["syslog_mode"], + paramgram["syslog_server"], paramgram["syslog_status"]]): + # enable syslog in the devprof template + results = set_devprof_syslog(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE SYSLOG FILTER IS PRESENT THEN RUN THAT + if paramgram["syslog_filter"] is not None: + results = set_devprof_syslog_filter(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS NTP OPTIONS + if paramgram["ntp_status"]: + # VALIDATE INPUT + if paramgram["ntp_type"] == "custom" and paramgram["ntp_server"] is None: + module.exit_json(msg="You requested custom NTP type but did not provide ntp_server parameter.") + if paramgram["ntp_auth"] == "enable" and paramgram["ntp_auth_pwd"] is None: + module.exit_json( + msg="You requested NTP Authentication but did not provide ntp_auth_pwd parameter.") + + results = set_devprof_ntp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + try: + # PROCESS THE ADMIN OPTIONS + if any(v is not None for v in ( + paramgram["admin_https_redirect"], paramgram["admin_https_port"], paramgram["admin_http_port"], + paramgram["admin_timeout"], + paramgram["admin_language"], paramgram["admin_switch_controller"], + paramgram["admin_gui_theme"])): + + results = set_devprof_admin(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS FORTIGUARD OPTIONS + if paramgram["admin_enable_fortiguard"] is not None: + + results = set_devprof_toggle_fg(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + results = set_devprof_fg(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE SMTP OPTIONS + if all(v is not None for v in ( + paramgram["smtp_username"], paramgram["smtp_password"], paramgram["smtp_port"], + paramgram["smtp_replyto"], + paramgram["smtp_conn_sec"], paramgram["smtp_server"], + paramgram["smtp_source_ipv4"], paramgram["smtp_validate_cert"])): + + results = set_devprof_smtp(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE DNS OPTIONS + if any(v is not None for v in + (paramgram["dns_suffix"], paramgram["dns_primary_ipv4"], paramgram["dns_secondary_ipv4"])): + results = set_devprof_dns(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE admin_fortianalyzer_target OPTIONS + if paramgram["admin_fortianalyzer_target"] is not None: + + results = set_devprof_faz(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # PROCESS THE PROVISIONING TEMPLATE TARGET PARAMETER + if paramgram["provision_targets"] is not None: + if paramgram["mode"] != "delete": + results = set_devprof_scope(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0], stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + if paramgram["mode"] == "delete": + # WE NEED TO FIGURE OUT WHAT'S THERE FIRST, BEFORE WE CAN RUN THIS + targets_to_add = list() + try: + current_scope = get_devprof_scope(fmgr, paramgram) + targets_to_remove = paramgram["provision_targets"].strip().split(",") + targets = current_scope[1][1]["scope member"] + for target in targets: + if target["name"] not in targets_to_remove: + target_append = {"name": target["name"]} + targets_to_add.append(target_append) + except BaseException: + pass + paramgram["targets_to_add"] = targets_to_add + results = set_devprof_scope(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -10033, -10000, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_address.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_address.py new file mode 100644 index 00000000..02b17553 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_address.py @@ -0,0 +1,661 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_address +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the management of firewall objects in FortiManager +description: + - Allows for the management of IPv4, IPv6, and multicast address objects within FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + allow_routing: + description: + - Enable/disable use of this address in the static route configuration. + choices: ['enable', 'disable'] + default: 'disable' + + associated_interface: + description: + - Associated interface name. + + cache_ttl: + description: + - Minimal TTL of individual IP addresses in FQDN cache. Only applies when type = wildcard-fqdn. + + color: + description: + - Color of the object in FortiManager GUI. + - Takes integers 1-32 + default: 22 + + comment: + description: + - Comment for the object in FortiManager. + + country: + description: + - Country name. Required if type = geographic. + + end_ip: + description: + - End IP. Only used when ipv4 = iprange. + + group_members: + description: + - Address group member. If this is defined w/out group_name, the operation will fail. + + group_name: + description: + - Address group name. If this is defined in playbook task, all other options are ignored. + + ipv4: + description: + - Type of IPv4 Object. + - Must not be specified with either multicast or IPv6 parameters. + choices: ['ipmask', 'iprange', 'fqdn', 'wildcard', 'geography', 'wildcard-fqdn', 'group'] + + ipv4addr: + description: + - IP and network mask. If only defining one IP use this parameter. (i.e. 10.7.220.30/255.255.255.255) + - Can also define subnets (i.e. 10.7.220.0/255.255.255.0) + - Also accepts CIDR (i.e. 10.7.220.0/24) + - If Netmask is omitted after IP address, /32 is assumed. + - When multicast is set to Broadcast Subnet the ipv4addr parameter is used to specify the subnet. + + ipv6: + description: + - Puts module into IPv6 mode. + - Must not be specified with either ipv4 or multicast parameters. + choices: ['ip', 'iprange', 'group'] + + ipv6addr: + description: + - IPv6 address in full. (i.e. 2001:0db8:85a3:0000:0000:8a2e:0370:7334) + + fqdn: + description: + - Fully qualified domain name. + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + + multicast: + description: + - Manages Multicast Address Objects. + - Sets either a Multicast IP Range or a Broadcast Subnet. + - Must not be specified with either ipv4 or ipv6 parameters. + - When set to Broadcast Subnet the ipv4addr parameter is used to specify the subnet. + - Can create IPv4 Multicast Objects (multicastrange and broadcastmask options -- uses start/end-ip and ipv4addr). + choices: ['multicastrange', 'broadcastmask', 'ip6'] + + name: + description: + - Friendly Name Address object name in FortiManager. + + obj_id: + description: + - Object ID for NSX. + + start_ip: + description: + - Start IP. Only used when ipv4 = iprange. + + visibility: + description: + - Enable/disable address visibility. + choices: ['enable', 'disable'] + default: 'enable' + + wildcard: + description: + - IP address and wildcard netmask. Required if ipv4 = wildcard. + + wildcard_fqdn: + description: + - Wildcard FQDN. Required if ipv4 = wildcard-fqdn. +''' + +EXAMPLES = ''' +- name: ADD IPv4 IP ADDRESS OBJECT + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.30/32" + name: "ansible_v4Obj" + comment: "Created by Ansible" + color: "6" + +- name: ADD IPv4 IP ADDRESS OBJECT MORE OPTIONS + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.34/32" + name: "ansible_v4Obj_MORE" + comment: "Created by Ansible" + color: "6" + allow_routing: "enable" + cache_ttl: "180" + associated_interface: "port1" + obj_id: "123" + +- name: ADD IPv4 IP ADDRESS SUBNET OBJECT + community.network.fmgr_fwobj_address: + ipv4: "ipmask" + ipv4addr: "10.7.220.0/255.255.255.128" + name: "ansible_subnet" + comment: "Created by Ansible" + mode: "set" + +- name: ADD IPv4 IP ADDRESS RANGE OBJECT + community.network.fmgr_fwobj_address: + ipv4: "iprange" + start_ip: "10.7.220.1" + end_ip: "10.7.220.125" + name: "ansible_range" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS WILDCARD OBJECT + community.network.fmgr_fwobj_address: + ipv4: "wildcard" + wildcard: "10.7.220.30/255.255.255.255" + name: "ansible_wildcard" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS WILDCARD FQDN OBJECT + community.network.fmgr_fwobj_address: + ipv4: "wildcard-fqdn" + wildcard_fqdn: "*.myds.com" + name: "Synology myds DDNS service" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS FQDN OBJECT + community.network.fmgr_fwobj_address: + ipv4: "fqdn" + fqdn: "ansible.com" + name: "ansible_fqdn" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS GEO OBJECT + community.network.fmgr_fwobj_address: + ipv4: "geography" + country: "usa" + name: "ansible_geo" + comment: "Created by Ansible" + +- name: ADD IPv6 ADDRESS + community.network.fmgr_fwobj_address: + ipv6: "ip" + ipv6addr: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + name: "ansible_v6Obj" + comment: "Created by Ansible" + +- name: ADD IPv6 ADDRESS RANGE + community.network.fmgr_fwobj_address: + ipv6: "iprange" + start_ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + end_ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7446" + name: "ansible_v6range" + comment: "Created by Ansible" + +- name: ADD IPv4 IP ADDRESS GROUP + community.network.fmgr_fwobj_address: + ipv4: "group" + group_name: "ansibleIPv4Group" + group_members: "ansible_fqdn, ansible_wildcard, ansible_range" + +- name: ADD IPv6 IP ADDRESS GROUP + community.network.fmgr_fwobj_address: + ipv6: "group" + group_name: "ansibleIPv6Group" + group_members: "ansible_v6Obj, ansible_v6range" + +- name: ADD MULTICAST RANGE + community.network.fmgr_fwobj_address: + multicast: "multicastrange" + start_ip: "224.0.0.251" + end_ip: "224.0.0.251" + name: "ansible_multicastrange" + comment: "Created by Ansible" + +- name: ADD BROADCAST SUBNET + community.network.fmgr_fwobj_address: + multicast: "broadcastmask" + ipv4addr: "10.7.220.0/24" + name: "ansible_broadcastSubnet" + comment: "Created by Ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + + +import re +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_fwobj_ipv4(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "comment": paramgram["comment"], + "associated-interface": paramgram["associated-interface"], + "cache-ttl": paramgram["cache-ttl"], + "name": paramgram["name"], + "allow-routing": paramgram["allow-routing"], + "color": paramgram["color"], + "meta fields": {}, + "dynamic_mapping": [], + "visibility": paramgram["allow-routing"], + "type": paramgram["ipv4"], + } + + # SET THE CORRECT URL BASED ON THE TYPE (WE'RE DOING GROUPS IN THIS METHOD, TOO) + if datagram["type"] == "group": + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp'.format(adom=paramgram["adom"]) + else: + url = '/pm/config/adom/{adom}/obj/firewall/address'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'ipmask' + ######################### + if datagram["type"] == "ipmask": + # CREATE THE SUBNET LIST OBJECT + subnet = [] + # EVAL THE IPV4ADDR INPUT AND SPLIT THE IP ADDRESS FROM THE MASK AND APPEND THEM TO THE SUBNET LIST + for subnets in paramgram["ipv4addr"].split("/"): + subnet.append(subnets) + + # CHECK THAT THE SECOND ENTRY IN THE SUBNET LIST (WHAT WAS TO THE RIGHT OF THE / CHARACTER) + # IS IN SUBNET MASK FORMAT AND NOT CIDR FORMAT. + # IF IT IS IN CIDR FORMAT, WE NEED TO CONVERT IT TO SUBNET BIT MASK FORMAT FOR THE JSON API + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + # IF THE SUBNET PARAMETER INPUT DIDN'T LOOK LIKE xxx.xxx.xxx.xxx TO REGEX... + # ... RUN IT THROUGH THE CIDR_TO_NETMASK() FUNCTION + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + # AND THEN UPDATE THE SUBNET LIST OBJECT + subnet[1] = mask + + # INCLUDE THE SUBNET LIST OBJECT IN THE DATAGRAM DICTIONARY TO BE SUBMITTED + datagram["subnet"] = subnet + + ######################### + # IF type = 'iprange' + ######################### + if datagram["type"] == "iprange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + datagram["subnet"] = ["0.0.0.0", "0.0.0.0"] + + ######################### + # IF type = 'geography' + ######################### + if datagram["type"] == "geography": + datagram["country"] = paramgram["country"] + + ######################### + # IF type = 'wildcard' + ######################### + if datagram["type"] == "wildcard": + + subnet = [] + for subnets in paramgram["wildcard"].split("/"): + subnet.append(subnets) + + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + subnet[1] = mask + + datagram["wildcard"] = subnet + + ######################### + # IF type = 'wildcard-fqdn' + ######################### + if datagram["type"] == "wildcard-fqdn": + datagram["wildcard-fqdn"] = paramgram["wildcard-fqdn"] + + ######################### + # IF type = 'fqdn' + ######################### + if datagram["type"] == "fqdn": + datagram["fqdn"] = paramgram["fqdn"] + + ######################### + # IF type = 'group' + ######################### + if datagram["type"] == "group": + datagram = { + "comment": paramgram["comment"], + "name": paramgram["group_name"], + "color": paramgram["color"], + "meta fields": {}, + "dynamic_mapping": [], + "visibility": paramgram["visibility"] + } + + members = [] + group_members = paramgram["group_members"].replace(" ", "") + try: + for member in group_members.split(","): + members.append(member) + except Exception: + pass + + datagram["member"] = members + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + # IF A GROUP, SET THE CORRECT NAME AND URL FOR THE GROUP ENDPOINT + if paramgram["ipv4"] == "group": + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp/{name}'.format(adom=paramgram["adom"], + name=paramgram["group_name"]) + # OTHERWISE WE'RE JUST GOING TO USE THE ADDRESS ENDPOINT + else: + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/address/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_ipv6(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "comment": paramgram["comment"], + "name": paramgram["name"], + "color": paramgram["color"], + "dynamic_mapping": [], + "visibility": paramgram["visibility"], + "type": paramgram["ipv6"] + } + + # SET THE CORRECT URL BASED ON THE TYPE (WE'RE DOING GROUPS IN THIS METHOD, TOO) + if datagram["type"] == "group": + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp6'.format(adom=paramgram["adom"]) + else: + url = '/pm/config/adom/{adom}/obj/firewall/address6'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'ip' + ######################### + if datagram["type"] == "ip": + datagram["type"] = "ipprefix" + datagram["ip6"] = paramgram["ipv6addr"] + + ######################### + # IF type = 'iprange' + ######################### + if datagram["type"] == "iprange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + + ######################### + # IF type = 'group' + ######################### + if datagram["type"] == "group": + datagram = None + datagram = { + "comment": paramgram["comment"], + "name": paramgram["group_name"], + "color": paramgram["color"], + "visibility": paramgram["visibility"] + } + + members = [] + group_members = paramgram["group_members"].replace(" ", "") + try: + for member in group_members.split(","): + members.append(member) + except Exception: + pass + + datagram["member"] = members + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + # IF A GROUP, SET THE CORRECT NAME AND URL FOR THE GROUP ENDPOINT + if paramgram["ipv6"] == "group": + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/addrgrp6/{name}'.format(adom=paramgram["adom"], + name=paramgram["group_name"]) + # OTHERWISE WE'RE JUST GOING TO USE THE ADDRESS ENDPOINT + else: + datagram = {} + url = '/pm/config/adom/{adom}/obj/firewall/address6/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_multicast(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add']: + # CREATE THE DATAGRAM DICTIONARY + # ENSURE THE DATAGRAM KEYS MATCH THE JSON API GUIDE ATTRIBUTES, NOT WHAT IS IN ANSIBLE + # SOME PARAMETERS SHOWN IN THIS DICTIONARY WE DON'T EVEN ASK THE USER FOR IN PLAYBOOKS BUT ARE REQUIRED + datagram = { + "associated-interface": paramgram["associated-interface"], + "comment": paramgram["comment"], + "name": paramgram["name"], + "color": paramgram["color"], + "type": paramgram["multicast"], + "visibility": paramgram["visibility"], + } + + # SET THE CORRECT URL + url = '/pm/config/adom/{adom}/obj/firewall/multicast-address'.format(adom=paramgram["adom"]) + + ######################### + # IF type = 'multicastrange' + ######################### + if paramgram["multicast"] == "multicastrange": + datagram["start-ip"] = paramgram["start-ip"] + datagram["end-ip"] = paramgram["end-ip"] + datagram["subnet"] = ["0.0.0.0", "0.0.0.0"] + + ######################### + # IF type = 'broadcastmask' + ######################### + if paramgram["multicast"] == "broadcastmask": + # EVAL THE IPV4ADDR INPUT AND SPLIT THE IP ADDRESS FROM THE MASK AND APPEND THEM TO THE SUBNET LIST + subnet = [] + for subnets in paramgram["ipv4addr"].split("/"): + subnet.append(subnets) + # CHECK THAT THE SECOND ENTRY IN THE SUBNET LIST (WHAT WAS TO THE RIGHT OF THE / CHARACTER) + # IS IN SUBNET MASK FORMAT AND NOT CIDR FORMAT. + # IF IT IS IN CIDR FORMAT, WE NEED TO CONVERT IT TO SUBNET BIT MASK FORMAT FOR THE JSON API + if not re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', subnet[1]): + # IF THE SUBNET PARAMETER INPUT DIDN'T LOOK LIKE 255.255.255.255 TO REGEX... + # ... RUN IT THROUGH THE fmgr_cidr_to_netmask() FUNCTION + mask = fmgr._tools.cidr_to_netmask(subnet[1]) + # AND THEN UPDATE THE SUBNET LIST OBJECT + subnet[1] = mask + + # INCLUDE THE SUBNET LIST OBJECT IN THE DATAGRAM DICTIONARY TO BE SUBMITTED + datagram["subnet"] = subnet + + # EVAL THE MODE PARAMETER FOR DELETE + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/multicast-address/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete"], type="str", default="add"), + + allow_routing=dict(required=False, type="str", choices=['enable', 'disable'], default="disable"), + associated_interface=dict(required=False, type="str"), + cache_ttl=dict(required=False, type="str"), + color=dict(required=False, type="str", default=22), + comment=dict(required=False, type="str"), + country=dict(required=False, type="str"), + fqdn=dict(required=False, type="str"), + name=dict(required=False, type="str"), + start_ip=dict(required=False, type="str"), + end_ip=dict(required=False, type="str"), + ipv4=dict(required=False, type="str", choices=['ipmask', 'iprange', 'fqdn', 'wildcard', + 'geography', 'wildcard-fqdn', 'group']), + visibility=dict(required=False, type="str", choices=['enable', 'disable'], default="enable"), + wildcard=dict(required=False, type="str"), + wildcard_fqdn=dict(required=False, type="str"), + ipv6=dict(required=False, type="str", choices=['ip', 'iprange', 'group']), + group_members=dict(required=False, type="str"), + group_name=dict(required=False, type="str"), + ipv4addr=dict(required=False, type="str"), + ipv6addr=dict(required=False, type="str"), + multicast=dict(required=False, type="str", choices=['multicastrange', 'broadcastmask', 'ip6']), + obj_id=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + mutually_exclusive=[ + ['ipv4', 'ipv6'], + ['ipv4', 'multicast'], + ['ipv6', 'multicast'] + ]) + paramgram = { + "adom": module.params["adom"], + "allow-routing": module.params["allow_routing"], + "associated-interface": module.params["associated_interface"], + "cache-ttl": module.params["cache_ttl"], + "color": module.params["color"], + "comment": module.params["comment"], + "country": module.params["country"], + "end-ip": module.params["end_ip"], + "fqdn": module.params["fqdn"], + "name": module.params["name"], + "start-ip": module.params["start_ip"], + "visibility": module.params["visibility"], + "wildcard": module.params["wildcard"], + "wildcard-fqdn": module.params["wildcard_fqdn"], + "ipv6": module.params["ipv6"], + "ipv4": module.params["ipv4"], + "group_members": module.params["group_members"], + "group_name": module.params["group_name"], + "ipv4addr": module.params["ipv4addr"], + "ipv6addr": module.params["ipv6addr"], + "multicast": module.params["multicast"], + "mode": module.params["mode"], + "obj-id": module.params["obj_id"], + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr._tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + try: + if paramgram["ipv4"]: + results = fmgr_fwobj_ipv4(fmgr, paramgram) + + elif paramgram["ipv6"]: + results = fmgr_fwobj_ipv6(fmgr, paramgram) + + elif paramgram["multicast"]: + results = fmgr_fwobj_multicast(fmgr, paramgram) + + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + if results is not None: + return module.exit_json(**results[1]) + else: + return module.exit_json(msg="Couldn't find a proper ipv4 or ipv6 or multicast parameter " + "to run in the logic tree. Exiting...") + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool.py new file mode 100644 index 00000000..36718197 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool.py @@ -0,0 +1,442 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_ippool +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the editing of IP Pool Objects within FortiManager. +description: + - Allows users to add/edit/delete IP Pool Objects. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + type: + description: + - IP pool type (overload, one-to-one, fixed port range, or port block allocation). + - choice | overload | IP addresses in the IP pool can be shared by clients. + - choice | one-to-one | One to one mapping. + - choice | fixed-port-range | Fixed port range. + - choice | port-block-allocation | Port block allocation. + required: false + choices: ["overload", "one-to-one", "fixed-port-range", "port-block-allocation"] + + startip: + description: + - First IPv4 address (inclusive) in the range for the address pool (format xxx.xxx.xxx.xxx, Default| 0.0.0.0). + required: false + + source_startip: + description: + - First IPv4 address (inclusive) in the range of the source addresses to be translated (format xxx.xxx.xxx.xxx, + Default| 0.0.0.0). + required: false + + source_endip: + description: + - Final IPv4 address (inclusive) in the range of the source addresses to be translated (format xxx.xxx.xxx.xxx, + Default| 0.0.0.0). + required: false + + permit_any_host: + description: + - Enable/disable full cone NAT. + - choice | disable | Disable full cone NAT. + - choice | enable | Enable full cone NAT. + required: false + choices: ["disable", "enable"] + + pba_timeout: + description: + - Port block allocation timeout (seconds). + required: false + + num_blocks_per_user: + description: + - Number of addresses blocks that can be used by a user (1 to 128, default = 8). + required: false + + name: + description: + - IP pool name. + required: false + + endip: + description: + - Final IPv4 address (inclusive) in the range for the address pool (format xxx.xxx.xxx.xxx, Default| 0.0.0.0). + required: false + + comments: + description: + - Comment. + required: false + + block_size: + description: + - Number of addresses in a block (64 to 4096, default = 128). + required: false + + associated_interface: + description: + - Associated interface name. + required: false + + arp_reply: + description: + - Enable/disable replying to ARP requests when an IP Pool is added to a policy (default = enable). + - choice | disable | Disable ARP reply. + - choice | enable | Enable ARP reply. + required: false + choices: ["disable", "enable"] + + arp_intf: + description: + - Select an interface from available options that will reply to ARP requests. (If blank, any is selected). + required: false + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameter.ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_arp_intf: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_arp_reply: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["disable", "enable"] + + dynamic_mapping_associated_interface: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_block_size: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_comments: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_num_blocks_per_user: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_pba_timeout: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_permit_any_host: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["disable", "enable"] + + dynamic_mapping_source_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_source_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_type: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + choices: ["overload", "one-to-one", "fixed-port-range", "port-block-allocation"] + + +''' + +EXAMPLES = ''' +- name: ADD FMGR_FIREWALL_IPPOOL Overload + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_overload" + comments: "Created by ansible" + type: "overload" + + # OPTIONS FOR ALL MODES + startip: "10.10.10.10" + endip: "10.10.10.100" + arp_reply: "enable" + +- name: ADD FMGR_FIREWALL_IPPOOL one-to-one + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_121" + comments: "Created by ansible" + type: "one-to-one" + + # OPTIONS FOR ALL MODES + startip: "10.10.20.10" + endip: "10.10.20.100" + arp_reply: "enable" + +- name: ADD FMGR_FIREWALL_IPPOOL FIXED PORT RANGE + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_fixed_port" + comments: "Created by ansible" + type: "fixed-port-range" + + # OPTIONS FOR ALL MODES + startip: "10.10.40.10" + endip: "10.10.40.100" + arp_reply: "enable" + # FIXED PORT RANGE OPTIONS + source_startip: "192.168.20.1" + source_endip: "192.168.20.20" + +- name: ADD FMGR_FIREWALL_IPPOOL PORT BLOCK ALLOCATION + community.network.fmgr_fwobj_ippool: + mode: "add" + adom: "ansible" + name: "Ansible_pool4_port_block_allocation" + comments: "Created by ansible" + type: "port-block-allocation" + + # OPTIONS FOR ALL MODES + startip: "10.10.30.10" + endip: "10.10.30.100" + arp_reply: "enable" + # PORT BLOCK ALLOCATION OPTIONS + block_size: "128" + num_blocks_per_user: "1" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_fwobj_ippool_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ippool'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ippool/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + type=dict(required=False, type="str", choices=["overload", + "one-to-one", + "fixed-port-range", + "port-block-allocation"]), + startip=dict(required=False, type="str"), + source_startip=dict(required=False, type="str"), + source_endip=dict(required=False, type="str"), + permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + pba_timeout=dict(required=False, type="int"), + num_blocks_per_user=dict(required=False, type="int"), + name=dict(required=False, type="str"), + endip=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + block_size=dict(required=False, type="int"), + associated_interface=dict(required=False, type="str"), + arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + arp_intf=dict(required=False, type="str"), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_arp_intf=dict(required=False, type="str"), + dynamic_mapping_arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_associated_interface=dict(required=False, type="str"), + dynamic_mapping_block_size=dict(required=False, type="int"), + dynamic_mapping_comments=dict(required=False, type="str"), + dynamic_mapping_endip=dict(required=False, type="str"), + dynamic_mapping_num_blocks_per_user=dict(required=False, type="int"), + dynamic_mapping_pba_timeout=dict(required=False, type="int"), + dynamic_mapping_permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_source_endip=dict(required=False, type="str"), + dynamic_mapping_source_startip=dict(required=False, type="str"), + dynamic_mapping_startip=dict(required=False, type="str"), + dynamic_mapping_type=dict(required=False, type="str", choices=["overload", + "one-to-one", + "fixed-port-range", + "port-block-allocation"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "type": module.params["type"], + "startip": module.params["startip"], + "source-startip": module.params["source_startip"], + "source-endip": module.params["source_endip"], + "permit-any-host": module.params["permit_any_host"], + "pba-timeout": module.params["pba_timeout"], + "num-blocks-per-user": module.params["num_blocks_per_user"], + "name": module.params["name"], + "endip": module.params["endip"], + "comments": module.params["comments"], + "block-size": module.params["block_size"], + "associated-interface": module.params["associated_interface"], + "arp-reply": module.params["arp_reply"], + "arp-intf": module.params["arp_intf"], + "dynamic_mapping": { + "arp-intf": module.params["dynamic_mapping_arp_intf"], + "arp-reply": module.params["dynamic_mapping_arp_reply"], + "associated-interface": module.params["dynamic_mapping_associated_interface"], + "block-size": module.params["dynamic_mapping_block_size"], + "comments": module.params["dynamic_mapping_comments"], + "endip": module.params["dynamic_mapping_endip"], + "num-blocks-per-user": module.params["dynamic_mapping_num_blocks_per_user"], + "pba-timeout": module.params["dynamic_mapping_pba_timeout"], + "permit-any-host": module.params["dynamic_mapping_permit_any_host"], + "source-endip": module.params["dynamic_mapping_source_endip"], + "source-startip": module.params["dynamic_mapping_source_startip"], + "startip": module.params["dynamic_mapping_startip"], + "type": module.params["dynamic_mapping_type"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + # UPDATE THE CHANGED PARAMGRAM + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_fwobj_ippool_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool6.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool6.py new file mode 100644 index 00000000..15e8977f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_ippool6.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_ippool6 +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the editing of IP Pool Objects within FortiManager. +description: + - Allows users to add/edit/delete IPv6 Pool Objects. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + startip: + description: + - First IPv6 address (inclusive) in the range for the address pool. + required: false + + name: + description: + - IPv6 IP pool name. + required: false + + endip: + description: + - Final IPv6 address (inclusive) in the range for the address pool. + required: false + + comments: + description: + - Comment. + required: false + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_comments: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_endip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + dynamic_mapping_startip: + description: + - Dynamic Mapping clone of original suffixed parameter. + required: false + + +''' + +EXAMPLES = ''' +- name: ADD FMGR_FIREWALL_IPPOOL6 + fmgr_firewall_ippool6: + mode: "add" + adom: "ansible" + startip: + name: "IPv6 IPPool" + endip: + comments: "Created by Ansible" + +- name: DELETE FMGR_FIREWALL_IPPOOL6 + fmgr_firewall_ippool6: + mode: "delete" + adom: "ansible" + name: "IPv6 IPPool" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_fwobj_ippool6_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ippool6'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ippool6/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + startip=dict(required=False, type="str"), + name=dict(required=False, type="str"), + endip=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_comments=dict(required=False, type="str"), + dynamic_mapping_endip=dict(required=False, type="str"), + dynamic_mapping_startip=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "startip": module.params["startip"], + "name": module.params["name"], + "endip": module.params["endip"], + "comments": module.params["comments"], + "dynamic_mapping": { + "comments": module.params["dynamic_mapping_comments"], + "endip": module.params["dynamic_mapping_endip"], + "startip": module.params["dynamic_mapping_startip"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_fwobj_ippool6_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_service.py new file mode 100644 index 00000000..ae2b7ff0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_service.py @@ -0,0 +1,617 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_service +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages FortiManager Firewall Service Objects. +description: + - Manages FortiManager Firewall Service Objects. + +options: + adom: + description: + -The ADOM the configuration should belong to. + required: false + default: root + + app_category: + description: + - Application category ID. + required: false + + app_service_type: + description: + - Application service type. + required: false + + application: + description: + - Application ID. + required: false + + category: + description: + - Service category. + required: false + + check_reset_range: + description: + - Enable disable RST check. + required: false + + color: + description: + - GUI icon color. + required: false + default: 22 + + comment: + description: + - Comment. + required: false + + custom_type: + description: + - Tells module what kind of custom service to be added. + choices: ['tcp_udp_sctp', 'icmp', 'icmp6', 'ip', 'http', 'ftp', 'connect', 'socks_tcp', 'socks_udp', 'all'] + default: all + required: false + + explicit_proxy: + description: + - Enable/disable explicit web proxy service. + choices: ['enable', 'disable'] + default: 'disable' + required: false + + fqdn: + description: + - Fully qualified domain name. + required: false + default: "" + + group_name: + description: + - Name of the Service Group. + required: false + + group_member: + description: + - Comma-Seperated list of members' names. + required: false + + icmp_code: + description: + - ICMP code. + required: false + + icmp_type: + description: + - ICMP type. + required: false + + iprange: + description: + - Start IP-End IP. + required: false + default: "0.0.0.0" + + name: + description: + - Custom service name. + required: false + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + required: false + + object_type: + description: + - Tells module if we are adding a custom service, category, or group. + choices: ['custom', 'group', 'category'] + required: false + + protocol: + description: + - Protocol type. + required: false + + protocol_number: + description: + - IP protocol number. + required: false + + sctp_portrange: + description: + - Multiple SCTP port ranges. Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + session_ttl: + description: + - Session TTL (300 - 604800, 0 = default). + required: false + default: 0 + + tcp_halfclose_timer: + description: + - TCP half close timeout (1 - 86400 sec, 0 = default). + required: false + default: 0 + + tcp_halfopen_timer: + description: + - TCP half close timeout (1 - 86400 sec, 0 = default). + required: false + default: 0 + + tcp_portrange: + description: + - Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + tcp_timewait_timer: + description: + - TCP half close timeout (1 - 300 sec, 0 = default). + required: false + default: 0 + + udp_idle_timer: + description: + - TCP half close timeout (0 - 86400 sec, 0 = default). + required: false + default: 0 + + udp_portrange: + description: + - Comma separated list of destination ports to add (i.e. '443,80'). + - Syntax is + - If no sourcePort is defined, it assumes all of them. + - Ranges can be defined with a hyphen - + - Examples -- '443' (destPort 443 only) '443:1000-2000' (destPort 443 from source ports 1000-2000). + - String multiple together in same quotes, comma separated. ('443:1000-2000, 80:1000-2000'). + required: false + + visibility: + description: + - Enable/disable service visibility. + required: false + choices: ["enable", "disable"] + default: "enable" + +''' + +EXAMPLES = ''' +- name: ADD A CUSTOM SERVICE FOR TCP/UDP/SCP + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_service" + object_type: "custom" + custom_type: "tcp_udp_sctp" + tcp_portrange: "443" + udp_portrange: "51" + sctp_portrange: "100" + +- name: ADD A CUSTOM SERVICE FOR TCP/UDP/SCP WITH SOURCE RANGES AND MULTIPLES + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_serviceWithSource" + object_type: "custom" + custom_type: "tcp_udp_sctp" + tcp_portrange: "443:2000-1000,80-82:10000-20000" + udp_portrange: "51:100-200,162:200-400" + sctp_portrange: "100:2000-2500" + +- name: ADD A CUSTOM SERVICE FOR ICMP + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp" + object_type: "custom" + custom_type: "icmp" + icmp_type: "8" + icmp_code: "3" + +- name: ADD A CUSTOM SERVICE FOR ICMP6 + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp6" + object_type: "custom" + custom_type: "icmp6" + icmp_type: "5" + icmp_code: "1" + +- name: ADD A CUSTOM SERVICE FOR IP - GRE + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_icmp6" + object_type: "custom" + custom_type: "ip" + protocol_number: "47" + +- name: ADD A CUSTOM PROXY FOR ALL WITH SOURCE RANGES AND MULTIPLES + community.network.fmgr_fwobj_service: + adom: "ansible" + name: "ansible_custom_proxy_all" + object_type: "custom" + custom_type: "all" + explicit_proxy: "enable" + tcp_portrange: "443:2000-1000,80-82:10000-20000" + iprange: "www.ansible.com" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_fwobj_service_custom(fmgr, paramgram): + """ + description: + - the tcp and udp-portrange parameters are in a list when there are multiple. they are not in a list when they + singular or by themselves (only 1 was listed) + - the syntax for this is (destPort:sourcePort). Ranges are (xxxx-xxxx) i.e. 443:443, or 443:1000-2000. + - if you leave out the second field after the colon (source port) it assumes any source port (which is usual) + - multiples would look like ['443:1000-2000','80'] + - a single would look simple like "443:1000-2000" without the list around it ( a string!) + - the protocol parameter is the protocol NUMBER, not the string of it. + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + # SET THE URL FOR ADD / SET + url = '/pm/config/adom/{adom}/obj/firewall/service/custom'.format(adom=paramgram["adom"]) + # BUILD THE DEFAULT DATAGRAM + datagram = { + # ADVANCED OPTIONS + "app-category": paramgram["app-category"], + "app-service-type": paramgram["app-service-type"], + "application": paramgram["application"], + "category": paramgram["category"], + "check-reset-range": paramgram["check-reset-range"], + "color": paramgram["color"], + "session-ttl": paramgram["session-ttl"], + "tcp-halfclose-timer": paramgram["tcp-halfclose-timer"], + "tcp-halfopen-timer": paramgram["tcp-halfopen-timer"], + "tcp-timewait-timer": paramgram["tcp-timewait-timer"], + "udp-idle-timer": paramgram["udp-idle-timer"], + "visibility": paramgram["visibility"], + "comment": paramgram["comment"], + "proxy": paramgram["explicit-proxy"], + "name": paramgram["name"] + } + + if datagram["proxy"] == "disable": + ####################################### + # object-type = "TCP/UDP/SCTP" + ####################################### + if paramgram["custom_type"] == "tcp_udp_sctp": + datagram["protocol"] = "TCP/UDP/SCTP" + # PROCESS PORT RANGES TO PUT INTO THE PROPER SYNTAX + if paramgram["tcp-portrange"] is not None: + tcp_list = [] + for tcp in paramgram["tcp-portrange"].split(","): + tcp = tcp.strip() + tcp_list.append(tcp) + datagram["tcp-portrange"] = tcp_list + + if paramgram["udp-portrange"] is not None: + udp_list = [] + for udp in paramgram["udp-portrange"].split(","): + udp = udp.strip() + udp_list.append(udp) + datagram["udp-portrange"] = udp_list + + if paramgram["sctp-portrange"] is not None: + sctp_list = [] + for sctp in paramgram["sctp-portrange"].split(","): + sctp = sctp.strip() + sctp_list.append(sctp) + datagram["sctp-portrange"] = sctp_list + + ####################################### + # object-type = "ICMP" + ####################################### + if paramgram["custom_type"] == "icmp": + datagram["icmpcode"] = paramgram["icmp_code"] + datagram["icmptype"] = paramgram["icmp_type"] + datagram["protocol"] = "ICMP" + + ####################################### + # object-type = "ICMP6" + ####################################### + if paramgram["custom_type"] == "icmp6": + datagram["icmpcode"] = paramgram["icmp_code"] + datagram["icmptype"] = paramgram["icmp_type"] + datagram["protocol"] = "ICMP6" + + ####################################### + # object-type = "IP" + ####################################### + if paramgram["custom_type"] == "ip": + datagram["protocol"] = "IP" + datagram["protocol-number"] = paramgram["protocol-number"] + + ####################################### + # object-type in any of the explicit proxy options + ####################################### + if datagram["proxy"] == "enable": + datagram["protocol"] = paramgram["custom_type"].upper() + datagram["iprange"] = paramgram["iprange"] + + # PROCESS PROXY TCP PORT RANGES TO PUT INTO THE PROPER SYNTAX + if paramgram["tcp-portrange"] is not None: + tcp_list = [] + for tcp in paramgram["tcp-portrange"].split(","): + tcp = tcp.strip() + tcp_list.append(tcp) + datagram["tcp-portrange"] = tcp_list + + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/custom' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_service_group(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/config/adom/{adom}/obj/firewall/service/group'.format(adom=paramgram["adom"]) + datagram = { + "name": paramgram["group-name"], + "comment": paramgram["comment"], + "proxy": paramgram["explicit-proxy"], + "color": paramgram["color"] + } + + members = paramgram["group-member"] + member = [] + for obj in members.split(","): + member.append(obj.strip()) + datagram["member"] = member + + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/group' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["group-name"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwobj_service_category(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/config/adom/{adom}/obj/firewall/service/category'.format(adom=paramgram["adom"]) + # GET RID OF ANY WHITESPACE + category = paramgram["category"] + category = category.strip() + + datagram = { + "name": paramgram["category"], + "comment": "Created by Ansible" + } + + # IF MODE = DELETE + if paramgram["mode"] == "delete": + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/config/adom/{adom}/obj/firewall/service/category' \ + '/{name}'.format(adom=paramgram["adom"], name=paramgram["category"]) + + datagram = scrub_dict(datagram) + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(required=False, type="str", choices=['add', 'set', 'delete'], default="add"), + app_category=dict(required=False, type="str"), + app_service_type=dict(required=False, type="str"), + application=dict(required=False, type="str"), + category=dict(required=False, type="str"), + check_reset_range=dict(required=False, type="str"), + color=dict(required=False, type="int", default=22), + comment=dict(required=False, type="str"), + custom_type=dict(required=False, type="str", choices=['tcp_udp_sctp', 'icmp', 'icmp6', 'ip', 'http', 'ftp', + 'connect', 'socks_tcp', 'socks_udp', 'all'], + default="all"), + explicit_proxy=dict(required=False, type="str", choices=['enable', 'disable'], default="disable"), + fqdn=dict(required=False, type="str", default=""), + group_name=dict(required=False, type="str"), + group_member=dict(required=False, type="str"), + icmp_code=dict(required=False, type="int"), + icmp_type=dict(required=False, type="int"), + iprange=dict(required=False, type="str", default="0.0.0.0"), + name=dict(required=False, type="str"), + protocol=dict(required=False, type="str"), + protocol_number=dict(required=False, type="int"), + sctp_portrange=dict(required=False, type="str"), + session_ttl=dict(required=False, type="int", default=0), + object_type=dict(required=False, type="str", choices=['custom', 'group', 'category']), + tcp_halfclose_timer=dict(required=False, type="int", default=0), + tcp_halfopen_timer=dict(required=False, type="int", default=0), + tcp_portrange=dict(required=False, type="str"), + tcp_timewait_timer=dict(required=False, type="int", default=0), + udp_idle_timer=dict(required=False, type="int", default=0), + udp_portrange=dict(required=False, type="str"), + visibility=dict(required=False, type="str", default="enable", choices=["enable", "disable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE DATAGRAM + paramgram = { + "adom": module.params["adom"], + "app-category": module.params["app_category"], + "app-service-type": module.params["app_service_type"], + "application": module.params["application"], + "category": module.params["category"], + "check-reset-range": module.params["check_reset_range"], + "color": module.params["color"], + "comment": module.params["comment"], + "custom_type": module.params["custom_type"], + "explicit-proxy": module.params["explicit_proxy"], + "fqdn": module.params["fqdn"], + "group-name": module.params["group_name"], + "group-member": module.params["group_member"], + "icmp_code": module.params["icmp_code"], + "icmp_type": module.params["icmp_type"], + "iprange": module.params["iprange"], + "name": module.params["name"], + "mode": module.params["mode"], + "protocol": module.params["protocol"], + "protocol-number": module.params["protocol_number"], + "sctp-portrange": module.params["sctp_portrange"], + "object_type": module.params["object_type"], + "session-ttl": module.params["session_ttl"], + "tcp-halfclose-timer": module.params["tcp_halfclose_timer"], + "tcp-halfopen-timer": module.params["tcp_halfopen_timer"], + "tcp-portrange": module.params["tcp_portrange"], + "tcp-timewait-timer": module.params["tcp_timewait_timer"], + "udp-idle-timer": module.params["udp_idle_timer"], + "udp-portrange": module.params["udp_portrange"], + "visibility": module.params["visibility"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + # CHECK FOR CATEGORIES TO ADD + # THIS IS ONLY WHEN OBJECT_TYPE ISN'T SPECIFICALLY ADDING A CATEGORY! + # WE NEED TO ADD THE CATEGORY BEFORE ADDING THE OBJECT + # IF ANY category ARE DEFINED AND MODE IS ADD OR SET LETS ADD THOSE + # THIS IS A "BLIND ADD" AND THE EXIT CODE FOR OBJECT ALREADY EXISTS IS TREATED AS A PASS + if paramgram["category"] is not None and paramgram["mode"] in ['add', 'set'] \ + and paramgram["object_type"] != "category": + category_add = fmgr_fwobj_service_category(fmgr, paramgram) + fmgr.govern_response(module=module, results=category_add, + ansible_facts=fmgr.construct_ansible_facts(category_add, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS CATEGORY... + if paramgram["object_type"] == 'category': + results = fmgr_fwobj_service_category(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS CUSTOM... + if paramgram["object_type"] == 'custom': + results = fmgr_fwobj_service_custom(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT_TYPE IS GROUP... + if paramgram["object_type"] == 'group': + results = fmgr_fwobj_service_group(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, good_codes=[0, -2, -3], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_vip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_vip.py new file mode 100644 index 00000000..d1902b42 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwobj_vip.py @@ -0,0 +1,2424 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwobj_vip +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages Virtual IPs objects in FortiManager +description: + - Manages Virtual IP objects in FortiManager for IPv4 + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + websphere_server: + description: + - Enable to add an HTTP header to indicate SSL offloading for a WebSphere server. + - choice | disable | Do not add HTTP header indicating SSL offload for WebSphere server. + - choice | enable | Add HTTP header indicating SSL offload for WebSphere server. + required: false + choices: ["disable", "enable"] + + weblogic_server: + description: + - Enable to add an HTTP header to indicate SSL offloading for a WebLogic server. + - choice | disable | Do not add HTTP header indicating SSL offload for WebLogic server. + - choice | enable | Add HTTP header indicating SSL offload for WebLogic server. + required: false + choices: ["disable", "enable"] + + type: + description: + - Configure a static NAT, load balance, server load balance, DNS translation, or FQDN VIP. + - choice | static-nat | Static NAT. + - choice | load-balance | Load balance. + - choice | server-load-balance | Server load balance. + - choice | dns-translation | DNS translation. + - choice | fqdn | FQDN Translation + required: false + choices: ["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"] + + ssl_server_session_state_type: + description: + - How to expire SSL sessions for the segment of the SSL connection between the server and the FortiGate. + - choice | disable | Do not keep session states. + - choice | time | Expire session states after this many minutes. + - choice | count | Expire session states when this maximum is reached. + - choice | both | Expire session states based on time or count, whichever occurs first. + required: false + choices: ["disable", "time", "count", "both"] + + ssl_server_session_state_timeout: + description: + - Number of minutes to keep FortiGate to Server SSL session state. + required: false + + ssl_server_session_state_max: + description: + - Maximum number of FortiGate to Server SSL session states to keep. + required: false + + ssl_server_min_version: + description: + - Lowest SSL/TLS version acceptable from a server. Use the client setting by default. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + - choice | client | Use same value as client configuration. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + ssl_server_max_version: + description: + - Highest SSL/TLS version acceptable from a server. Use the client setting by default. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + - choice | client | Use same value as client configuration. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + ssl_server_algorithm: + description: + - Permitted encryption algorithms for the server side of SSL full mode sessions according to encryption strength + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | custom | Custom encryption. Use ssl-server-cipher-suites to select the cipher suites that are allowed. + - choice | client | Use the same encryption algorithms for both client and server sessions. + required: false + choices: ["high", "low", "medium", "custom", "client"] + + ssl_send_empty_frags: + description: + - Enable/disable sending empty fragments to avoid CBC IV attacks (SSL 3.0 & TLS 1.0 only). + - choice | disable | Do not send empty fragments. + - choice | enable | Send empty fragments. + required: false + choices: ["disable", "enable"] + + ssl_pfs: + description: + - Select the cipher suites that can be used for SSL perfect forward secrecy (PFS). + - choice | require | Allow only Diffie-Hellman cipher-suites, so PFS is applied. + - choice | deny | Allow only non-Diffie-Hellman cipher-suites, so PFS is not applied. + - choice | allow | Allow use of any cipher suite so PFS may or may not be used depending on the cipher suite + required: false + choices: ["require", "deny", "allow"] + + ssl_mode: + description: + - Apply SSL offloading mode + - choice | half | Client to FortiGate SSL. + - choice | full | Client to FortiGate and FortiGate to Server SSL. + required: false + choices: ["half", "full"] + + ssl_min_version: + description: + - Lowest SSL/TLS version acceptable from a client. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_max_version: + description: + - Highest SSL/TLS version acceptable from a client. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_http_match_host: + description: + - Enable/disable HTTP host matching for location conversion. + - choice | disable | Do not match HTTP host. + - choice | enable | Match HTTP host in response header. + required: false + choices: ["disable", "enable"] + + ssl_http_location_conversion: + description: + - Enable to replace HTTP with HTTPS in the reply's Location HTTP header field. + - choice | disable | Disable HTTP location conversion. + - choice | enable | Enable HTTP location conversion. + required: false + choices: ["disable", "enable"] + + ssl_hsts_include_subdomains: + description: + - Indicate that HSTS header applies to all subdomains. + - choice | disable | HSTS header does not apply to subdomains. + - choice | enable | HSTS header applies to subdomains. + required: false + choices: ["disable", "enable"] + + ssl_hsts_age: + description: + - Number of seconds the client should honour the HSTS setting. + required: false + + ssl_hsts: + description: + - Enable/disable including HSTS header in response. + - choice | disable | Do not add a HSTS header to each a HTTP response. + - choice | enable | Add a HSTS header to each HTTP response. + required: false + choices: ["disable", "enable"] + + ssl_hpkp_report_uri: + description: + - URL to report HPKP violations to. + required: false + + ssl_hpkp_primary: + description: + - Certificate to generate primary HPKP pin from. + required: false + + ssl_hpkp_include_subdomains: + description: + - Indicate that HPKP header applies to all subdomains. + - choice | disable | HPKP header does not apply to subdomains. + - choice | enable | HPKP header applies to subdomains. + required: false + choices: ["disable", "enable"] + + ssl_hpkp_backup: + description: + - Certificate to generate backup HPKP pin from. + required: false + + ssl_hpkp_age: + description: + - Number of seconds the client should honour the HPKP setting. + required: false + + ssl_hpkp: + description: + - Enable/disable including HPKP header in response. + - choice | disable | Do not add a HPKP header to each HTTP response. + - choice | enable | Add a HPKP header to each a HTTP response. + - choice | report-only | Add a HPKP Report-Only header to each HTTP response. + required: false + choices: ["disable", "enable", "report-only"] + + ssl_dh_bits: + description: + - Number of bits to use in the Diffie-Hellman exchange for RSA encryption of SSL sessions. + - choice | 768 | 768-bit Diffie-Hellman prime. + - choice | 1024 | 1024-bit Diffie-Hellman prime. + - choice | 1536 | 1536-bit Diffie-Hellman prime. + - choice | 2048 | 2048-bit Diffie-Hellman prime. + - choice | 3072 | 3072-bit Diffie-Hellman prime. + - choice | 4096 | 4096-bit Diffie-Hellman prime. + required: false + choices: ["768", "1024", "1536", "2048", "3072", "4096"] + + ssl_client_session_state_type: + description: + - How to expire SSL sessions for the segment of the SSL connection between the client and the FortiGate. + - choice | disable | Do not keep session states. + - choice | time | Expire session states after this many minutes. + - choice | count | Expire session states when this maximum is reached. + - choice | both | Expire session states based on time or count, whichever occurs first. + required: false + choices: ["disable", "time", "count", "both"] + + ssl_client_session_state_timeout: + description: + - Number of minutes to keep client to FortiGate SSL session state. + required: false + + ssl_client_session_state_max: + description: + - Maximum number of client to FortiGate SSL session states to keep. + required: false + + ssl_client_renegotiation: + description: + - Allow, deny, or require secure renegotiation of client sessions to comply with RFC 5746. + - choice | deny | Abort any client initiated SSL re-negotiation attempt. + - choice | allow | Allow a SSL client to renegotiate. + - choice | secure | Abort any client initiated SSL re-negotiation attempt that does not use RFC 5746. + required: false + choices: ["deny", "allow", "secure"] + + ssl_client_fallback: + description: + - Enable/disable support for preventing Downgrade Attacks on client connections (RFC 7507). + - choice | disable | Disable. + - choice | enable | Enable. + required: false + choices: ["disable", "enable"] + + ssl_certificate: + description: + - The name of the SSL certificate to use for SSL acceleration. + required: false + + ssl_algorithm: + description: + - Permitted encryption algorithms for SSL sessions according to encryption strength. + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + - choice | custom | Custom encryption. Use config ssl-cipher-suites to select the cipher suites that are allowed. + required: false + choices: ["high", "medium", "low", "custom"] + + srcintf_filter: + description: + - Interfaces to which the VIP applies. Separate the names with spaces. + required: false + + src_filter: + description: + - Source address filter. Each address must be either an IP/subnet (x.x.x.x/n) or a range (x.x.x.x-y.y.y.y). + - Separate addresses with spaces. + required: false + + service: + description: + - Service name. + required: false + + server_type: + description: + - Protocol to be load balanced by the virtual server (also called the server load balance virtual IP). + - choice | http | HTTP + - choice | https | HTTPS + - choice | ssl | SSL + - choice | tcp | TCP + - choice | udp | UDP + - choice | ip | IP + - choice | imaps | IMAPS + - choice | pop3s | POP3S + - choice | smtps | SMTPS + required: false + choices: ["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"] + + protocol: + description: + - Protocol to use when forwarding packets. + - choice | tcp | TCP. + - choice | udp | UDP. + - choice | sctp | SCTP. + - choice | icmp | ICMP. + required: false + choices: ["tcp", "udp", "sctp", "icmp"] + + portmapping_type: + description: + - Port mapping type. + - choice | 1-to-1 | One to one. + - choice | m-to-n | Many to many. + required: false + choices: ["1-to-1", "m-to-n"] + + portforward: + description: + - Enable/disable port forwarding. + - choice | disable | Disable port forward. + - choice | enable | Enable port forward. + required: false + choices: ["disable", "enable"] + + persistence: + description: + - Configure how to make sure that clients connect to the same server every time they make a request that is part + - of the same session. + - choice | none | None. + - choice | http-cookie | HTTP cookie. + - choice | ssl-session-id | SSL session ID. + required: false + choices: ["none", "http-cookie", "ssl-session-id"] + + outlook_web_access: + description: + - Enable to add the Front-End-Https header for Microsoft Outlook Web Access. + - choice | disable | Disable Outlook Web Access support. + - choice | enable | Enable Outlook Web Access support. + required: false + choices: ["disable", "enable"] + + nat_source_vip: + description: + - Enable to prevent unintended servers from using a virtual IP. + - Disable to use the actual IP address of the server as the source address. + - choice | disable | Do not force to NAT as VIP. + - choice | enable | Force to NAT as VIP. + required: false + choices: ["disable", "enable"] + + name: + description: + - Virtual IP name. + required: false + + monitor: + description: + - Name of the health check monitor to use when polling to determine a virtual server's connectivity status. + required: false + + max_embryonic_connections: + description: + - Maximum number of incomplete connections. + required: false + + mappedport: + description: + - Port number range on the destination network to which the external port number range is mapped. + required: false + + mappedip: + description: + - IP address or address range on the destination network to which the external IP address is mapped. + required: false + + mapped_addr: + description: + - Mapped FQDN address name. + required: false + + ldb_method: + description: + - Method used to distribute sessions to real servers. + - choice | static | Distribute to server based on source IP. + - choice | round-robin | Distribute to server based round robin order. + - choice | weighted | Distribute to server based on weight. + - choice | least-session | Distribute to server with lowest session count. + - choice | least-rtt | Distribute to server with lowest Round-Trip-Time. + - choice | first-alive | Distribute to the first server that is alive. + - choice | http-host | Distribute to server based on host field in HTTP header. + required: false + choices: ["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", "http-host"] + + https_cookie_secure: + description: + - Enable/disable verification that inserted HTTPS cookies are secure. + - choice | disable | Do not mark cookie as secure, allow sharing between an HTTP and HTTPS connection. + - choice | enable | Mark inserted cookie as secure, cookie can only be used for HTTPS a connection. + required: false + choices: ["disable", "enable"] + + http_multiplex: + description: + - Enable/disable HTTP multiplexing. + - choice | disable | Disable HTTP session multiplexing. + - choice | enable | Enable HTTP session multiplexing. + required: false + choices: ["disable", "enable"] + + http_ip_header_name: + description: + - For HTTP multiplexing, enter a custom HTTPS header name. The orig client IP address is added to this header. + - If empty, X-Forwarded-For is used. + required: false + + http_ip_header: + description: + - For HTTP multiplexing, enable to add the original client IP address in the XForwarded-For HTTP header. + - choice | disable | Disable adding HTTP header. + - choice | enable | Enable adding HTTP header. + required: false + choices: ["disable", "enable"] + + http_cookie_share: + description: + - Control sharing of cookies across virtual servers. same-ip means a cookie from one virtual server can be used + - by another. Disable stops cookie sharing. + - choice | disable | Only allow HTTP cookie to match this virtual server. + - choice | same-ip | Allow HTTP cookie to match any virtual server with same IP. + required: false + choices: ["disable", "same-ip"] + + http_cookie_path: + description: + - Limit HTTP cookie persistence to the specified path. + required: false + + http_cookie_generation: + description: + - Generation of HTTP cookie to be accepted. Changing invalidates all existing cookies. + required: false + + http_cookie_domain_from_host: + description: + - Enable/disable use of HTTP cookie domain from host field in HTTP. + - choice | disable | Disable use of HTTP cookie domain from host field in HTTP (use http-cooke-domain setting). + - choice | enable | Enable use of HTTP cookie domain from host field in HTTP. + required: false + choices: ["disable", "enable"] + + http_cookie_domain: + description: + - Domain that HTTP cookie persistence should apply to. + required: false + + http_cookie_age: + description: + - Time in minutes that client web browsers should keep a cookie. Default is 60 seconds. 0 = no time limit. + required: false + + gratuitous_arp_interval: + description: + - Enable to have the VIP send gratuitous ARPs. 0=disabled. Set from 5 up to 8640000 seconds to enable. + required: false + + extport: + description: + - Incoming port number range that you want to map to a port number range on the destination network. + required: false + + extip: + description: + - IP address or address range on the external interface that you want to map to an address or address range on t + - he destination network. + required: false + + extintf: + description: + - Interface connected to the source network that receives the packets that will be forwarded to the destination + - network. + required: false + + extaddr: + description: + - External FQDN address name. + required: false + + dns_mapping_ttl: + description: + - DNS mapping TTL (Set to zero to use TTL in DNS response, default = 0). + required: false + + comment: + description: + - Comment. + required: false + + color: + description: + - Color of icon on the GUI. + required: false + + arp_reply: + description: + - Enable to respond to ARP requests for this virtual IP address. Enabled by default. + - choice | disable | Disable ARP reply. + - choice | enable | Enable ARP reply. + required: false + choices: ["disable", "enable"] + + dynamic_mapping: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + dynamic_mapping_arp_reply: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_color: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_comment: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_dns_mapping_ttl: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extaddr: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extintf: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_extport: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_gratuitous_arp_interval: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_domain: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_domain_from_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_http_cookie_generation: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_path: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_cookie_share: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | same-ip | + required: false + choices: ["disable", "same-ip"] + + dynamic_mapping_http_ip_header: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_http_ip_header_name: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_http_multiplex: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_https_cookie_secure: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ldb_method: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | static | + - choice | round-robin | + - choice | weighted | + - choice | least-session | + - choice | least-rtt | + - choice | first-alive | + - choice | http-host | + required: false + choices: ["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", "http-host"] + + dynamic_mapping_mapped_addr: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_mappedip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_mappedport: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_max_embryonic_connections: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_monitor: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_nat_source_vip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_outlook_web_access: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_persistence: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | none | + - choice | http-cookie | + - choice | ssl-session-id | + required: false + choices: ["none", "http-cookie", "ssl-session-id"] + + dynamic_mapping_portforward: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_portmapping_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | 1-to-1 | + - choice | m-to-n | + required: false + choices: ["1-to-1", "m-to-n"] + + dynamic_mapping_protocol: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | tcp | + - choice | udp | + - choice | sctp | + - choice | icmp | + required: false + choices: ["tcp", "udp", "sctp", "icmp"] + + dynamic_mapping_server_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | http | + - choice | https | + - choice | ssl | + - choice | tcp | + - choice | udp | + - choice | ip | + - choice | imaps | + - choice | pop3s | + - choice | smtps | + required: false + choices: ["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"] + + dynamic_mapping_service: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_src_filter: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_srcintf_filter: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_algorithm: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | high | + - choice | medium | + - choice | low | + - choice | custom | + required: false + choices: ["high", "medium", "low", "custom"] + + dynamic_mapping_ssl_certificate: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_fallback: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_client_renegotiation: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | deny | + - choice | allow | + - choice | secure | + required: false + choices: ["deny", "allow", "secure"] + + dynamic_mapping_ssl_client_session_state_max: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_session_state_timeout: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_client_session_state_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | time | + - choice | count | + - choice | both | + required: false + choices: ["disable", "time", "count", "both"] + + dynamic_mapping_ssl_dh_bits: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | 768 | + - choice | 1024 | + - choice | 1536 | + - choice | 2048 | + - choice | 3072 | + - choice | 4096 | + required: false + choices: ["768", "1024", "1536", "2048", "3072", "4096"] + + dynamic_mapping_ssl_hpkp: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + - choice | report-only | + required: false + choices: ["disable", "enable", "report-only"] + + dynamic_mapping_ssl_hpkp_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_backup: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_include_subdomains: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_hpkp_primary: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hpkp_report_uri: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hsts: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_hsts_age: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_hsts_include_subdomains: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_http_location_conversion: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_http_match_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_max_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + dynamic_mapping_ssl_min_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + dynamic_mapping_ssl_mode: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | half | + - choice | full | + required: false + choices: ["half", "full"] + + dynamic_mapping_ssl_pfs: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | require | + - choice | deny | + - choice | allow | + required: false + choices: ["require", "deny", "allow"] + + dynamic_mapping_ssl_send_empty_frags: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_ssl_server_algorithm: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | high | + - choice | low | + - choice | medium | + - choice | custom | + - choice | client | + required: false + choices: ["high", "low", "medium", "custom", "client"] + + dynamic_mapping_ssl_server_max_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + - choice | client | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + dynamic_mapping_ssl_server_min_version: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | ssl-3.0 | + - choice | tls-1.0 | + - choice | tls-1.1 | + - choice | tls-1.2 | + - choice | client | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"] + + dynamic_mapping_ssl_server_session_state_max: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_server_session_state_timeout: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_server_session_state_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | time | + - choice | count | + - choice | both | + required: false + choices: ["disable", "time", "count", "both"] + + dynamic_mapping_type: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | static-nat | + - choice | load-balance | + - choice | server-load-balance | + - choice | dns-translation | + - choice | fqdn | + required: false + choices: ["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"] + + dynamic_mapping_weblogic_server: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_websphere_server: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + required: false + choices: ["disable", "enable"] + + dynamic_mapping_realservers_client_ip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_healthcheck: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | disable | + - choice | enable | + - choice | vip | + required: false + choices: ["disable", "enable", "vip"] + + dynamic_mapping_realservers_holddown_interval: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_http_host: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_ip: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_max_connections: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_monitor: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_port: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_seq: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_realservers_status: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | active | + - choice | standby | + - choice | disable | + required: false + choices: ["active", "standby", "disable"] + + dynamic_mapping_realservers_weight: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + required: false + + dynamic_mapping_ssl_cipher_suites_cipher: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - choice | TLS-RSA-WITH-RC4-128-MD5 | + - choice | TLS-RSA-WITH-RC4-128-SHA | + - choice | TLS-RSA-WITH-DES-CBC-SHA | + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-RSA-WITH-SEED-CBC-SHA | + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + dynamic_mapping_ssl_cipher_suites_versions: + description: + - Dynamic Mapping Version of Suffixed Option Name. Sub-Table. Same Descriptions as Parent. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | + - flag | tls-1.0 | + - flag | tls-1.1 | + - flag | tls-1.2 | + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + realservers: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + realservers_client_ip: + description: + - Only clients in this IP range can connect to this real server. + required: false + + realservers_healthcheck: + description: + - Enable to check the responsiveness of the real server before forwarding traffic. + - choice | disable | Disable per server health check. + - choice | enable | Enable per server health check. + - choice | vip | Use health check defined in VIP. + required: false + choices: ["disable", "enable", "vip"] + + realservers_holddown_interval: + description: + - Time in seconds that the health check monitor monitors an unresponsive server that should be active. + required: false + + realservers_http_host: + description: + - HTTP server domain name in HTTP header. + required: false + + realservers_ip: + description: + - IP address of the real server. + required: false + + realservers_max_connections: + description: + - Max number of active connections that can be directed to the real server. When reached, sessions are sent to + - their real servers. + required: false + + realservers_monitor: + description: + - Name of the health check monitor to use when polling to determine a virtual server's connectivity status. + required: false + + realservers_port: + description: + - Port for communicating with the real server. Required if port forwarding is enabled. + required: false + + realservers_seq: + description: + - Real Server Sequence Number + required: false + + realservers_status: + description: + - Set the status of the real server to active so that it can accept traffic. + - Or on standby or disabled so no traffic is sent. + - choice | active | Server status active. + - choice | standby | Server status standby. + - choice | disable | Server status disable. + required: false + choices: ["active", "standby", "disable"] + + realservers_weight: + description: + - Weight of the real server. If weighted load balancing is enabled, the server with the highest weight gets more + - connections. + required: false + + ssl_cipher_suites: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_cipher_suites_cipher: + description: + - Cipher suite name. + - choice | TLS-RSA-WITH-RC4-128-MD5 | Cipher suite TLS-RSA-WITH-RC4-128-MD5. + - choice | TLS-RSA-WITH-RC4-128-SHA | Cipher suite TLS-RSA-WITH-RC4-128-SHA. + - choice | TLS-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-RSA-WITH-DES-CBC-SHA. + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-DES-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | Cipher suite TLS-ECDHE-RSA-WITH-RC4-128-SHA. + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-DSS-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DSS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC_SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC_SHA384. + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-DES-CBC-SHA. + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + ssl_cipher_suites_versions: + description: + - SSL/TLS versions that the cipher suite can be used with. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | SSL 3.0. + - flag | tls-1.0 | TLS 1.0. + - flag | tls-1.1 | TLS 1.1. + - flag | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + ssl_server_cipher_suites: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_server_cipher_suites_cipher: + description: + - Cipher suite name. + - choice | TLS-RSA-WITH-RC4-128-MD5 | Cipher suite TLS-RSA-WITH-RC4-128-MD5. + - choice | TLS-RSA-WITH-RC4-128-SHA | Cipher suite TLS-RSA-WITH-RC4-128-SHA. + - choice | TLS-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-RSA-WITH-DES-CBC-SHA. + - choice | TLS-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-DHE-RSA-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-DES-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-RSA-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-RC4-128-SHA | Cipher suite TLS-ECDHE-RSA-WITH-RC4-128-SHA. + - choice | TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA. + - choice | TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256 | Suite TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256 | Cipher suite TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-128-GCM-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-AES-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-DHE-DSS-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-RSA-WITH-AES-128-GCM-SHA256 | Cipher suite TLS-RSA-WITH-AES-128-GCM-SHA256. + - choice | TLS-RSA-WITH-AES-256-GCM-SHA384 | Cipher suite TLS-RSA-WITH-AES-256-GCM-SHA384. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA | Cipher suite TLS-DSS-RSA-WITH-CAMELLIA-128-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-SEED-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-SEED-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256. + - choice | TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC_SHA256. + - choice | TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384 | Cipher suite TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC_SHA384. + - choice | TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA. + - choice | TLS-DHE-DSS-WITH-DES-CBC-SHA | Cipher suite TLS-DHE-DSS-WITH-DES-CBC-SHA. + required: false + choices: ["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"] + + ssl_server_cipher_suites_priority: + description: + - SSL/TLS cipher suites priority. + required: false + + ssl_server_cipher_suites_versions: + description: + - SSL/TLS versions that the cipher suite can be used with. + - FLAG Based Options. Specify multiple in list form. + - flag | ssl-3.0 | SSL 3.0. + - flag | tls-1.0 | TLS 1.0. + - flag | tls-1.1 | TLS 1.1. + - flag | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + +''' + +EXAMPLES = ''' +# BASIC FULL STATIC NAT MAPPING +- name: EDIT FMGR_FIREWALL_VIP SNAT + community.network.fmgr_fwobj_vip: + name: "Basic StaticNAT Map" + mode: "set" + adom: "ansible" + type: "static-nat" + extip: "82.72.192.185" + extintf: "any" + mappedip: "10.7.220.25" + comment: "Created by Ansible" + color: "17" + +# BASIC PORT PNAT MAPPING +- name: EDIT FMGR_FIREWALL_VIP PNAT + community.network.fmgr_fwobj_vip: + name: "Basic PNAT Map Port 10443" + mode: "set" + adom: "ansible" + type: "static-nat" + extip: "82.72.192.185" + extport: "10443" + extintf: "any" + portforward: "enable" + protocol: "tcp" + mappedip: "10.7.220.25" + mappedport: "443" + comment: "Created by Ansible" + color: "17" + +# BASIC DNS TRANSLATION NAT +- name: EDIT FMGR_FIREWALL_DNST + community.network.fmgr_fwobj_vip: + name: "Basic DNS Translation" + mode: "set" + adom: "ansible" + type: "dns-translation" + extip: "192.168.0.1-192.168.0.100" + extintf: "dmz" + mappedip: "3.3.3.0/24, 4.0.0.0/24" + comment: "Created by Ansible" + color: "12" + +# BASIC FQDN NAT +- name: EDIT FMGR_FIREWALL_FQDN + community.network.fmgr_fwobj_vip: + name: "Basic FQDN Translation" + mode: "set" + adom: "ansible" + type: "fqdn" + mapped_addr: "google-play" + comment: "Created by Ansible" + color: "5" + +# DELETE AN ENTRY +- name: DELETE FMGR_FIREWALL_VIP PNAT + community.network.fmgr_fwobj_vip: + name: "Basic PNAT Map Port 10443" + mode: "delete" + adom: "ansible" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_firewall_vip_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/vip'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/vip/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + websphere_server=dict(required=False, type="str", choices=["disable", "enable"]), + weblogic_server=dict(required=False, type="str", choices=["disable", "enable"]), + type=dict(required=False, type="str", + choices=["static-nat", "load-balance", "server-load-balance", "dns-translation", "fqdn"]), + ssl_server_session_state_type=dict(required=False, type="str", choices=["disable", "time", "count", "both"]), + ssl_server_session_state_timeout=dict(required=False, type="int"), + ssl_server_session_state_max=dict(required=False, type="int"), + ssl_server_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + ssl_server_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + ssl_server_algorithm=dict(required=False, type="str", choices=["high", "low", "medium", "custom", "client"]), + ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + ssl_mode=dict(required=False, type="str", choices=["half", "full"]), + ssl_min_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_max_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_http_match_host=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_http_location_conversion=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hsts_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hsts_age=dict(required=False, type="int"), + ssl_hsts=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hpkp_report_uri=dict(required=False, type="str"), + ssl_hpkp_primary=dict(required=False, type="str"), + ssl_hpkp_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_hpkp_backup=dict(required=False, type="str"), + ssl_hpkp_age=dict(required=False, type="int"), + ssl_hpkp=dict(required=False, type="str", choices=["disable", "enable", "report-only"]), + ssl_dh_bits=dict(required=False, type="str", choices=["768", "1024", "1536", "2048", "3072", "4096"]), + ssl_client_session_state_type=dict(required=False, type="str", choices=["disable", "time", "count", "both"]), + ssl_client_session_state_timeout=dict(required=False, type="int"), + ssl_client_session_state_max=dict(required=False, type="int"), + ssl_client_renegotiation=dict(required=False, type="str", choices=["deny", "allow", "secure"]), + ssl_client_fallback=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_certificate=dict(required=False, type="str"), + ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low", "custom"]), + srcintf_filter=dict(required=False, type="str"), + src_filter=dict(required=False, type="str"), + service=dict(required=False, type="str"), + server_type=dict(required=False, type="str", + choices=["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", "smtps"]), + protocol=dict(required=False, type="str", choices=["tcp", "udp", "sctp", "icmp"]), + portmapping_type=dict(required=False, type="str", choices=["1-to-1", "m-to-n"]), + portforward=dict(required=False, type="str", choices=["disable", "enable"]), + persistence=dict(required=False, type="str", choices=["none", "http-cookie", "ssl-session-id"]), + outlook_web_access=dict(required=False, type="str", choices=["disable", "enable"]), + nat_source_vip=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + monitor=dict(required=False, type="str"), + max_embryonic_connections=dict(required=False, type="int"), + mappedport=dict(required=False, type="str"), + mappedip=dict(required=False, type="str"), + mapped_addr=dict(required=False, type="str"), + ldb_method=dict(required=False, type="str", + choices=["static", "round-robin", "weighted", "least-session", "least-rtt", "first-alive", + "http-host"]), + https_cookie_secure=dict(required=False, type="str", choices=["disable", "enable"]), + http_multiplex=dict(required=False, type="str", choices=["disable", "enable"]), + http_ip_header_name=dict(required=False, type="str"), + http_ip_header=dict(required=False, type="str", choices=["disable", "enable"]), + http_cookie_share=dict(required=False, type="str", choices=["disable", "same-ip"]), + http_cookie_path=dict(required=False, type="str"), + http_cookie_generation=dict(required=False, type="int"), + http_cookie_domain_from_host=dict(required=False, type="str", choices=["disable", "enable"]), + http_cookie_domain=dict(required=False, type="str"), + http_cookie_age=dict(required=False, type="int"), + gratuitous_arp_interval=dict(required=False, type="int"), + extport=dict(required=False, type="str"), + extip=dict(required=False, type="str"), + extintf=dict(required=False, type="str"), + extaddr=dict(required=False, type="str"), + dns_mapping_ttl=dict(required=False, type="int"), + comment=dict(required=False, type="str"), + color=dict(required=False, type="int"), + arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping=dict(required=False, type="list"), + dynamic_mapping_arp_reply=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_color=dict(required=False, type="int"), + dynamic_mapping_comment=dict(required=False, type="str"), + dynamic_mapping_dns_mapping_ttl=dict(required=False, type="int"), + dynamic_mapping_extaddr=dict(required=False, type="str"), + dynamic_mapping_extintf=dict(required=False, type="str"), + dynamic_mapping_extip=dict(required=False, type="str"), + dynamic_mapping_extport=dict(required=False, type="str"), + dynamic_mapping_gratuitous_arp_interval=dict(required=False, type="int"), + dynamic_mapping_http_cookie_age=dict(required=False, type="int"), + dynamic_mapping_http_cookie_domain=dict(required=False, type="str"), + dynamic_mapping_http_cookie_domain_from_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_http_cookie_generation=dict(required=False, type="int"), + dynamic_mapping_http_cookie_path=dict(required=False, type="str"), + dynamic_mapping_http_cookie_share=dict(required=False, type="str", choices=["disable", "same-ip"]), + dynamic_mapping_http_ip_header=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_http_ip_header_name=dict(required=False, type="str"), + dynamic_mapping_http_multiplex=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_https_cookie_secure=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ldb_method=dict(required=False, type="str", choices=["static", + "round-robin", + "weighted", + "least-session", + "least-rtt", + "first-alive", + "http-host"]), + dynamic_mapping_mapped_addr=dict(required=False, type="str"), + dynamic_mapping_mappedip=dict(required=False, type="str"), + dynamic_mapping_mappedport=dict(required=False, type="str"), + dynamic_mapping_max_embryonic_connections=dict(required=False, type="int"), + dynamic_mapping_monitor=dict(required=False, type="str"), + dynamic_mapping_nat_source_vip=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_outlook_web_access=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_persistence=dict(required=False, type="str", choices=["none", "http-cookie", "ssl-session-id"]), + dynamic_mapping_portforward=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_portmapping_type=dict(required=False, type="str", choices=["1-to-1", "m-to-n"]), + dynamic_mapping_protocol=dict(required=False, type="str", choices=["tcp", "udp", "sctp", "icmp"]), + dynamic_mapping_server_type=dict(required=False, type="str", + choices=["http", "https", "ssl", "tcp", "udp", "ip", "imaps", "pop3s", + "smtps"]), + dynamic_mapping_service=dict(required=False, type="str"), + dynamic_mapping_src_filter=dict(required=False, type="str"), + dynamic_mapping_srcintf_filter=dict(required=False, type="str"), + dynamic_mapping_ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low", "custom"]), + dynamic_mapping_ssl_certificate=dict(required=False, type="str"), + dynamic_mapping_ssl_client_fallback=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_client_renegotiation=dict(required=False, type="str", choices=["deny", "allow", "secure"]), + dynamic_mapping_ssl_client_session_state_max=dict(required=False, type="int"), + dynamic_mapping_ssl_client_session_state_timeout=dict(required=False, type="int"), + dynamic_mapping_ssl_client_session_state_type=dict(required=False, type="str", + choices=["disable", "time", "count", "both"]), + dynamic_mapping_ssl_dh_bits=dict(required=False, type="str", + choices=["768", "1024", "1536", "2048", "3072", "4096"]), + dynamic_mapping_ssl_hpkp=dict(required=False, type="str", choices=["disable", "enable", "report-only"]), + dynamic_mapping_ssl_hpkp_age=dict(required=False, type="int"), + dynamic_mapping_ssl_hpkp_backup=dict(required=False, type="str"), + dynamic_mapping_ssl_hpkp_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_hpkp_primary=dict(required=False, type="str"), + dynamic_mapping_ssl_hpkp_report_uri=dict(required=False, type="str"), + dynamic_mapping_ssl_hsts=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_hsts_age=dict(required=False, type="int"), + dynamic_mapping_ssl_hsts_include_subdomains=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_http_location_conversion=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_http_match_host=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + dynamic_mapping_ssl_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + dynamic_mapping_ssl_mode=dict(required=False, type="str", choices=["half", "full"]), + dynamic_mapping_ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + dynamic_mapping_ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_ssl_server_algorithm=dict(required=False, type="str", + choices=["high", "low", "medium", "custom", "client"]), + dynamic_mapping_ssl_server_max_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + dynamic_mapping_ssl_server_min_version=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2", "client"]), + dynamic_mapping_ssl_server_session_state_max=dict(required=False, type="int"), + dynamic_mapping_ssl_server_session_state_timeout=dict(required=False, type="int"), + dynamic_mapping_ssl_server_session_state_type=dict(required=False, type="str", + choices=["disable", "time", "count", "both"]), + dynamic_mapping_type=dict(required=False, type="str", + choices=["static-nat", "load-balance", "server-load-balance", "dns-translation", + "fqdn"]), + dynamic_mapping_weblogic_server=dict(required=False, type="str", choices=["disable", "enable"]), + dynamic_mapping_websphere_server=dict(required=False, type="str", choices=["disable", "enable"]), + + dynamic_mapping_realservers_client_ip=dict(required=False, type="str"), + dynamic_mapping_realservers_healthcheck=dict(required=False, type="str", choices=["disable", "enable", "vip"]), + dynamic_mapping_realservers_holddown_interval=dict(required=False, type="int"), + dynamic_mapping_realservers_http_host=dict(required=False, type="str"), + dynamic_mapping_realservers_ip=dict(required=False, type="str"), + dynamic_mapping_realservers_max_connections=dict(required=False, type="int"), + dynamic_mapping_realservers_monitor=dict(required=False, type="str"), + dynamic_mapping_realservers_port=dict(required=False, type="int"), + dynamic_mapping_realservers_seq=dict(required=False, type="str"), + dynamic_mapping_realservers_status=dict(required=False, type="str", choices=["active", "standby", "disable"]), + dynamic_mapping_realservers_weight=dict(required=False, type="int"), + + dynamic_mapping_ssl_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + dynamic_mapping_ssl_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + realservers=dict(required=False, type="list"), + realservers_client_ip=dict(required=False, type="str"), + realservers_healthcheck=dict(required=False, type="str", choices=["disable", "enable", "vip"]), + realservers_holddown_interval=dict(required=False, type="int"), + realservers_http_host=dict(required=False, type="str"), + realservers_ip=dict(required=False, type="str"), + realservers_max_connections=dict(required=False, type="int"), + realservers_monitor=dict(required=False, type="str"), + realservers_port=dict(required=False, type="int"), + realservers_seq=dict(required=False, type="str"), + realservers_status=dict(required=False, type="str", choices=["active", "standby", "disable"]), + realservers_weight=dict(required=False, type="int"), + ssl_cipher_suites=dict(required=False, type="list"), + ssl_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + ssl_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + ssl_server_cipher_suites=dict(required=False, type="list"), + ssl_server_cipher_suites_cipher=dict(required=False, + type="str", + choices=["TLS-RSA-WITH-RC4-128-MD5", + "TLS-RSA-WITH-RC4-128-SHA", + "TLS-RSA-WITH-DES-CBC-SHA", + "TLS-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA", + "TLS-RSA-WITH-AES-256-CBC-SHA", + "TLS-RSA-WITH-AES-128-CBC-SHA256", + "TLS-RSA-WITH-AES-256-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-RSA-WITH-SEED-CBC-SHA", + "TLS-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-RSA-WITH-DES-CBC-SHA", + "TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-DHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-RSA-WITH-SEED-CBC-SHA", + "TLS-DHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-RC4-128-SHA", + "TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA", + "TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256", + "TLS-DHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-DHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA", + "TLS-DHE-DSS-WITH-AES-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-128-GCM-SHA256", + "TLS-DHE-DSS-WITH-AES-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA", + "TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256", + "TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "TLS-RSA-WITH-AES-128-GCM-SHA256", + "TLS-RSA-WITH-AES-256-GCM-SHA384", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA", + "TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256", + "TLS-DHE-DSS-WITH-SEED-CBC-SHA", + "TLS-DHE-DSS-WITH-ARIA-128-CBC-SHA256", + "TLS-DHE-DSS-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-RSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-RSA-WITH-ARIA-256-CBC-SHA384", + "TLS-ECDHE-ECDSA-WITH-ARIA-128-CBC-SHA256", + "TLS-ECDHE-ECDSA-WITH-ARIA-256-CBC-SHA384", + "TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA", + "TLS-DHE-DSS-WITH-DES-CBC-SHA"]), + ssl_server_cipher_suites_priority=dict(required=False, type="str"), + ssl_server_cipher_suites_versions=dict(required=False, type="str", + choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "websphere-server": module.params["websphere_server"], + "weblogic-server": module.params["weblogic_server"], + "type": module.params["type"], + "ssl-server-session-state-type": module.params["ssl_server_session_state_type"], + "ssl-server-session-state-timeout": module.params["ssl_server_session_state_timeout"], + "ssl-server-session-state-max": module.params["ssl_server_session_state_max"], + "ssl-server-min-version": module.params["ssl_server_min_version"], + "ssl-server-max-version": module.params["ssl_server_max_version"], + "ssl-server-algorithm": module.params["ssl_server_algorithm"], + "ssl-send-empty-frags": module.params["ssl_send_empty_frags"], + "ssl-pfs": module.params["ssl_pfs"], + "ssl-mode": module.params["ssl_mode"], + "ssl-min-version": module.params["ssl_min_version"], + "ssl-max-version": module.params["ssl_max_version"], + "ssl-http-match-host": module.params["ssl_http_match_host"], + "ssl-http-location-conversion": module.params["ssl_http_location_conversion"], + "ssl-hsts-include-subdomains": module.params["ssl_hsts_include_subdomains"], + "ssl-hsts-age": module.params["ssl_hsts_age"], + "ssl-hsts": module.params["ssl_hsts"], + "ssl-hpkp-report-uri": module.params["ssl_hpkp_report_uri"], + "ssl-hpkp-primary": module.params["ssl_hpkp_primary"], + "ssl-hpkp-include-subdomains": module.params["ssl_hpkp_include_subdomains"], + "ssl-hpkp-backup": module.params["ssl_hpkp_backup"], + "ssl-hpkp-age": module.params["ssl_hpkp_age"], + "ssl-hpkp": module.params["ssl_hpkp"], + "ssl-dh-bits": module.params["ssl_dh_bits"], + "ssl-client-session-state-type": module.params["ssl_client_session_state_type"], + "ssl-client-session-state-timeout": module.params["ssl_client_session_state_timeout"], + "ssl-client-session-state-max": module.params["ssl_client_session_state_max"], + "ssl-client-renegotiation": module.params["ssl_client_renegotiation"], + "ssl-client-fallback": module.params["ssl_client_fallback"], + "ssl-certificate": module.params["ssl_certificate"], + "ssl-algorithm": module.params["ssl_algorithm"], + "srcintf-filter": module.params["srcintf_filter"], + "src-filter": module.params["src_filter"], + "service": module.params["service"], + "server-type": module.params["server_type"], + "protocol": module.params["protocol"], + "portmapping-type": module.params["portmapping_type"], + "portforward": module.params["portforward"], + "persistence": module.params["persistence"], + "outlook-web-access": module.params["outlook_web_access"], + "nat-source-vip": module.params["nat_source_vip"], + "name": module.params["name"], + "monitor": module.params["monitor"], + "max-embryonic-connections": module.params["max_embryonic_connections"], + "mappedport": module.params["mappedport"], + "mappedip": module.params["mappedip"], + "mapped-addr": module.params["mapped_addr"], + "ldb-method": module.params["ldb_method"], + "https-cookie-secure": module.params["https_cookie_secure"], + "http-multiplex": module.params["http_multiplex"], + "http-ip-header-name": module.params["http_ip_header_name"], + "http-ip-header": module.params["http_ip_header"], + "http-cookie-share": module.params["http_cookie_share"], + "http-cookie-path": module.params["http_cookie_path"], + "http-cookie-generation": module.params["http_cookie_generation"], + "http-cookie-domain-from-host": module.params["http_cookie_domain_from_host"], + "http-cookie-domain": module.params["http_cookie_domain"], + "http-cookie-age": module.params["http_cookie_age"], + "gratuitous-arp-interval": module.params["gratuitous_arp_interval"], + "extport": module.params["extport"], + "extip": module.params["extip"], + "extintf": module.params["extintf"], + "extaddr": module.params["extaddr"], + "dns-mapping-ttl": module.params["dns_mapping_ttl"], + "comment": module.params["comment"], + "color": module.params["color"], + "arp-reply": module.params["arp_reply"], + "dynamic_mapping": { + "arp-reply": module.params["dynamic_mapping_arp_reply"], + "color": module.params["dynamic_mapping_color"], + "comment": module.params["dynamic_mapping_comment"], + "dns-mapping-ttl": module.params["dynamic_mapping_dns_mapping_ttl"], + "extaddr": module.params["dynamic_mapping_extaddr"], + "extintf": module.params["dynamic_mapping_extintf"], + "extip": module.params["dynamic_mapping_extip"], + "extport": module.params["dynamic_mapping_extport"], + "gratuitous-arp-interval": module.params["dynamic_mapping_gratuitous_arp_interval"], + "http-cookie-age": module.params["dynamic_mapping_http_cookie_age"], + "http-cookie-domain": module.params["dynamic_mapping_http_cookie_domain"], + "http-cookie-domain-from-host": module.params["dynamic_mapping_http_cookie_domain_from_host"], + "http-cookie-generation": module.params["dynamic_mapping_http_cookie_generation"], + "http-cookie-path": module.params["dynamic_mapping_http_cookie_path"], + "http-cookie-share": module.params["dynamic_mapping_http_cookie_share"], + "http-ip-header": module.params["dynamic_mapping_http_ip_header"], + "http-ip-header-name": module.params["dynamic_mapping_http_ip_header_name"], + "http-multiplex": module.params["dynamic_mapping_http_multiplex"], + "https-cookie-secure": module.params["dynamic_mapping_https_cookie_secure"], + "ldb-method": module.params["dynamic_mapping_ldb_method"], + "mapped-addr": module.params["dynamic_mapping_mapped_addr"], + "mappedip": module.params["dynamic_mapping_mappedip"], + "mappedport": module.params["dynamic_mapping_mappedport"], + "max-embryonic-connections": module.params["dynamic_mapping_max_embryonic_connections"], + "monitor": module.params["dynamic_mapping_monitor"], + "nat-source-vip": module.params["dynamic_mapping_nat_source_vip"], + "outlook-web-access": module.params["dynamic_mapping_outlook_web_access"], + "persistence": module.params["dynamic_mapping_persistence"], + "portforward": module.params["dynamic_mapping_portforward"], + "portmapping-type": module.params["dynamic_mapping_portmapping_type"], + "protocol": module.params["dynamic_mapping_protocol"], + "server-type": module.params["dynamic_mapping_server_type"], + "service": module.params["dynamic_mapping_service"], + "src-filter": module.params["dynamic_mapping_src_filter"], + "srcintf-filter": module.params["dynamic_mapping_srcintf_filter"], + "ssl-algorithm": module.params["dynamic_mapping_ssl_algorithm"], + "ssl-certificate": module.params["dynamic_mapping_ssl_certificate"], + "ssl-client-fallback": module.params["dynamic_mapping_ssl_client_fallback"], + "ssl-client-renegotiation": module.params["dynamic_mapping_ssl_client_renegotiation"], + "ssl-client-session-state-max": module.params["dynamic_mapping_ssl_client_session_state_max"], + "ssl-client-session-state-timeout": module.params["dynamic_mapping_ssl_client_session_state_timeout"], + "ssl-client-session-state-type": module.params["dynamic_mapping_ssl_client_session_state_type"], + "ssl-dh-bits": module.params["dynamic_mapping_ssl_dh_bits"], + "ssl-hpkp": module.params["dynamic_mapping_ssl_hpkp"], + "ssl-hpkp-age": module.params["dynamic_mapping_ssl_hpkp_age"], + "ssl-hpkp-backup": module.params["dynamic_mapping_ssl_hpkp_backup"], + "ssl-hpkp-include-subdomains": module.params["dynamic_mapping_ssl_hpkp_include_subdomains"], + "ssl-hpkp-primary": module.params["dynamic_mapping_ssl_hpkp_primary"], + "ssl-hpkp-report-uri": module.params["dynamic_mapping_ssl_hpkp_report_uri"], + "ssl-hsts": module.params["dynamic_mapping_ssl_hsts"], + "ssl-hsts-age": module.params["dynamic_mapping_ssl_hsts_age"], + "ssl-hsts-include-subdomains": module.params["dynamic_mapping_ssl_hsts_include_subdomains"], + "ssl-http-location-conversion": module.params["dynamic_mapping_ssl_http_location_conversion"], + "ssl-http-match-host": module.params["dynamic_mapping_ssl_http_match_host"], + "ssl-max-version": module.params["dynamic_mapping_ssl_max_version"], + "ssl-min-version": module.params["dynamic_mapping_ssl_min_version"], + "ssl-mode": module.params["dynamic_mapping_ssl_mode"], + "ssl-pfs": module.params["dynamic_mapping_ssl_pfs"], + "ssl-send-empty-frags": module.params["dynamic_mapping_ssl_send_empty_frags"], + "ssl-server-algorithm": module.params["dynamic_mapping_ssl_server_algorithm"], + "ssl-server-max-version": module.params["dynamic_mapping_ssl_server_max_version"], + "ssl-server-min-version": module.params["dynamic_mapping_ssl_server_min_version"], + "ssl-server-session-state-max": module.params["dynamic_mapping_ssl_server_session_state_max"], + "ssl-server-session-state-timeout": module.params["dynamic_mapping_ssl_server_session_state_timeout"], + "ssl-server-session-state-type": module.params["dynamic_mapping_ssl_server_session_state_type"], + "type": module.params["dynamic_mapping_type"], + "weblogic-server": module.params["dynamic_mapping_weblogic_server"], + "websphere-server": module.params["dynamic_mapping_websphere_server"], + "realservers": { + "client-ip": module.params["dynamic_mapping_realservers_client_ip"], + "healthcheck": module.params["dynamic_mapping_realservers_healthcheck"], + "holddown-interval": module.params["dynamic_mapping_realservers_holddown_interval"], + "http-host": module.params["dynamic_mapping_realservers_http_host"], + "ip": module.params["dynamic_mapping_realservers_ip"], + "max-connections": module.params["dynamic_mapping_realservers_max_connections"], + "monitor": module.params["dynamic_mapping_realservers_monitor"], + "port": module.params["dynamic_mapping_realservers_port"], + "seq": module.params["dynamic_mapping_realservers_seq"], + "status": module.params["dynamic_mapping_realservers_status"], + "weight": module.params["dynamic_mapping_realservers_weight"], + }, + "ssl-cipher-suites": { + "cipher": module.params["dynamic_mapping_ssl_cipher_suites_cipher"], + "versions": module.params["dynamic_mapping_ssl_cipher_suites_versions"], + }, + }, + "realservers": { + "client-ip": module.params["realservers_client_ip"], + "healthcheck": module.params["realservers_healthcheck"], + "holddown-interval": module.params["realservers_holddown_interval"], + "http-host": module.params["realservers_http_host"], + "ip": module.params["realservers_ip"], + "max-connections": module.params["realservers_max_connections"], + "monitor": module.params["realservers_monitor"], + "port": module.params["realservers_port"], + "seq": module.params["realservers_seq"], + "status": module.params["realservers_status"], + "weight": module.params["realservers_weight"], + }, + "ssl-cipher-suites": { + "cipher": module.params["ssl_cipher_suites_cipher"], + "versions": module.params["ssl_cipher_suites_versions"], + }, + "ssl-server-cipher-suites": { + "cipher": module.params["ssl_server_cipher_suites_cipher"], + "priority": module.params["ssl_server_cipher_suites_priority"], + "versions": module.params["ssl_server_cipher_suites_versions"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['dynamic_mapping', 'realservers', 'ssl-cipher-suites', 'ssl-server-cipher-suites'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_firewall_vip_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_ipv4.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_ipv4.py new file mode 100644 index 00000000..6cc057ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_ipv4.py @@ -0,0 +1,1355 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwpol_ipv4 +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Allows the add/delete of Firewall Policies on Packages in FortiManager. +description: + - Allows the add/delete of Firewall Policies on Packages in FortiManager. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + package_name: + description: + - The policy package you want to modify + required: false + default: "default" + + fail_on_missing_dependency: + description: + - Normal behavior is to "skip" tasks that fail dependency checks, so other tasks can run. + - If set to "enabled" if a failed dependency check happeens, Ansible will exit as with failure instead of skip. + required: false + default: "disable" + choices: ["enable", "disable"] + + wsso: + description: + - Enable/disable WiFi Single Sign On (WSSO). + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + webfilter_profile: + description: + - Name of an existing Web filter profile. + required: false + + webcache_https: + description: + - Enable/disable web cache for HTTPS. + - choice | disable | Disable web cache for HTTPS. + - choice | enable | Enable web cache for HTTPS. + required: false + choices: ["disable", "enable"] + + webcache: + description: + - Enable/disable web cache. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + wccp: + description: + - Enable/disable forwarding traffic matching this policy to a configured WCCP server. + - choice | disable | Disable WCCP setting. + - choice | enable | Enable WCCP setting. + required: false + choices: ["disable", "enable"] + + wanopt_profile: + description: + - WAN optimization profile. + required: false + + wanopt_peer: + description: + - WAN optimization peer. + required: false + + wanopt_passive_opt: + description: + - WAN optimization passive mode options. This option decides what IP address will be used to connect server. + - choice | default | Allow client side WAN opt peer to decide. + - choice | transparent | Use address of client to connect to server. + - choice | non-transparent | Use local FortiGate address to connect to server. + required: false + choices: ["default", "transparent", "non-transparent"] + + wanopt_detection: + description: + - WAN optimization auto-detection mode. + - choice | active | Active WAN optimization peer auto-detection. + - choice | passive | Passive WAN optimization peer auto-detection. + - choice | off | Turn off WAN optimization peer auto-detection. + required: false + choices: ["active", "passive", "off"] + + wanopt: + description: + - Enable/disable WAN optimization. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + waf_profile: + description: + - Name of an existing Web application firewall profile. + required: false + + vpntunnel: + description: + - Policy-based IPsec VPN | name of the IPsec VPN Phase 1. + required: false + + voip_profile: + description: + - Name of an existing VoIP profile. + required: false + + vlan_filter: + description: + - Set VLAN filters. + required: false + + vlan_cos_rev: + description: + - VLAN reverse direction user priority | 255 passthrough, 0 lowest, 7 highest.. + required: false + + vlan_cos_fwd: + description: + - VLAN forward direction user priority | 255 passthrough, 0 lowest, 7 highest. + required: false + + utm_status: + description: + - Enable to add one or more security profiles (AV, IPS, etc.) to the firewall policy. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + users: + description: + - Names of individual users that can authenticate with this policy. + required: false + + url_category: + description: + - URL category ID list. + required: false + + traffic_shaper_reverse: + description: + - Reverse traffic shaper. + required: false + + traffic_shaper: + description: + - Traffic shaper. + required: false + + timeout_send_rst: + description: + - Enable/disable sending RST packets when TCP sessions expire. + - choice | disable | Disable sending of RST packet upon TCP session expiration. + - choice | enable | Enable sending of RST packet upon TCP session expiration. + required: false + choices: ["disable", "enable"] + + tcp_session_without_syn: + description: + - Enable/disable creation of TCP session without SYN flag. + - choice | all | Enable TCP session without SYN. + - choice | data-only | Enable TCP session data only. + - choice | disable | Disable TCP session without SYN. + required: false + choices: ["all", "data-only", "disable"] + + tcp_mss_sender: + description: + - Sender TCP maximum segment size (MSS). + required: false + + tcp_mss_receiver: + description: + - Receiver TCP maximum segment size (MSS). + required: false + + status: + description: + - Enable or disable this policy. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ssl_ssh_profile: + description: + - Name of an existing SSL SSH profile. + required: false + + ssl_mirror_intf: + description: + - SSL mirror interface name. + required: false + + ssl_mirror: + description: + - Enable to copy decrypted SSL traffic to a FortiGate interface (called SSL mirroring). + - choice | disable | Disable SSL mirror. + - choice | enable | Enable SSL mirror. + required: false + choices: ["disable", "enable"] + + ssh_filter_profile: + description: + - Name of an existing SSH filter profile. + required: false + + srcintf: + description: + - Incoming (ingress) interface. + required: false + + srcaddr_negate: + description: + - When enabled srcaddr specifies what the source address must NOT be. + - choice | disable | Disable source address negate. + - choice | enable | Enable source address negate. + required: false + choices: ["disable", "enable"] + + srcaddr: + description: + - Source address and address group names. + required: false + + spamfilter_profile: + description: + - Name of an existing Spam filter profile. + required: false + + session_ttl: + description: + - TTL in seconds for sessions accepted by this policy (0 means use the system default session TTL). + required: false + + service_negate: + description: + - When enabled service specifies what the service must NOT be. + - choice | disable | Disable negated service match. + - choice | enable | Enable negated service match. + required: false + choices: ["disable", "enable"] + + service: + description: + - Service and service group names. + required: false + + send_deny_packet: + description: + - Enable to send a reply when a session is denied or blocked by a firewall policy. + - choice | disable | Disable deny-packet sending. + - choice | enable | Enable deny-packet sending. + required: false + choices: ["disable", "enable"] + + schedule_timeout: + description: + - Enable to force current sessions to end when the schedule object times out. + - choice | disable | Disable schedule timeout. + - choice | enable | Enable schedule timeout. + required: false + choices: ["disable", "enable"] + + schedule: + description: + - Schedule name. + required: false + + scan_botnet_connections: + description: + - Block or monitor connections to Botnet servers or disable Botnet scanning. + - choice | disable | Do not scan connections to botnet servers. + - choice | block | Block connections to botnet servers. + - choice | monitor | Log connections to botnet servers. + required: false + choices: ["disable", "block", "monitor"] + + rtp_nat: + description: + - Enable Real Time Protocol (RTP) NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + rtp_addr: + description: + - Address names if this is an RTP NAT policy. + required: false + + rsso: + description: + - Enable/disable RADIUS single sign-on (RSSO). + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + replacemsg_override_group: + description: + - Override the default replacement message group for this policy. + required: false + + redirect_url: + description: + - URL users are directed to after seeing and accepting the disclaimer or authenticating. + required: false + + radius_mac_auth_bypass: + description: + - Enable MAC authentication bypass. The bypassed MAC address must be received from RADIUS server. + - choice | disable | Disable MAC authentication bypass. + - choice | enable | Enable MAC authentication bypass. + required: false + choices: ["disable", "enable"] + + profile_type: + description: + - Determine whether the firewall policy allows security profile groups or single profiles only. + - choice | single | Do not allow security profile groups. + - choice | group | Allow security profile groups. + required: false + choices: ["single", "group"] + + profile_protocol_options: + description: + - Name of an existing Protocol options profile. + required: false + + profile_group: + description: + - Name of profile group. + required: false + + poolname: + description: + - IP Pool names. + required: false + + policyid: + description: + - Policy ID. + required: false + + permit_stun_host: + description: + - Accept UDP packets from any Session Traversal Utilities for NAT (STUN) host. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + permit_any_host: + description: + - Accept UDP packets from any host. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + per_ip_shaper: + description: + - Per-IP traffic shaper. + required: false + + outbound: + description: + - Policy-based IPsec VPN | only traffic from the internal network can initiate a VPN. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ntlm_guest: + description: + - Enable/disable NTLM guest user access. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ntlm_enabled_browsers: + description: + - HTTP-User-Agent value of supported browsers. + required: false + + ntlm: + description: + - Enable/disable NTLM authentication. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + np_acceleration: + description: + - Enable/disable UTM Network Processor acceleration. + - choice | disable | Disable UTM Network Processor acceleration. + - choice | enable | Enable UTM Network Processor acceleration. + required: false + choices: ["disable", "enable"] + + natoutbound: + description: + - Policy-based IPsec VPN | apply source NAT to outbound traffic. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + natip: + description: + - Policy-based IPsec VPN | source NAT IP address for outgoing traffic. + required: false + + natinbound: + description: + - Policy-based IPsec VPN | apply destination NAT to inbound traffic. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + nat: + description: + - Enable/disable source NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + name: + description: + - Policy name. + required: false + + mms_profile: + description: + - Name of an existing MMS profile. + required: false + + match_vip: + description: + - Enable to match packets that have had their destination addresses changed by a VIP. + - choice | disable | Do not match DNATed packet. + - choice | enable | Match DNATed packet. + required: false + choices: ["disable", "enable"] + + logtraffic_start: + description: + - Record logs when a session starts and ends. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + logtraffic: + description: + - Enable or disable logging. Log all sessions or security profile sessions. + - choice | disable | Disable all logging for this policy. + - choice | all | Log all sessions accepted or denied by this policy. + - choice | utm | Log traffic that has a security profile applied to it. + required: false + choices: ["disable", "all", "utm"] + + learning_mode: + description: + - Enable to allow everything, but log all of the meaningful data for security information gathering. + - choice | disable | Disable learning mode in firewall policy. + - choice | enable | Enable learning mode in firewall policy. + required: false + choices: ["disable", "enable"] + + label: + description: + - Label for the policy that appears when the GUI is in Section View mode. + required: false + + ips_sensor: + description: + - Name of an existing IPS sensor. + required: false + + ippool: + description: + - Enable to use IP Pools for source NAT. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + internet_service_src_negate: + description: + - When enabled internet-service-src specifies what the service must NOT be. + - choice | disable | Disable negated Internet Service source match. + - choice | enable | Enable negated Internet Service source match. + required: false + choices: ["disable", "enable"] + + internet_service_src_id: + description: + - Internet Service source ID. + required: false + + internet_service_src_custom: + description: + - Custom Internet Service source name. + required: false + + internet_service_src: + description: + - Enable/disable use of Internet Services in source for this policy. If enabled, source address is not used. + - choice | disable | Disable use of Internet Services source in policy. + - choice | enable | Enable use of Internet Services source in policy. + required: false + choices: ["disable", "enable"] + + internet_service_negate: + description: + - When enabled internet-service specifies what the service must NOT be. + - choice | disable | Disable negated Internet Service match. + - choice | enable | Enable negated Internet Service match. + required: false + choices: ["disable", "enable"] + + internet_service_id: + description: + - Internet Service ID. + required: false + + internet_service_custom: + description: + - Custom Internet Service name. + required: false + + internet_service: + description: + - Enable/disable use of Internet Services for this policy. If enabled, dstaddr and service are not used. + - choice | disable | Disable use of Internet Services in policy. + - choice | enable | Enable use of Internet Services in policy. + required: false + choices: ["disable", "enable"] + + inbound: + description: + - Policy-based IPsec VPN | only traffic from the remote network can initiate a VPN. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + identity_based_route: + description: + - Name of identity-based routing rule. + required: false + + icap_profile: + description: + - Name of an existing ICAP profile. + required: false + + gtp_profile: + description: + - GTP profile. + required: false + + groups: + description: + - Names of user groups that can authenticate with this policy. + required: false + + global_label: + description: + - Label for the policy that appears when the GUI is in Global View mode. + required: false + + fsso_agent_for_ntlm: + description: + - FSSO agent to use for NTLM authentication. + required: false + + fsso: + description: + - Enable/disable Fortinet Single Sign-On. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + fixedport: + description: + - Enable to prevent source NAT from changing a session's source port. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + firewall_session_dirty: + description: + - How to handle sessions if the configuration of this firewall policy changes. + - choice | check-all | Flush all current sessions accepted by this policy. + - choice | check-new | Continue to allow sessions already accepted by this policy. + required: false + choices: ["check-all", "check-new"] + + dstintf: + description: + - Outgoing (egress) interface. + required: false + + dstaddr_negate: + description: + - When enabled dstaddr specifies what the destination address must NOT be. + - choice | disable | Disable destination address negate. + - choice | enable | Enable destination address negate. + required: false + choices: ["disable", "enable"] + + dstaddr: + description: + - Destination address and address group names. + required: false + + dsri: + description: + - Enable DSRI to ignore HTTP server responses. + - choice | disable | Disable DSRI. + - choice | enable | Enable DSRI. + required: false + choices: ["disable", "enable"] + + dscp_value: + description: + - DSCP value. + required: false + + dscp_negate: + description: + - Enable negated DSCP match. + - choice | disable | Disable DSCP negate. + - choice | enable | Enable DSCP negate. + required: false + choices: ["disable", "enable"] + + dscp_match: + description: + - Enable DSCP check. + - choice | disable | Disable DSCP check. + - choice | enable | Enable DSCP check. + required: false + choices: ["disable", "enable"] + + dnsfilter_profile: + description: + - Name of an existing DNS filter profile. + required: false + + dlp_sensor: + description: + - Name of an existing DLP sensor. + required: false + + disclaimer: + description: + - Enable/disable user authentication disclaimer. + - choice | disable | Disable user authentication disclaimer. + - choice | enable | Enable user authentication disclaimer. + required: false + choices: ["disable", "enable"] + + diffservcode_rev: + description: + - Change packet's reverse (reply) DiffServ to this value. + required: false + + diffservcode_forward: + description: + - Change packet's DiffServ to this value. + required: false + + diffserv_reverse: + description: + - Enable to change packet's reverse (reply) DiffServ values to the specified diffservcode-rev value. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + diffserv_forward: + description: + - Enable to change packet's DiffServ values to the specified diffservcode-forward value. + - choice | disable | Disable WAN optimization. + - choice | enable | Enable WAN optimization. + required: false + choices: ["disable", "enable"] + + devices: + description: + - Names of devices or device groups that can be matched by the policy. + required: false + + delay_tcp_npu_session: + description: + - Enable TCP NPU session delay to guarantee packet order of 3-way handshake. + - choice | disable | Disable TCP NPU session delay in order to guarantee packet order of 3-way handshake. + - choice | enable | Enable TCP NPU session delay in order to guarantee packet order of 3-way handshake. + required: false + choices: ["disable", "enable"] + + custom_log_fields: + description: + - Custom fields to append to log messages for this policy. + required: false + + comments: + description: + - Comment. + required: false + + capture_packet: + description: + - Enable/disable capture packets. + - choice | disable | Disable capture packets. + - choice | enable | Enable capture packets. + required: false + choices: ["disable", "enable"] + + captive_portal_exempt: + description: + - Enable to exempt some users from the captive portal. + - choice | disable | Disable exemption of captive portal. + - choice | enable | Enable exemption of captive portal. + required: false + choices: ["disable", "enable"] + + block_notification: + description: + - Enable/disable block notification. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + av_profile: + description: + - Name of an existing Antivirus profile. + required: false + + auto_asic_offload: + description: + - Enable/disable offloading security profile processing to CP processors. + - choice | disable | Disable ASIC offloading. + - choice | enable | Enable auto ASIC offloading. + required: false + choices: ["disable", "enable"] + + auth_redirect_addr: + description: + - HTTP-to-HTTPS redirect address for firewall authentication. + required: false + + auth_path: + description: + - Enable/disable authentication-based routing. + - choice | disable | Disable authentication-based routing. + - choice | enable | Enable authentication-based routing. + required: false + choices: ["disable", "enable"] + + auth_cert: + description: + - HTTPS server certificate for policy authentication. + required: false + + application_list: + description: + - Name of an existing Application list. + required: false + + application: + description: + - Application ID list. + required: false + + app_group: + description: + - Application group names. + required: false + + app_category: + description: + - Application category ID list. + required: false + + action: + description: + - Policy action (allow/deny/ipsec). + - choice | deny | Blocks sessions that match the firewall policy. + - choice | accept | Allows session that match the firewall policy. + - choice | ipsec | Firewall policy becomes a policy-based IPsec VPN policy. + required: false + choices: ["deny", "accept", "ipsec"] + + vpn_dst_node: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + required: false + + vpn_dst_node_host: + description: + - VPN Destination Node Host. + required: false + + vpn_dst_node_seq: + description: + - VPN Destination Node Seq. + required: false + + vpn_dst_node_subnet: + description: + - VPN Destination Node Seq. + required: false + + vpn_src_node: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + required: false + + vpn_src_node_host: + description: + - VPN Source Node Host. + required: false + + vpn_src_node_seq: + description: + - VPN Source Node Seq. + required: false + + vpn_src_node_subnet: + description: + - VPN Source Node. + required: false + + +''' + +EXAMPLES = ''' +- name: ADD VERY BASIC IPV4 POLICY WITH NO NAT (WIDE OPEN) + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy" + comments: "Created by Ansible" + action: "accept" + dstaddr: "all" + srcaddr: "all" + dstintf: "any" + srcintf: "any" + logtraffic: "utm" + service: "ALL" + schedule: "always" + +- name: ADD VERY BASIC IPV4 POLICY WITH NAT AND MULTIPLE ENTRIES + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy_2" + comments: "Created by Ansible" + action: "accept" + dstaddr: "google-play" + srcaddr: "all" + dstintf: "any" + srcintf: "any" + logtraffic: "utm" + service: "HTTP, HTTPS" + schedule: "always" + nat: "enable" + users: "karen, kevin" + +- name: ADD VERY BASIC IPV4 POLICY WITH NAT AND MULTIPLE ENTRIES AND SEC PROFILES + community.network.fmgr_fwpol_ipv4: + mode: "set" + adom: "ansible" + package_name: "default" + name: "Basic_IPv4_Policy_3" + comments: "Created by Ansible" + action: "accept" + dstaddr: "google-play, autoupdate.opera.com" + srcaddr: "corp_internal" + dstintf: "zone_wan1, zone_wan2" + srcintf: "zone_int1" + logtraffic: "utm" + service: "HTTP, HTTPS" + schedule: "always" + nat: "enable" + users: "karen, kevin" + av_profile: "sniffer-profile" + ips_sensor: "default" + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_firewall_policy_modify(fmgr, paramgram): + """ + fmgr_firewall_policy -- Add/Set/Deletes Firewall Policy Objects defined in the "paramgram" + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall/policy'.format(adom=adom, pkg=paramgram["package_name"]) + datagram = scrub_dict((prepare_dict(paramgram))) + del datagram["package_name"] + datagram = fmgr._tools.split_comma_strings_into_lists(datagram) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall' \ + '/policy/{policyid}'.format(adom=paramgram["adom"], + pkg=paramgram["package_name"], + policyid=paramgram["policyid"]) + datagram = { + "policyid": paramgram["policyid"] + } + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + package_name=dict(type="str", required=False, default="default"), + fail_on_missing_dependency=dict(type="str", required=False, default="disable", choices=["enable", + "disable"]), + wsso=dict(required=False, type="str", choices=["disable", "enable"]), + webfilter_profile=dict(required=False, type="str"), + webcache_https=dict(required=False, type="str", choices=["disable", "enable"]), + webcache=dict(required=False, type="str", choices=["disable", "enable"]), + wccp=dict(required=False, type="str", choices=["disable", "enable"]), + wanopt_profile=dict(required=False, type="str"), + wanopt_peer=dict(required=False, type="str"), + wanopt_passive_opt=dict(required=False, type="str", choices=["default", "transparent", "non-transparent"]), + wanopt_detection=dict(required=False, type="str", choices=["active", "passive", "off"]), + wanopt=dict(required=False, type="str", choices=["disable", "enable"]), + waf_profile=dict(required=False, type="str"), + vpntunnel=dict(required=False, type="str"), + voip_profile=dict(required=False, type="str"), + vlan_filter=dict(required=False, type="str"), + vlan_cos_rev=dict(required=False, type="int"), + vlan_cos_fwd=dict(required=False, type="int"), + utm_status=dict(required=False, type="str", choices=["disable", "enable"]), + users=dict(required=False, type="str"), + url_category=dict(required=False, type="str"), + traffic_shaper_reverse=dict(required=False, type="str"), + traffic_shaper=dict(required=False, type="str"), + timeout_send_rst=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_session_without_syn=dict(required=False, type="str", choices=["all", "data-only", "disable"]), + tcp_mss_sender=dict(required=False, type="int"), + tcp_mss_receiver=dict(required=False, type="int"), + status=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_ssh_profile=dict(required=False, type="str"), + ssl_mirror_intf=dict(required=False, type="str"), + ssl_mirror=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_filter_profile=dict(required=False, type="str"), + srcintf=dict(required=False, type="str"), + srcaddr_negate=dict(required=False, type="str", choices=["disable", "enable"]), + srcaddr=dict(required=False, type="str"), + spamfilter_profile=dict(required=False, type="str"), + session_ttl=dict(required=False, type="int"), + service_negate=dict(required=False, type="str", choices=["disable", "enable"]), + service=dict(required=False, type="str"), + send_deny_packet=dict(required=False, type="str", choices=["disable", "enable"]), + schedule_timeout=dict(required=False, type="str", choices=["disable", "enable"]), + schedule=dict(required=False, type="str"), + scan_botnet_connections=dict(required=False, type="str", choices=["disable", "block", "monitor"]), + rtp_nat=dict(required=False, type="str", choices=["disable", "enable"]), + rtp_addr=dict(required=False, type="str"), + rsso=dict(required=False, type="str", choices=["disable", "enable"]), + replacemsg_override_group=dict(required=False, type="str"), + redirect_url=dict(required=False, type="str"), + radius_mac_auth_bypass=dict(required=False, type="str", choices=["disable", "enable"]), + profile_type=dict(required=False, type="str", choices=["single", "group"]), + profile_protocol_options=dict(required=False, type="str"), + profile_group=dict(required=False, type="str"), + poolname=dict(required=False, type="str"), + policyid=dict(required=False, type="str"), + permit_stun_host=dict(required=False, type="str", choices=["disable", "enable"]), + permit_any_host=dict(required=False, type="str", choices=["disable", "enable"]), + per_ip_shaper=dict(required=False, type="str"), + outbound=dict(required=False, type="str", choices=["disable", "enable"]), + ntlm_guest=dict(required=False, type="str", choices=["disable", "enable"]), + ntlm_enabled_browsers=dict(required=False, type="str"), + ntlm=dict(required=False, type="str", choices=["disable", "enable"]), + np_acceleration=dict(required=False, type="str", choices=["disable", "enable"]), + natoutbound=dict(required=False, type="str", choices=["disable", "enable"]), + natip=dict(required=False, type="str"), + natinbound=dict(required=False, type="str", choices=["disable", "enable"]), + nat=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + mms_profile=dict(required=False, type="str"), + match_vip=dict(required=False, type="str", choices=["disable", "enable"]), + logtraffic_start=dict(required=False, type="str", choices=["disable", "enable"]), + logtraffic=dict(required=False, type="str", choices=["disable", "all", "utm"]), + learning_mode=dict(required=False, type="str", choices=["disable", "enable"]), + label=dict(required=False, type="str"), + ips_sensor=dict(required=False, type="str"), + ippool=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_src_negate=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_src_id=dict(required=False, type="str"), + internet_service_src_custom=dict(required=False, type="str"), + internet_service_src=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_negate=dict(required=False, type="str", choices=["disable", "enable"]), + internet_service_id=dict(required=False, type="str"), + internet_service_custom=dict(required=False, type="str"), + internet_service=dict(required=False, type="str", choices=["disable", "enable"]), + inbound=dict(required=False, type="str", choices=["disable", "enable"]), + identity_based_route=dict(required=False, type="str"), + icap_profile=dict(required=False, type="str"), + gtp_profile=dict(required=False, type="str"), + groups=dict(required=False, type="str"), + global_label=dict(required=False, type="str"), + fsso_agent_for_ntlm=dict(required=False, type="str"), + fsso=dict(required=False, type="str", choices=["disable", "enable"]), + fixedport=dict(required=False, type="str", choices=["disable", "enable"]), + firewall_session_dirty=dict(required=False, type="str", choices=["check-all", "check-new"]), + dstintf=dict(required=False, type="str"), + dstaddr_negate=dict(required=False, type="str", choices=["disable", "enable"]), + dstaddr=dict(required=False, type="str"), + dsri=dict(required=False, type="str", choices=["disable", "enable"]), + dscp_value=dict(required=False, type="str"), + dscp_negate=dict(required=False, type="str", choices=["disable", "enable"]), + dscp_match=dict(required=False, type="str", choices=["disable", "enable"]), + dnsfilter_profile=dict(required=False, type="str"), + dlp_sensor=dict(required=False, type="str"), + disclaimer=dict(required=False, type="str", choices=["disable", "enable"]), + diffservcode_rev=dict(required=False, type="str"), + diffservcode_forward=dict(required=False, type="str"), + diffserv_reverse=dict(required=False, type="str", choices=["disable", "enable"]), + diffserv_forward=dict(required=False, type="str", choices=["disable", "enable"]), + devices=dict(required=False, type="str"), + delay_tcp_npu_session=dict(required=False, type="str", choices=["disable", "enable"]), + custom_log_fields=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + capture_packet=dict(required=False, type="str", choices=["disable", "enable"]), + captive_portal_exempt=dict(required=False, type="str", choices=["disable", "enable"]), + block_notification=dict(required=False, type="str", choices=["disable", "enable"]), + av_profile=dict(required=False, type="str"), + auto_asic_offload=dict(required=False, type="str", choices=["disable", "enable"]), + auth_redirect_addr=dict(required=False, type="str"), + auth_path=dict(required=False, type="str", choices=["disable", "enable"]), + auth_cert=dict(required=False, type="str"), + application_list=dict(required=False, type="str"), + application=dict(required=False, type="str"), + app_group=dict(required=False, type="str"), + app_category=dict(required=False, type="str"), + action=dict(required=False, type="str", choices=["deny", "accept", "ipsec"]), + vpn_dst_node=dict(required=False, type="list"), + vpn_dst_node_host=dict(required=False, type="str"), + vpn_dst_node_seq=dict(required=False, type="str"), + vpn_dst_node_subnet=dict(required=False, type="str"), + vpn_src_node=dict(required=False, type="list"), + vpn_src_node_host=dict(required=False, type="str"), + vpn_src_node_seq=dict(required=False, type="str"), + vpn_src_node_subnet=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "package_name": module.params["package_name"], + "wsso": module.params["wsso"], + "webfilter-profile": module.params["webfilter_profile"], + "webcache-https": module.params["webcache_https"], + "webcache": module.params["webcache"], + "wccp": module.params["wccp"], + "wanopt-profile": module.params["wanopt_profile"], + "wanopt-peer": module.params["wanopt_peer"], + "wanopt-passive-opt": module.params["wanopt_passive_opt"], + "wanopt-detection": module.params["wanopt_detection"], + "wanopt": module.params["wanopt"], + "waf-profile": module.params["waf_profile"], + "vpntunnel": module.params["vpntunnel"], + "voip-profile": module.params["voip_profile"], + "vlan-filter": module.params["vlan_filter"], + "vlan-cos-rev": module.params["vlan_cos_rev"], + "vlan-cos-fwd": module.params["vlan_cos_fwd"], + "utm-status": module.params["utm_status"], + "users": module.params["users"], + "url-category": module.params["url_category"], + "traffic-shaper-reverse": module.params["traffic_shaper_reverse"], + "traffic-shaper": module.params["traffic_shaper"], + "timeout-send-rst": module.params["timeout_send_rst"], + "tcp-session-without-syn": module.params["tcp_session_without_syn"], + "tcp-mss-sender": module.params["tcp_mss_sender"], + "tcp-mss-receiver": module.params["tcp_mss_receiver"], + "status": module.params["status"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "ssl-mirror-intf": module.params["ssl_mirror_intf"], + "ssl-mirror": module.params["ssl_mirror"], + "ssh-filter-profile": module.params["ssh_filter_profile"], + "srcintf": module.params["srcintf"], + "srcaddr-negate": module.params["srcaddr_negate"], + "srcaddr": module.params["srcaddr"], + "spamfilter-profile": module.params["spamfilter_profile"], + "session-ttl": module.params["session_ttl"], + "service-negate": module.params["service_negate"], + "service": module.params["service"], + "send-deny-packet": module.params["send_deny_packet"], + "schedule-timeout": module.params["schedule_timeout"], + "schedule": module.params["schedule"], + "scan-botnet-connections": module.params["scan_botnet_connections"], + "rtp-nat": module.params["rtp_nat"], + "rtp-addr": module.params["rtp_addr"], + "rsso": module.params["rsso"], + "replacemsg-override-group": module.params["replacemsg_override_group"], + "redirect-url": module.params["redirect_url"], + "radius-mac-auth-bypass": module.params["radius_mac_auth_bypass"], + "profile-type": module.params["profile_type"], + "profile-protocol-options": module.params["profile_protocol_options"], + "profile-group": module.params["profile_group"], + "poolname": module.params["poolname"], + "policyid": module.params["policyid"], + "permit-stun-host": module.params["permit_stun_host"], + "permit-any-host": module.params["permit_any_host"], + "per-ip-shaper": module.params["per_ip_shaper"], + "outbound": module.params["outbound"], + "ntlm-guest": module.params["ntlm_guest"], + "ntlm-enabled-browsers": module.params["ntlm_enabled_browsers"], + "ntlm": module.params["ntlm"], + "np-acceleration": module.params["np_acceleration"], + "natoutbound": module.params["natoutbound"], + "natip": module.params["natip"], + "natinbound": module.params["natinbound"], + "nat": module.params["nat"], + "name": module.params["name"], + "mms-profile": module.params["mms_profile"], + "match-vip": module.params["match_vip"], + "logtraffic-start": module.params["logtraffic_start"], + "logtraffic": module.params["logtraffic"], + "learning-mode": module.params["learning_mode"], + "label": module.params["label"], + "ips-sensor": module.params["ips_sensor"], + "ippool": module.params["ippool"], + "internet-service-src-negate": module.params["internet_service_src_negate"], + "internet-service-src-id": module.params["internet_service_src_id"], + "internet-service-src-custom": module.params["internet_service_src_custom"], + "internet-service-src": module.params["internet_service_src"], + "internet-service-negate": module.params["internet_service_negate"], + "internet-service-id": module.params["internet_service_id"], + "internet-service-custom": module.params["internet_service_custom"], + "internet-service": module.params["internet_service"], + "inbound": module.params["inbound"], + "identity-based-route": module.params["identity_based_route"], + "icap-profile": module.params["icap_profile"], + "gtp-profile": module.params["gtp_profile"], + "groups": module.params["groups"], + "global-label": module.params["global_label"], + "fsso-agent-for-ntlm": module.params["fsso_agent_for_ntlm"], + "fsso": module.params["fsso"], + "fixedport": module.params["fixedport"], + "firewall-session-dirty": module.params["firewall_session_dirty"], + "dstintf": module.params["dstintf"], + "dstaddr-negate": module.params["dstaddr_negate"], + "dstaddr": module.params["dstaddr"], + "dsri": module.params["dsri"], + "dscp-value": module.params["dscp_value"], + "dscp-negate": module.params["dscp_negate"], + "dscp-match": module.params["dscp_match"], + "dnsfilter-profile": module.params["dnsfilter_profile"], + "dlp-sensor": module.params["dlp_sensor"], + "disclaimer": module.params["disclaimer"], + "diffservcode-rev": module.params["diffservcode_rev"], + "diffservcode-forward": module.params["diffservcode_forward"], + "diffserv-reverse": module.params["diffserv_reverse"], + "diffserv-forward": module.params["diffserv_forward"], + "devices": module.params["devices"], + "delay-tcp-npu-session": module.params["delay_tcp_npu_session"], + "custom-log-fields": module.params["custom_log_fields"], + "comments": module.params["comments"], + "capture-packet": module.params["capture_packet"], + "captive-portal-exempt": module.params["captive_portal_exempt"], + "block-notification": module.params["block_notification"], + "av-profile": module.params["av_profile"], + "auto-asic-offload": module.params["auto_asic_offload"], + "auth-redirect-addr": module.params["auth_redirect_addr"], + "auth-path": module.params["auth_path"], + "auth-cert": module.params["auth_cert"], + "application-list": module.params["application_list"], + "application": module.params["application"], + "app-group": module.params["app_group"], + "app-category": module.params["app_category"], + "action": module.params["action"], + "vpn_dst_node": { + "host": module.params["vpn_dst_node_host"], + "seq": module.params["vpn_dst_node_seq"], + "subnet": module.params["vpn_dst_node_subnet"], + }, + "vpn_src_node": { + "host": module.params["vpn_src_node_host"], + "seq": module.params["vpn_src_node_seq"], + "subnet": module.params["vpn_src_node_subnet"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['vpn_dst_node', 'vpn_src_node'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + try: + if paramgram["mode"] == "delete": + # WE NEED TO GET THE POLICY ID FROM THE NAME OF THE POLICY TO DELETE IT + url = '/pm/config/adom/{adom}/pkg/{pkg}/firewall' \ + '/policy/'.format(adom=paramgram["adom"], + pkg=paramgram["package_name"]) + datagram = { + "filter": ["name", "==", paramgram["name"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + try: + if response[1][0]["policyid"]: + policy_id = response[1][0]["policyid"] + paramgram["policyid"] = policy_id + except BaseException: + fmgr.return_response(module=module, results=response, good_codes=[0, ], stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram), + msg="Couldn't find policy ID number for policy name specified.") + except Exception as err: + raise FMGBaseException(err) + + try: + results = fmgr_firewall_policy_modify(fmgr, paramgram) + if module.params["fail_on_missing_dependency"] == "disable": + fmgr.govern_response(module=module, results=results, good_codes=[0, -9998], + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + if module.params["fail_on_missing_dependency"] == "enable" and results[0] == -10131: + fmgr.govern_response(module=module, results=results, good_codes=[0, ], failed=True, skipped=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_package.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_package.py new file mode 100644 index 00000000..37e9bc9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_fwpol_package.py @@ -0,0 +1,479 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_fwpol_package +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages FortiManager Firewall Policies Packages. +description: + - Manages FortiManager Firewall Policies Packages. Policy Packages contain one or more Firewall Policies/Rules and + are distritbuted via FortiManager to Fortigates. + - This module controls the creation/edit/delete/assign of these packages. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + choices: ['add', 'set', 'delete'] + default: add + + name: + description: + - Name of the FortiManager package or folder. + required: True + + object_type: + description: + - Are we managing packages or folders, or installing packages? + required: True + choices: ['pkg','folder','install'] + + package_folder: + description: + - Name of the folder you want to put the package into. + required: false + + central_nat: + description: + - Central NAT setting. + required: false + choices: ['enable', 'disable'] + default: disable + + fwpolicy_implicit_log: + description: + - Implicit Log setting for all IPv4 policies in package. + required: false + choices: ['enable', 'disable'] + default: disable + + fwpolicy6_implicit_log: + description: + - Implicit Log setting for all IPv6 policies in package. + required: false + choices: ['enable', 'disable'] + default: disable + + inspection_mode: + description: + - Inspection mode setting for the policies flow or proxy. + required: false + choices: ['flow', 'proxy'] + default: flow + + ngfw_mode: + description: + - NGFW mode setting for the policies flow or proxy. + required: false + choices: ['profile-based', 'policy-based'] + default: profile-based + + ssl_ssh_profile: + description: + - if policy-based ngfw-mode, refer to firewall ssl-ssh-profile. + required: false + + scope_members: + description: + - The devices or scope that you want to assign this policy package to. + required: false + + scope_members_vdom: + description: + - The members VDOM you want to assign the package to. + required: false + default: root + + parent_folder: + description: + - The parent folder name you want to add this object under. + required: false + +''' + + +EXAMPLES = ''' +- name: CREATE BASIC POLICY PACKAGE + community.network.fmgr_fwpol_package: + adom: "ansible" + mode: "add" + name: "testPackage" + object_type: "pkg" + +- name: ADD PACKAGE WITH TARGETS + community.network.fmgr_fwpol_package: + mode: "add" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "pkg" + inspection_mode: "flow" + ngfw_mode: "profile-based" + scope_members: "seattle-fgt02, seattle-fgt03" + +- name: ADD FOLDER + community.network.fmgr_fwpol_package: + mode: "add" + adom: "ansible" + name: "ansibleTestFolder1" + object_type: "folder" + +- name: ADD PACKAGE INTO PARENT FOLDER + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestPackage2" + object_type: "pkg" + parent_folder: "ansibleTestFolder1" + +- name: ADD FOLDER INTO PARENT FOLDER + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestFolder2" + object_type: "folder" + parent_folder: "ansibleTestFolder1" + +- name: INSTALL PACKAGE + community.network.fmgr_fwpol_package: + mode: "set" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "install" + scope_members: "seattle-fgt03, seattle-fgt02" + +- name: REMOVE PACKAGE + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestPackage1" + object_type: "pkg" + +- name: REMOVE NESTED PACKAGE + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestPackage2" + object_type: "pkg" + parent_folder: "ansibleTestFolder1" + +- name: REMOVE NESTED FOLDER + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestFolder2" + object_type: "folder" + parent_folder: "ansibleTestFolder1" + +- name: REMOVE FOLDER + community.network.fmgr_fwpol_package: + mode: "delete" + adom: "ansible" + name: "ansibleTestFolder1" + object_type: "folder" +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods + + +def fmgr_fwpol_package(fmgr, paramgram): + """ + This function will create FMGR Firewall Policy Packages, or delete them. It is also capable of assigning packages. + This function DOES NOT install the package. See the function fmgr_fwpol_package_install() + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"]) + members_list = [] + + # CHECK FOR SCOPE MEMBERS AND CREATE THAT DICT + if paramgram["scope_members"] is not None: + members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"]) + for member in members: + scope_dict = { + "name": member, + "vdom": paramgram["scope_members_vdom"], + } + members_list.append(scope_dict) + + # IF PARENT FOLDER IS NOT DEFINED + if paramgram["parent_folder"] is None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["name"], + "scope member": members_list, + "package settings": { + "central-nat": paramgram["central-nat"], + "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"], + "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"], + "inspection-mode": paramgram["inspection-mode"], + "ngfw-mode": paramgram["ngfw-mode"], + } + } + + if paramgram["ngfw-mode"] == "policy-based" and paramgram["ssl-ssh-profile"] is not None: + datagram["package settings"]["ssl-ssh-profile"] = paramgram["ssl-ssh-profile"] + + # IF PARENT FOLDER IS DEFINED + if paramgram["parent_folder"] is not None: + datagram = { + "type": "folder", + "name": paramgram["parent_folder"], + "subobj": [{ + "name": paramgram["name"], + "scope member": members_list, + "type": "pkg", + "package settings": { + "central-nat": paramgram["central-nat"], + "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"], + "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"], + "inspection-mode": paramgram["inspection-mode"], + "ngfw-mode": paramgram["ngfw-mode"], + } + }] + } + + # NORMAL DELETE NO PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + # DELETE WITH PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"], + parent_folder=paramgram["parent_folder"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwpol_package_folder(fmgr, paramgram): + """ + This function will create folders for firewall packages. It can create down to two levels deep. + We haven't yet tested for any more layers below two levels. + parent_folders for multiple levels may need to defined as "level1/level2/level3" for the URL parameters and such. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + if paramgram["mode"] in ['set', 'add']: + url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"]) + # IF PARENT FOLDER IS NOT DEFINED + if paramgram["parent_folder"] is None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["name"], + } + + # IF PARENT FOLDER IS DEFINED + if paramgram["parent_folder"] is not None: + datagram = { + "type": paramgram["object_type"], + "name": paramgram["parent_folder"], + "subobj": [{ + "name": paramgram["name"], + "type": paramgram["object_type"], + + }] + } + # NORMAL DELETE NO PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"]) + + # DELETE WITH PARENT + if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None: + datagram = { + "name": paramgram["name"] + } + # SET DELETE URL + url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"], + parent_folder=paramgram["parent_folder"]) + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +def fmgr_fwpol_package_install(fmgr, paramgram): + """ + This method/function installs FMGR FW Policy Packages to the scope members defined in the playbook. + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + # INIT BLANK MEMBERS LIST + members_list = [] + # USE THE PARSE CSV FUNCTION TO GET A LIST FORMAT OF THE MEMBERS + members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"]) + # USE THAT LIST TO BUILD THE DICTIONARIES NEEDED, AND ADD TO THE BLANK MEMBERS LIST + for member in members: + scope_dict = { + "name": member, + "vdom": paramgram["scope_members_vdom"], + } + members_list.append(scope_dict) + # THEN FOR THE DATAGRAM, USING THE MEMBERS LIST CREATED ABOVE + datagram = { + "adom": paramgram["adom"], + "pkg": paramgram["name"], + "scope": members_list + } + # EXECUTE THE INSTALL REQUEST + url = '/securityconsole/install/package' + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete"], type="str", default="add"), + + name=dict(required=False, type="str"), + object_type=dict(required=True, type="str", choices=['pkg', 'folder', 'install']), + package_folder=dict(required=False, type="str"), + central_nat=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + fwpolicy_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + fwpolicy6_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']), + inspection_mode=dict(required=False, type="str", default="flow", choices=['flow', 'proxy']), + ngfw_mode=dict(required=False, type="str", default="profile-based", choices=['profile-based', 'policy-based']), + ssl_ssh_profile=dict(required=False, type="str"), + scope_members=dict(required=False, type="str"), + scope_members_vdom=dict(required=False, type="str", default="root"), + parent_folder=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE DATAGRAM + paramgram = { + "adom": module.params["adom"], + "name": module.params["name"], + "mode": module.params["mode"], + "object_type": module.params["object_type"], + "package-folder": module.params["package_folder"], + "central-nat": module.params["central_nat"], + "fwpolicy-implicit-log": module.params["fwpolicy_implicit_log"], + "fwpolicy6-implicit-log": module.params["fwpolicy6_implicit_log"], + "inspection-mode": module.params["inspection_mode"], + "ngfw-mode": module.params["ngfw_mode"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "scope_members": module.params["scope_members"], + "scope_members_vdom": module.params["scope_members_vdom"], + "parent_folder": module.params["parent_folder"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["object_type"] == "pkg": + results = fmgr_fwpol_package(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE object_type IS FOLDER LETS RUN THAT METHOD + if paramgram["object_type"] == "folder": + results = fmgr_fwpol_package_folder(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF THE object_type IS INSTALL AND NEEDED PARAMETERS ARE DEFINED INSTALL THE PACKAGE + if paramgram["scope_members"] is not None and paramgram["name"] is not None and\ + paramgram["object_type"] == "install": + results = fmgr_fwpol_package_install(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_ha.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_ha.py new file mode 100644 index 00000000..e0f7a7a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_ha.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_ha +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manages the High-Availability State of FortiManager Clusters and Nodes. +description: Change HA state or settings of FortiManager nodes (Standalone/Master/Slave). + +options: + fmgr_ha_mode: + description: + - Sets the role of the FortiManager host for HA. + required: false + choices: ["standalone", "master", "slave"] + + fmgr_ha_peer_ipv4: + description: + - Sets the IPv4 address of a HA peer. + required: false + + fmgr_ha_peer_ipv6: + description: + - Sets the IPv6 address of a HA peer. + required: false + + fmgr_ha_peer_sn: + description: + - Sets the HA Peer Serial Number. + required: false + + fmgr_ha_peer_status: + description: + - Sets the peer status to enable or disable. + required: false + choices: ["enable", "disable"] + + fmgr_ha_cluster_pw: + description: + - Sets the password for the HA cluster. Only required once. System remembers between HA mode switches. + required: false + + fmgr_ha_cluster_id: + description: + - Sets the ID number of the HA cluster. Defaults to 1. + required: false + default: 1 + + fmgr_ha_hb_threshold: + description: + - Sets heartbeat lost threshold (1-255). + required: false + default: 3 + + fmgr_ha_hb_interval: + description: + - Sets the heartbeat interval (1-255). + required: false + default: 5 + + fmgr_ha_file_quota: + description: + - Sets the File quota in MB (2048-20480). + required: false + default: 4096 +''' + + +EXAMPLES = ''' +- name: SET FORTIMANAGER HA NODE TO MASTER + community.network.fmgr_ha: + fmgr_ha_mode: "master" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + +- name: SET FORTIMANAGER HA NODE TO SLAVE + community.network.fmgr_ha: + fmgr_ha_mode: "slave" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + +- name: SET FORTIMANAGER HA NODE TO STANDALONE + community.network.fmgr_ha: + fmgr_ha_mode: "standalone" + +- name: ADD FORTIMANAGER HA PEER + community.network.fmgr_ha: + fmgr_ha_peer_ipv4: "192.168.1.254" + fmgr_ha_peer_sn: "FMG-VM1234567890" + fmgr_ha_peer_status: "enable" + +- name: CREATE CLUSTER ON MASTER + community.network.fmgr_ha: + fmgr_ha_mode: "master" + fmgr_ha_cluster_pw: "fortinet" + fmgr_ha_cluster_id: "1" + fmgr_ha_hb_threshold: "10" + fmgr_ha_hb_interval: "15" + fmgr_ha_file_quota: "2048" +''' +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_set_ha_mode(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + if paramgram["fmgr_ha_cluster_pw"] is not None and str(paramgram["fmgr_ha_mode"].lower()) != "standalone": + datagram = { + "mode": paramgram["fmgr_ha_mode"], + "file-quota": paramgram["fmgr_ha_file_quota"], + "hb-interval": paramgram["fmgr_ha_hb_interval"], + "hb-lost-threshold": paramgram["fmgr_ha_hb_threshold"], + "password": paramgram["fmgr_ha_cluster_pw"], + "clusterid": paramgram["fmgr_ha_cluster_id"] + } + elif str(paramgram["fmgr_ha_mode"].lower()) == "standalone": + datagram = { + "mode": paramgram["fmgr_ha_mode"], + "file-quota": paramgram["fmgr_ha_file_quota"], + "hb-interval": paramgram["fmgr_ha_hb_interval"], + "hb-lost-threshold": paramgram["fmgr_ha_hb_threshold"], + "clusterid": paramgram["fmgr_ha_cluster_id"] + } + + url = '/cli/global/system/ha' + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def fmgr_get_ha_peer_list(fmgr): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + + datagram = {} + paramgram = {} + + url = '/cli/global/system/ha/peer/' + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_set_ha_peer(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + "ip": paramgram["fmgr_ha_peer_ipv4"], + "ip6": paramgram["fmgr_ha_peer_ipv6"], + "serial-number": paramgram["fmgr_ha_peer_sn"], + "status": paramgram["fmgr_ha_peer_status"], + "id": paramgram["peer_id"] + } + + url = '/cli/global/system/ha/peer/' + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def main(): + argument_spec = dict( + fmgr_ha_mode=dict(required=False, type="str", choices=["standalone", "master", "slave"]), + fmgr_ha_cluster_pw=dict(required=False, type="str", no_log=True), + fmgr_ha_peer_status=dict(required=False, type="str", choices=["enable", "disable"]), + fmgr_ha_peer_sn=dict(required=False, type="str"), + fmgr_ha_peer_ipv4=dict(required=False, type="str"), + fmgr_ha_peer_ipv6=dict(required=False, type="str"), + fmgr_ha_hb_threshold=dict(required=False, type="int", default=3), + fmgr_ha_hb_interval=dict(required=False, type="int", default=5), + fmgr_ha_file_quota=dict(required=False, type="int", default=4096), + fmgr_ha_cluster_id=dict(required=False, type="int", default=1) + ) + + required_if = [ + ['fmgr_ha_peer_ipv4', 'present', ['fmgr_ha_peer_sn', 'fmgr_ha_peer_status']], + ['fmgr_ha_peer_ipv6', 'present', ['fmgr_ha_peer_sn', 'fmgr_ha_peer_status']], + ['fmgr_ha_mode', 'master', ['fmgr_ha_cluster_pw', 'fmgr_ha_cluster_id']], + ['fmgr_ha_mode', 'slave', ['fmgr_ha_cluster_pw', 'fmgr_ha_cluster_id']], + ] + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_if=required_if) + paramgram = { + "fmgr_ha_mode": module.params["fmgr_ha_mode"], + "fmgr_ha_cluster_pw": module.params["fmgr_ha_cluster_pw"], + "fmgr_ha_peer_status": module.params["fmgr_ha_peer_status"], + "fmgr_ha_peer_sn": module.params["fmgr_ha_peer_sn"], + "fmgr_ha_peer_ipv4": module.params["fmgr_ha_peer_ipv4"], + "fmgr_ha_peer_ipv6": module.params["fmgr_ha_peer_ipv6"], + "fmgr_ha_hb_threshold": module.params["fmgr_ha_hb_threshold"], + "fmgr_ha_hb_interval": module.params["fmgr_ha_hb_interval"], + "fmgr_ha_file_quota": module.params["fmgr_ha_file_quota"], + "fmgr_ha_cluster_id": module.params["fmgr_ha_cluster_id"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + # INIT FLAGS AND COUNTERS + get_ha_peers = 0 + results = DEFAULT_RESULT_OBJ + try: + if any(v is not None for v in (paramgram["fmgr_ha_peer_sn"], paramgram["fmgr_ha_peer_ipv4"], + paramgram["fmgr_ha_peer_ipv6"], paramgram["fmgr_ha_peer_status"])): + get_ha_peers = 1 + except Exception as err: + raise FMGBaseException(err) + try: + # IF HA MODE IS NOT NULL, SWITCH THAT + if paramgram["fmgr_ha_mode"] is not None: + if (str.lower(paramgram["fmgr_ha_mode"]) != "standalone" and paramgram["fmgr_ha_cluster_pw"] is not None)\ + or str.lower(paramgram["fmgr_ha_mode"]) == "standalone": + results = fmgr_set_ha_mode(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=False, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + elif str.lower(paramgram["fmgr_ha_mode"]) != "standalone" and\ + paramgram["fmgr_ha_mode"] is not None and\ + paramgram["fmgr_ha_cluster_pw"] is None: + module.exit_json(msg="If setting HA Mode of MASTER or SLAVE, you must specify a cluster password") + + except Exception as err: + raise FMGBaseException(err) + # IF GET_HA_PEERS IS ENABLED, LETS PROCESS THE PEERS + try: + if get_ha_peers == 1: + # GET THE CURRENT LIST OF PEERS FROM THE NODE + peers = fmgr_get_ha_peer_list(fmgr) + # GET LENGTH OF RETURNED PEERS LIST AND ADD ONE FOR THE NEXT ID + paramgram["next_peer_id"] = len(peers[1]) + 1 + # SET THE ACTUAL NUMBER OF PEERS + num_of_peers = len(peers[1]) + # SET THE PEER ID FOR DISABLE METHOD + paramgram["peer_id"] = len(peers) - 1 + # SET THE PEER LOOPCOUNT TO 1 TO START THE LOOP + peer_loopcount = 1 + + # LOOP THROUGH PEERS TO FIND THE SERIAL NUMBER MATCH TO GET THE RIGHT PEER ID + # IDEA BEING WE DON'T WANT TO SUBMIT A BAD peer_id THAT DOESN'T JIVE WITH CURRENT DB ON FMG + # SO LETS SEARCH FOR IT, AND IF WE FIND IT, WE WILL CHANGE THE PEER ID VARIABLES TO MATCH + # IF NOT FOUND, LIFE GOES ON AND WE ASSUME THAT WE'RE ADDING A PEER + # AT WHICH POINT THE next_peer_id VARIABLE WILL HAVE THE RIGHT PRIMARY KEY + + if paramgram["fmgr_ha_peer_sn"] is not None: + while peer_loopcount <= num_of_peers: + # GET THE SERIAL NUMBER FOR CURRENT PEER IN LOOP TO COMPARE TO SN IN PLAYBOOK + try: + sn_compare = peers[1][peer_loopcount - 1]["serial-number"] + # IF THE SN IN THE PEERS MATCHES THE PLAYBOOK SN, SET THE IDS + if sn_compare == paramgram["fmgr_ha_peer_sn"]: + paramgram["peer_id"] = peer_loopcount + paramgram["next_peer_id"] = paramgram["peer_id"] + except Exception as err: + raise FMGBaseException(err) + # ADVANCE THE LOOP AND REPEAT UNTIL DONE + peer_loopcount += 1 + + # IF THE PEER STATUS ISN'T IN THE PLAYBOOK, ASSUME ITS ENABLE + if paramgram["fmgr_ha_peer_status"] is None: + paramgram["fmgr_ha_peer_status"] = "enable" + + # IF THE PEER STATUS IS ENABLE, USE THE next_peer_id IN THE API CALL FOR THE ID + if paramgram["fmgr_ha_peer_status"] == "enable": + results = fmgr_set_ha_peer(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, + module.params, paramgram)) + + # IF THE PEER STATUS IS DISABLE, WE HAVE TO HANDLE THAT A BIT DIFFERENTLY + # JUST USING TWO DIFFERENT peer_id 's HERE + if paramgram["fmgr_ha_peer_status"] == "disable": + results = fmgr_set_ha_peer(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, stop_on_success=True, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_provisioning.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_provisioning.py new file mode 100644 index 00000000..3884a12d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_provisioning.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_provisioning +author: Andrew Welsh (@Ghilli3) +short_description: Provision devices via FortiMananger +description: + - Add model devices on the FortiManager using jsonrpc API and have them pre-configured, + so when central management is configured, the configuration is pushed down to the + registering devices + +options: + adom: + description: + - The administrative domain (admon) the configuration belongs to + required: true + vdom: + description: + - The virtual domain (vdom) the configuration belongs to + host: + description: + - The FortiManager's Address. + required: true + username: + description: + - The username to log into the FortiManager + required: true + password: + description: + - The password associated with the username account. + required: false + + policy_package: + description: + - The name of the policy package to be assigned to the device. + required: True + name: + description: + - The name of the device to be provisioned. + required: True + group: + description: + - The name of the device group the provisioned device can belong to. + required: False + serial: + description: + - The serial number of the device that will be provisioned. + required: True + platform: + description: + - The platform of the device, such as model number or VM. + required: True + description: + description: + - Description of the device to be provisioned. + required: False + os_version: + description: + - The Fortinet OS version to be used for the device, such as 5.0 or 6.0. + required: True + minor_release: + description: + - The minor release number such as 6.X.1, as X being the minor release. + required: False + patch_release: + description: + - The patch release number such as 6.0.X, as X being the patch release. + required: False + os_type: + description: + - The Fortinet OS type to be pushed to the device, such as 'FOS' for FortiOS. + required: True +''' + +EXAMPLES = ''' +- name: Create FGT1 Model Device + community.network.fmgr_provisioning: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "default" + name: "FGT1" + group: "Ansible" + serial: "FGVM000000117994" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '6.0' + minor_release: 0 + patch_release: 0 + os_type: 'fos' + + +- name: Create FGT2 Model Device + community.network.fmgr_provisioning: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "test_pp" + name: "FGT2" + group: "Ansible" + serial: "FGVM000000117992" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '5.0' + minor_release: 6 + patch_release: 0 + os_type: 'fos' + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import AnsibleFortiManager + +# check for pyFMG lib +try: + from pyFMG.fortimgr import FortiManager + HAS_PYFMGR = True +except ImportError: + HAS_PYFMGR = False + + +def dev_group_exists(fmg, dev_grp_name, adom): + datagram = { + 'adom': adom, + 'name': dev_grp_name, + } + + url = '/dvmdb/adom/{adom}/group/{dev_grp_name}'.format(adom=adom, dev_grp_name=dev_grp_name) + response = fmg.get(url, datagram) + return response + + +def prov_template_exists(fmg, prov_template, adom, vdom): + datagram = { + 'name': prov_template, + 'adom': adom, + } + + url = '/pm/devprof/adom/{adom}/devprof/{name}'.format(adom=adom, name=prov_template) + response = fmg.get(url, datagram) + return response + + +def create_model_device(fmg, name, serial, group, platform, os_version, + os_type, minor_release, patch_release=0, adom='root'): + datagram = { + 'adom': adom, + 'flags': ['create_task', 'nonblocking'], + 'groups': [{'name': group, 'vdom': 'root'}], + 'device': { + 'mr': minor_release, + 'name': name, + 'sn': serial, + 'mgmt_mode': 'fmg', + 'device action': 'add_model', + 'platform_str': platform, + 'os_ver': os_version, + 'os_type': os_type, + 'patch': patch_release, + 'desc': 'Provisioned by Ansible', + } + } + + url = '/dvm/cmd/add/device' + response = fmg.execute(url, datagram) + return response + + +def update_flags(fmg, name): + datagram = { + 'flags': ['is_model', 'linked_to_model'] + } + url = 'dvmdb/device/{name}'.format(name=name) + response = fmg.update(url, datagram) + return response + + +def assign_provision_template(fmg, template, adom, target): + datagram = { + 'name': template, + 'type': 'devprof', + 'description': 'Provisioned by Ansible', + 'scope member': [{'name': target}] + } + url = "/pm/devprof/adom/{adom}".format(adom=adom) + response = fmg.update(url, datagram) + return response + + +def set_devprof_scope(self, provisioning_template, adom, provision_targets): + """ + GET the DevProf (check to see if exists) + """ + fields = dict() + targets = [] + fields["name"] = provisioning_template + fields["type"] = "devprof" + fields["description"] = "CreatedByAnsible" + + for target in provision_targets.strip().split(","): + # split the host on the space to get the mask out + new_target = {"name": target} + targets.append(new_target) + + fields["scope member"] = targets + + body = {"method": "set", "params": [{"url": "/pm/devprof/adom/{adom}".format(adom=adom), + "data": fields, "session": self.session}]} + response = self.make_request(body).json() + return response + + +def assign_dev_grp(fmg, grp_name, device_name, vdom, adom): + datagram = { + 'name': device_name, + 'vdom': vdom, + } + url = "/dvmdb/adom/{adom}/group/{grp_name}/object member".format(adom=adom, grp_name=grp_name) + response = fmg.set(url, datagram) + return response + + +def update_install_target(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'scope member': [{'name': device, 'vdom': vdom}], + 'type': 'pkg' + } + url = '/pm/pkg/adom/{adom}/{pkg_name}'.format(adom=adom, pkg_name=pp) + response = fmg.update(url, datagram) + return response + + +def install_pp(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'adom': adom, + 'flags': 'nonblocking', + 'pkg': pp, + 'scope': [{'name': device, 'vdom': vdom}], + } + url = 'securityconsole/install/package' + response = fmg.execute(url, datagram) + return response + + +def main(): + + argument_spec = dict( + adom=dict(required=False, type="str"), + vdom=dict(required=False, type="str"), + host=dict(required=True, type="str"), + password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True), + username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"]), no_log=True), + + policy_package=dict(required=False, type="str"), + name=dict(required=False, type="str"), + group=dict(required=False, type="str"), + serial=dict(required=True, type="str"), + platform=dict(required=True, type="str"), + description=dict(required=False, type="str"), + os_version=dict(required=True, type="str"), + minor_release=dict(required=False, type="str"), + patch_release=dict(required=False, type="str"), + os_type=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True, ) + + # check if params are set + if module.params["host"] is None or module.params["username"] is None: + module.fail_json(msg="Host and username are required for connection") + + # check if login failed + fmg = AnsibleFortiManager(module, module.params["host"], module.params["username"], module.params["password"]) + response = fmg.login() + + if "FortiManager instance connnected" not in str(response): + module.fail_json(msg="Connection to FortiManager Failed") + else: + + if module.params["policy_package"] is None: + module.params["policy_package"] = 'default' + if module.params["adom"] is None: + module.params["adom"] = 'root' + if module.params["vdom"] is None: + module.params["vdom"] = 'root' + if module.params["platform"] is None: + module.params["platform"] = 'FortiGate-VM64' + if module.params["os_type"] is None: + module.params["os_type"] = 'fos' + + results = create_model_device(fmg, + module.params["name"], + module.params["serial"], + module.params["group"], + module.params["platform"], + module.params["os_ver"], + module.params["os_type"], + module.params["minor_release"], + module.params["patch_release"], + module.params["adom"]) + if results[0] != 0: + module.fail_json(msg="Create model failed", **results) + + results = update_flags(fmg, module.params["name"]) + if results[0] != 0: + module.fail_json(msg="Update device flags failed", **results) + + # results = assign_dev_grp(fmg, 'Ansible', 'FGVM000000117992', 'root', 'root') + # if not results[0] == 0: + # module.fail_json(msg="Setting device group failed", **results) + + results = update_install_target(fmg, module.params["name"], module.params["policy_package"]) + if results[0] != 0: + module.fail_json(msg="Adding device target to package failed", **results) + + results = install_pp(fmg, module.params["name"], module.params["policy_package"]) + if results[0] != 0: + module.fail_json(msg="Installing policy package failed", **results) + + fmg.logout() + + # results is returned as a tuple + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_query.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_query.py new file mode 100644 index 00000000..661cf1cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_query.py @@ -0,0 +1,424 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_query +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: Luke Weighall (@lweighall) +short_description: Query FortiManager data objects for use in Ansible workflows. +description: + - Provides information on data objects within FortiManager so that playbooks can perform conditionals. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + object: + description: + - The data object we wish to query (device, package, rule, etc). Will expand choices as improves. + required: true + choices: + - device + - cluster_nodes + - task + - custom + + custom_endpoint: + description: + - ADVANCED USERS ONLY! REQUIRES KNOWLEDGE OF FMGR JSON API! + - The HTTP Endpoint on FortiManager you wish to GET from. + required: false + + custom_dict: + description: + - ADVANCED USERS ONLY! REQUIRES KNOWLEDGE OF FMGR JSON API! + - DICTIONARY JSON FORMAT ONLY -- Custom dictionary/datagram to send to the endpoint. + required: false + + device_ip: + description: + - The IP of the device you want to query. + required: false + + device_unique_name: + description: + - The desired "friendly" name of the device you want to query. + required: false + + device_serial: + description: + - The serial number of the device you want to query. + required: false + + task_id: + description: + - The ID of the task you wish to query status on. If left blank and object = 'task' a list of tasks are returned. + required: false + + nodes: + description: + - A LIST of firewalls in the cluster you want to verify i.e. ["firewall_A","firewall_B"]. + required: false +''' + + +EXAMPLES = ''' +- name: QUERY FORTIGATE DEVICE BY IP + community.network.fmgr_query: + object: "device" + adom: "ansible" + device_ip: "10.7.220.41" + +- name: QUERY FORTIGATE DEVICE BY SERIAL + community.network.fmgr_query: + adom: "ansible" + object: "device" + device_serial: "FGVM000000117992" + +- name: QUERY FORTIGATE DEVICE BY FRIENDLY NAME + community.network.fmgr_query: + adom: "ansible" + object: "device" + device_unique_name: "ansible-fgt01" + +- name: VERIFY CLUSTER MEMBERS AND STATUS + community.network.fmgr_query: + adom: "ansible" + object: "cluster_nodes" + device_unique_name: "fgt-cluster01" + nodes: ["ansible-fgt01", "ansible-fgt02", "ansible-fgt03"] + +- name: GET STATUS OF TASK ID + community.network.fmgr_query: + adom: "ansible" + object: "task" + task_id: "3" + +- name: USE CUSTOM TYPE TO QUERY AVAILABLE SCRIPTS + community.network.fmgr_query: + adom: "ansible" + object: "custom" + custom_endpoint: "/dvmdb/adom/ansible/script" + custom_dict: { "type": "cli" } +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def fmgr_get_custom(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # IF THE CUSTOM DICTIONARY (OFTEN CONTAINING FILTERS) IS DEFINED CREATED THAT + if paramgram["custom_dict"] is not None: + datagram = paramgram["custom_dict"] + else: + datagram = dict() + + # SET THE CUSTOM ENDPOINT PROVIDED + url = paramgram["custom_endpoint"] + # MAKE THE CALL AND RETURN RESULTS + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_get_task_status(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # IF THE TASK_ID IS DEFINED, THEN GET THAT SPECIFIC TASK + # OTHERWISE, GET ALL RECENT TASKS IN A LIST + if paramgram["task_id"] is not None: + + datagram = { + "adom": paramgram["adom"] + } + url = '/task/task/{task_id}'.format(task_id=paramgram["task_id"]) + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + else: + datagram = { + "adom": paramgram["adom"] + } + url = '/task/task' + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + return response + + +def fmgr_get_device(fmgr, paramgram): + """ + This method is used to get information on devices. This will not work on HA_SLAVE nodes, only top level devices. + Such as cluster objects and standalone devices. + + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + # FIRST TRY TO RUN AN UPDATE ON THE DEVICE + # RUN A QUICK CLUSTER REFRESH/UPDATE ATTEMPT TO ENSURE WE'RE GETTING THE LATEST INFORMOATION + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + update_url = '/dvm/cmd/update/device' + update_dict = { + "adom": paramgram['adom'], + "device": paramgram['device_unique_name'], + "flags": "create_task" + } + # DO THE UPDATE CALL + fmgr.process_request(update_url, update_dict, FMGRMethods.EXEC) + + # SET THE URL + url = '/dvmdb/adom/{adom}/device'.format(adom=paramgram["adom"]) + device_found = 0 + response = [] + + # TRY TO FIND IT FIRST BY SERIAL NUMBER + if paramgram["device_serial"] is not None: + datagram = { + "filter": ["sn", "==", paramgram["device_serial"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + # CHECK IF ANYTHING WAS RETURNED, IF NOT TRY DEVICE NAME PARAMETER + if device_found == 0 and paramgram["device_unique_name"] is not None: + datagram = { + "filter": ["name", "==", paramgram["device_unique_name"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + # CHECK IF ANYTHING WAS RETURNED, IF NOT TRY DEVICE IP ADDRESS + if device_found == 0 and paramgram["device_ip"] is not None: + datagram = { + "filter": ["ip", "==", paramgram["device_ip"]] + } + response = fmgr.process_request(url, datagram, FMGRMethods.GET) + if len(response[1]) >= 0: + device_found = 1 + + return response + + +def fmgr_get_cluster_nodes(fmgr, paramgram): + """ + This method is used to get information on devices. This WILL work on HA_SLAVE nodes, but NOT top level standalone + devices. + Such as cluster objects and standalone devices. + + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + # USE THE DEVICE METHOD TO GET THE CLUSTER INFORMATION SO WE CAN SEE THE HA_SLAVE NODES + response = fmgr_get_device(fmgr, paramgram) + # CHECK FOR HA_SLAVE NODES, IF CLUSTER IS MISSING COMPLETELY THEN QUIT + try: + returned_nodes = response[1][0]["ha_slave"] + num_of_nodes = len(returned_nodes) + except Exception: + error_msg = {"cluster_status": "MISSING"} + return error_msg + + # INIT LOOP RESOURCES + loop_count = 0 + good_nodes = [] + expected_nodes = list(paramgram["nodes"]) + missing_nodes = list(paramgram["nodes"]) + bad_status_nodes = [] + + # LOOP THROUGH THE NODES AND GET THEIR STATUS TO BUILD THE RETURN JSON OBJECT + # WE'RE ALSO CHECKING THE NODES IF THEY ARE BAD STATUS, OR PLAIN MISSING + while loop_count < num_of_nodes: + node_append = { + "node_name": returned_nodes[loop_count]["name"], + "node_serial": returned_nodes[loop_count]["sn"], + "node_parent": returned_nodes[loop_count]["did"], + "node_status": returned_nodes[loop_count]["status"], + } + # IF THE NODE IS IN THE EXPECTED NODES LIST AND WORKING THEN ADD IT TO GOOD NODES LIST + if node_append["node_name"] in expected_nodes and node_append["node_status"] == 1: + good_nodes.append(node_append["node_name"]) + # IF THE NODE IS IN THE EXPECTED NODES LIST BUT NOT WORKING THEN ADDED IT TO BAD_STATUS_NODES + # IF THE NODE STATUS IS NOT 1 THEN ITS BAD + if node_append["node_name"] in expected_nodes and node_append["node_status"] != 1: + bad_status_nodes.append(node_append["node_name"]) + # REMOVE THE NODE FROM MISSING NODES LIST IF NOTHING IS WRONG WITH NODE -- LEAVING US A LIST OF + # NOT WORKING NODES + missing_nodes.remove(node_append["node_name"]) + loop_count += 1 + + # BUILD RETURN OBJECT FROM NODE LISTS + nodes = { + "good_nodes": good_nodes, + "expected_nodes": expected_nodes, + "missing_nodes": missing_nodes, + "bad_nodes": bad_status_nodes, + "query_status": "good", + } + if len(nodes["good_nodes"]) == len(nodes["expected_nodes"]): + nodes["cluster_status"] = "OK" + else: + nodes["cluster_status"] = "NOT-COMPLIANT" + return nodes + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + object=dict(required=True, type="str", choices=["device", "cluster_nodes", "task", "custom"]), + custom_endpoint=dict(required=False, type="str"), + custom_dict=dict(required=False, type="dict"), + device_ip=dict(required=False, type="str"), + device_unique_name=dict(required=False, type="str"), + device_serial=dict(required=False, type="str"), + nodes=dict(required=False, type="list"), + task_id=dict(required=False, type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "adom": module.params["adom"], + "object": module.params["object"], + "device_ip": module.params["device_ip"], + "device_unique_name": module.params["device_unique_name"], + "device_serial": module.params["device_serial"], + "nodes": module.params["nodes"], + "task_id": module.params["task_id"], + "custom_endpoint": module.params["custom_endpoint"], + "custom_dict": module.params["custom_dict"] + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + # IF OBJECT IS DEVICE + if paramgram["object"] == "device" and any(v is not None for v in [paramgram["device_unique_name"], + paramgram["device_serial"], + paramgram["device_ip"]]): + results = fmgr_get_device(fmgr, paramgram) + if results[0] not in [0]: + module.fail_json(msg="Device query failed!") + elif len(results[1]) == 0: + module.exit_json(msg="Device NOT FOUND!") + else: + module.exit_json(msg="Device Found", **results[1][0]) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS CLUSTER_NODES + if paramgram["object"] == "cluster_nodes" and paramgram["nodes"] is not None: + results = fmgr_get_cluster_nodes(fmgr, paramgram) + if results["cluster_status"] == "MISSING": + module.exit_json(msg="No cluster device found!", **results) + elif results["query_status"] == "good": + module.exit_json(msg="Cluster Found - Showing Nodes", **results) + elif results is None: + module.fail_json(msg="Query FAILED -- Check module or playbook syntax") + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS TASK + if paramgram["object"] == "task": + results = fmgr_get_task_status(fmgr, paramgram) + if results[0] != 0: + module.fail_json(**results[1]) + if results[0] == 0: + module.exit_json(**results[1]) + except Exception as err: + raise FMGBaseException(err) + + try: + # IF OBJECT IS CUSTOM + if paramgram["object"] == "custom": + results = fmgr_get_custom(fmgr, paramgram) + if results[0] != 0: + module.fail_json(msg="QUERY FAILED -- Please check syntax check JSON guide if needed.") + if results[0] == 0: + results_len = len(results[1]) + if results_len > 0: + results_combine = dict() + if isinstance(results[1], dict): + results_combine["results"] = results[1] + if isinstance(results[1], list): + results_combine["results"] = results[1][0:results_len] + module.exit_json(msg="Custom Query Success", **results_combine) + else: + module.exit_json(msg="NO RESULTS") + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_script.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_script.py new file mode 100644 index 00000000..80061826 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_script.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_script +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: Andrew Welsh (@Ghilli3) +short_description: Add/Edit/Delete and execute scripts +description: Create/edit/delete scripts and execute the scripts on the FortiManager using jsonrpc API + +options: + adom: + description: + - The administrative domain (admon) the configuration belongs to + required: true + + vdom: + description: + - The virtual domain (vdom) the configuration belongs to + + mode: + description: + - The desired mode of the specified object. Execute will run the script. + required: false + default: "add" + choices: ["add", "delete", "execute", "set"] + + script_name: + description: + - The name of the script. + required: True + + script_type: + description: + - The type of script (CLI or TCL). + required: false + + script_target: + description: + - The target of the script to be run. + required: false + + script_description: + description: + - The description of the script. + required: false + + script_content: + description: + - The script content that will be executed. + required: false + + script_scope: + description: + - (datasource) The devices that the script will run on, can have both device member and device group member. + required: false + + script_package: + description: + - (datasource) Policy package object to run the script against + required: false +''' + +EXAMPLES = ''' +- name: CREATE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + script_type: "cli" + script_target: "remote_device" + script_description: "Create by Ansible" + script_content: "get system status" + +- name: EXECUTE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + mode: "execute" + script_scope: "FGT1,FGT2" + +- name: DELETE SCRIPT + community.network.fmgr_script: + adom: "root" + script_name: "TestScript" + mode: "delete" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG + + +def set_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + 'content': paramgram["script_content"], + 'desc': paramgram["script_description"], + 'name': paramgram["script_name"], + 'target': paramgram["script_target"], + 'type': paramgram["script_type"], + } + + url = '/dvmdb/adom/{adom}/script/'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.SET) + return response + + +def delete_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + datagram = { + 'name': paramgram["script_name"], + } + + url = '/dvmdb/adom/{adom}/script/{script_name}'.format(adom=paramgram["adom"], script_name=paramgram["script_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) + return response + + +def execute_script(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + scope_list = list() + scope = paramgram["script_scope"].replace(' ', '') + scope = scope.split(',') + for dev_name in scope: + scope_list.append({'name': dev_name, 'vdom': paramgram["vdom"]}) + + datagram = { + 'adom': paramgram["adom"], + 'script': paramgram["script_name"], + 'package': paramgram["script_package"], + 'scope': scope_list, + } + + url = '/dvmdb/adom/{adom}/script/execute'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) + return response + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + vdom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "execute", "set", "delete"], type="str", default="add"), + script_name=dict(required=True, type="str"), + script_type=dict(required=False, type="str"), + script_target=dict(required=False, type="str"), + script_description=dict(required=False, type="str"), + script_content=dict(required=False, type="str"), + script_scope=dict(required=False, type="str"), + script_package=dict(required=False, type="str"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "script_name": module.params["script_name"], + "script_type": module.params["script_type"], + "script_target": module.params["script_target"], + "script_description": module.params["script_description"], + "script_content": module.params["script_content"], + "script_scope": module.params["script_scope"], + "script_package": module.params["script_package"], + "adom": module.params["adom"], + "vdom": module.params["vdom"], + "mode": module.params["mode"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + if paramgram["mode"] in ['add', 'set']: + results = set_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + try: + if paramgram["mode"] == "execute": + results = execute_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + try: + if paramgram["mode"] == "delete": + results = delete_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_appctrl.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_appctrl.py new file mode 100644 index 00000000..477ab7d5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_appctrl.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_appctrl +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage application control security profiles +description: + - Manage application control security profiles within FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + unknown_application_log: + description: + - Enable/disable logging for unknown applications. + - choice | disable | Disable logging for unknown applications. + - choice | enable | Enable logging for unknown applications. + required: false + choices: ["disable", "enable"] + + unknown_application_action: + description: + - Pass or block traffic from unknown applications. + - choice | pass | Pass or allow unknown applications. + - choice | block | Drop or block unknown applications. + required: false + choices: ["pass", "block"] + + replacemsg_group: + description: + - Replacement message group. + required: false + + p2p_black_list: + description: + - NO DESCRIPTION PARSED ENTER MANUALLY + - FLAG Based Options. Specify multiple in list form. + - flag | skype | Skype. + - flag | edonkey | Edonkey. + - flag | bittorrent | Bit torrent. + required: false + choices: ["skype", "edonkey", "bittorrent"] + + other_application_log: + description: + - Enable/disable logging for other applications. + - choice | disable | Disable logging for other applications. + - choice | enable | Enable logging for other applications. + required: false + choices: ["disable", "enable"] + + other_application_action: + description: + - Action for other applications. + - choice | pass | Allow sessions matching an application in this application list. + - choice | block | Block sessions matching an application in this application list. + required: false + choices: ["pass", "block"] + + options: + description: + - NO DESCRIPTION PARSED ENTER MANUALLY + - FLAG Based Options. Specify multiple in list form. + - flag | allow-dns | Allow DNS. + - flag | allow-icmp | Allow ICMP. + - flag | allow-http | Allow generic HTTP web browsing. + - flag | allow-ssl | Allow generic SSL communication. + - flag | allow-quic | Allow QUIC. + required: false + choices: ["allow-dns", "allow-icmp", "allow-http", "allow-ssl", "allow-quic"] + + name: + description: + - List name. + required: false + + extended_log: + description: + - Enable/disable extended logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + deep_app_inspection: + description: + - Enable/disable deep application inspection. + - choice | disable | Disable deep application inspection. + - choice | enable | Enable deep application inspection. + required: false + choices: ["disable", "enable"] + + comment: + description: + - comments + required: false + + app_replacemsg: + description: + - Enable/disable replacement messages for blocked applications. + - choice | disable | Disable replacement messages for blocked applications. + - choice | enable | Enable replacement messages for blocked applications. + required: false + choices: ["disable", "enable"] + + entries: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, OMIT THE USE OF THIS PARAMETER + - AND USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + entries_action: + description: + - Pass or block traffic, or reset connection for traffic from this application. + - choice | pass | Pass or allow matching traffic. + - choice | block | Block or drop matching traffic. + - choice | reset | Reset sessions for matching traffic. + required: false + choices: ["pass", "block", "reset"] + + entries_application: + description: + - ID of allowed applications. + required: false + + entries_behavior: + description: + - Application behavior filter. + required: false + + entries_category: + description: + - Category ID list. + required: false + + entries_log: + description: + - Enable/disable logging for this application list. + - choice | disable | Disable logging. + - choice | enable | Enable logging. + required: false + choices: ["disable", "enable"] + + entries_log_packet: + description: + - Enable/disable packet logging. + - choice | disable | Disable packet logging. + - choice | enable | Enable packet logging. + required: false + choices: ["disable", "enable"] + + entries_per_ip_shaper: + description: + - Per-IP traffic shaper. + required: false + + entries_popularity: + description: + - Application popularity filter (1 - 5, from least to most popular). + - FLAG Based Options. Specify multiple in list form. + - flag | 1 | Popularity level 1. + - flag | 2 | Popularity level 2. + - flag | 3 | Popularity level 3. + - flag | 4 | Popularity level 4. + - flag | 5 | Popularity level 5. + required: false + choices: ["1", "2", "3", "4", "5"] + + entries_protocols: + description: + - Application protocol filter. + required: false + + entries_quarantine: + description: + - Quarantine method. + - choice | none | Quarantine is disabled. + - choice | attacker | Block all traffic sent from attacker's IP address. + - The attacker's IP address is also added to the banned user list. The target's address is not affected. + required: false + choices: ["none", "attacker"] + + entries_quarantine_expiry: + description: + - Duration of quarantine. (Format ###d##h##m, minimum 1m, maximum 364d23h59m, default = 5m). + - Requires quarantine set to attacker. + required: false + + entries_quarantine_log: + description: + - Enable/disable quarantine logging. + - choice | disable | Disable quarantine logging. + - choice | enable | Enable quarantine logging. + required: false + choices: ["disable", "enable"] + + entries_rate_count: + description: + - Count of the rate. + required: false + + entries_rate_duration: + description: + - Duration (sec) of the rate. + required: false + + entries_rate_mode: + description: + - Rate limit mode. + - choice | periodical | Allow configured number of packets every rate-duration. + - choice | continuous | Block packets once the rate is reached. + required: false + choices: ["periodical", "continuous"] + + entries_rate_track: + description: + - Track the packet protocol field. + - choice | none | + - choice | src-ip | Source IP. + - choice | dest-ip | Destination IP. + - choice | dhcp-client-mac | DHCP client. + - choice | dns-domain | DNS domain. + required: false + choices: ["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"] + + entries_risk: + description: + - Risk, or impact, of allowing traffic from this application to occur 1 - 5; + - (Low, Elevated, Medium, High, and Critical). + required: false + + entries_session_ttl: + description: + - Session TTL (0 = default). + required: false + + entries_shaper: + description: + - Traffic shaper. + required: false + + entries_shaper_reverse: + description: + - Reverse traffic shaper. + required: false + + entries_sub_category: + description: + - Application Sub-category ID list. + required: false + + entries_technology: + description: + - Application technology filter. + required: false + + entries_vendor: + description: + - Application vendor filter. + required: false + + entries_parameters_value: + description: + - Parameter value. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_appctrl: + name: "Ansible_Application_Control_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_appctrl: + name: "Ansible_Application_Control_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + entries: [{ + action: "block", + log: "enable", + log-packet: "enable", + protocols: ["1"], + quarantine: "attacker", + quarantine-log: "enable", + }, + {action: "pass", + category: ["2","3","4"]}, + ] +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_application_list_modify(fmgr, paramgram): + """ + fmgr_application_list -- Modifies Application Control Profiles on FortiManager + + :param fmgr: The fmgr object instance from fmgr_utils.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + + :return: The response from the FortiManager + :rtype: dict + """ + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if paramgram["mode"] in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/application/list'.format(adom=paramgram["adom"]) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif paramgram["mode"] == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/application/list/{name}'.format(adom=paramgram["adom"], + name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + unknown_application_log=dict(required=False, type="str", choices=["disable", "enable"]), + unknown_application_action=dict(required=False, type="str", choices=["pass", "block"]), + replacemsg_group=dict(required=False, type="str"), + p2p_black_list=dict(required=False, type="str", choices=["skype", "edonkey", "bittorrent"]), + other_application_log=dict(required=False, type="str", choices=["disable", "enable"]), + other_application_action=dict(required=False, type="str", choices=["pass", "block"]), + options=dict(required=False, type="str", + choices=["allow-dns", "allow-icmp", "allow-http", "allow-ssl", "allow-quic"]), + name=dict(required=False, type="str"), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + deep_app_inspection=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + app_replacemsg=dict(required=False, type="str", choices=["disable", "enable"]), + entries=dict(required=False, type="list"), + entries_action=dict(required=False, type="str", choices=["pass", "block", "reset"]), + entries_application=dict(required=False, type="str"), + entries_behavior=dict(required=False, type="str"), + entries_category=dict(required=False, type="str"), + entries_log=dict(required=False, type="str", choices=["disable", "enable"]), + entries_log_packet=dict(required=False, type="str", choices=["disable", "enable"]), + entries_per_ip_shaper=dict(required=False, type="str"), + entries_popularity=dict(required=False, type="str", choices=["1", "2", "3", "4", "5"]), + entries_protocols=dict(required=False, type="str"), + entries_quarantine=dict(required=False, type="str", choices=["none", "attacker"]), + entries_quarantine_expiry=dict(required=False, type="str"), + entries_quarantine_log=dict(required=False, type="str", choices=["disable", "enable"]), + entries_rate_count=dict(required=False, type="int"), + entries_rate_duration=dict(required=False, type="int"), + entries_rate_mode=dict(required=False, type="str", choices=["periodical", "continuous"]), + entries_rate_track=dict(required=False, type="str", + choices=["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"]), + entries_risk=dict(required=False, type="str"), + entries_session_ttl=dict(required=False, type="int"), + entries_shaper=dict(required=False, type="str"), + entries_shaper_reverse=dict(required=False, type="str"), + entries_sub_category=dict(required=False, type="str"), + entries_technology=dict(required=False, type="str"), + entries_vendor=dict(required=False, type="str"), + + entries_parameters_value=dict(required=False, type="str"), + + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "unknown-application-log": module.params["unknown_application_log"], + "unknown-application-action": module.params["unknown_application_action"], + "replacemsg-group": module.params["replacemsg_group"], + "p2p-black-list": module.params["p2p_black_list"], + "other-application-log": module.params["other_application_log"], + "other-application-action": module.params["other_application_action"], + "options": module.params["options"], + "name": module.params["name"], + "extended-log": module.params["extended_log"], + "deep-app-inspection": module.params["deep_app_inspection"], + "comment": module.params["comment"], + "app-replacemsg": module.params["app_replacemsg"], + "entries": { + "action": module.params["entries_action"], + "application": module.params["entries_application"], + "behavior": module.params["entries_behavior"], + "category": module.params["entries_category"], + "log": module.params["entries_log"], + "log-packet": module.params["entries_log_packet"], + "per-ip-shaper": module.params["entries_per_ip_shaper"], + "popularity": module.params["entries_popularity"], + "protocols": module.params["entries_protocols"], + "quarantine": module.params["entries_quarantine"], + "quarantine-expiry": module.params["entries_quarantine_expiry"], + "quarantine-log": module.params["entries_quarantine_log"], + "rate-count": module.params["entries_rate_count"], + "rate-duration": module.params["entries_rate_duration"], + "rate-mode": module.params["entries_rate_mode"], + "rate-track": module.params["entries_rate_track"], + "risk": module.params["entries_risk"], + "session-ttl": module.params["entries_session_ttl"], + "shaper": module.params["entries_shaper"], + "shaper-reverse": module.params["entries_shaper_reverse"], + "sub-category": module.params["entries_sub_category"], + "technology": module.params["entries_technology"], + "vendor": module.params["entries_vendor"], + "parameters": { + "value": module.params["entries_parameters_value"], + } + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['entries'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_application_list_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_av.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_av.py new file mode 100644 index 00000000..bf2768b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_av.py @@ -0,0 +1,1386 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_av +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage security profile +description: + - Manage security profile groups for FortiManager objects + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + scan_mode: + description: + - Choose between full scan mode and quick scan mode. + required: false + choices: + - quick + - full + + replacemsg_group: + description: + - Replacement message group customized for this profile. + required: false + + name: + description: + - Profile name. + required: false + + mobile_malware_db: + description: + - Enable/disable using the mobile malware signature database. + required: false + choices: + - disable + - enable + + inspection_mode: + description: + - Inspection mode. + required: false + choices: + - proxy + - flow-based + + ftgd_analytics: + description: + - Settings to control which files are uploaded to FortiSandbox. + required: false + choices: + - disable + - suspicious + - everything + + extended_log: + description: + - Enable/disable extended logging for antivirus. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + av_virus_log: + description: + - Enable/disable AntiVirus logging. + required: false + choices: + - disable + - enable + + av_block_log: + description: + - Enable/disable logging for AntiVirus file blocking. + required: false + choices: + - disable + - enable + + analytics_wl_filetype: + description: + - Do not submit files matching this DLP file-pattern to FortiSandbox. + required: false + + analytics_max_upload: + description: + - Maximum size of files that can be uploaded to FortiSandbox (1 - 395 MBytes, default = 10). + required: false + + analytics_db: + description: + - Enable/disable using the FortiSandbox signature database to supplement the AV signature databases. + required: false + choices: + - disable + - enable + + analytics_bl_filetype: + description: + - Only submit files matching this DLP file-pattern to FortiSandbox. + required: false + + content_disarm: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + content_disarm_cover_page: + description: + - Enable/disable inserting a cover page into the disarmed document. + required: false + choices: + - disable + - enable + + content_disarm_detect_only: + description: + - Enable/disable only detect disarmable files, do not alter content. + required: false + choices: + - disable + - enable + + content_disarm_office_embed: + description: + - Enable/disable stripping of embedded objects in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_hylink: + description: + - Enable/disable stripping of hyperlinks in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_linked: + description: + - Enable/disable stripping of linked objects in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_office_macro: + description: + - Enable/disable stripping of macros in Microsoft Office documents. + required: false + choices: + - disable + - enable + + content_disarm_original_file_destination: + description: + - Destination to send original file if active content is removed. + required: false + choices: + - fortisandbox + - quarantine + - discard + + content_disarm_pdf_act_form: + description: + - Enable/disable stripping of actions that submit data to other targets in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_gotor: + description: + - Enable/disable stripping of links to other PDFs in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_java: + description: + - Enable/disable stripping of actions that execute JavaScript code in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_launch: + description: + - Enable/disable stripping of links to external applications in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_movie: + description: + - Enable/disable stripping of embedded movies in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_act_sound: + description: + - Enable/disable stripping of embedded sound files in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_embedfile: + description: + - Enable/disable stripping of embedded files in PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_hyperlink: + description: + - Enable/disable stripping of hyperlinks from PDF documents. + required: false + choices: + - disable + - enable + + content_disarm_pdf_javacode: + description: + - Enable/disable stripping of JavaScript code in PDF documents. + required: false + choices: + - disable + - enable + + ftp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + ftp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + ftp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + ftp_options: + description: + - Enable/disable FTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + ftp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + http: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + http_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + http_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + http_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + http_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + http_options: + description: + - Enable/disable HTTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + http_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + imap: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imap_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + imap_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + imap_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + imap_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + imap_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + imap_options: + description: + - Enable/disable IMAP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + imap_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + mapi_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + mapi_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + mapi_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + mapi_options: + description: + - Enable/disable MAPI AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + mapi_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + nac_quar: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + nac_quar_expiry: + description: + - Duration of quarantine. + required: false + + nac_quar_infected: + description: + - Enable/Disable quarantining infected hosts to the banned user list. + required: false + choices: + - none + - quar-src-ip + + nac_quar_log: + description: + - Enable/disable AntiVirus quarantine logging. + required: false + choices: + - disable + - enable + + nntp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + nntp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + nntp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + nntp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + nntp_options: + description: + - Enable/disable NNTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + nntp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + pop3: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + pop3_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + pop3_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + pop3_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + pop3_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + pop3_options: + description: + - Enable/disable POP3 AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + pop3_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + smb: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smb_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smb_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smb_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + smb_options: + description: + - Enable/disable SMB AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + smb_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive + + smtp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtp_archive_block: + description: + - Select the archive types to block. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smtp_archive_log: + description: + - Select the archive types to log. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - encrypted + - corrupted + - multipart + - nested + - mailbomb + - unhandled + - partiallycorrupted + - fileslimit + - timeout + + smtp_content_disarm: + description: + - Enable Content Disarm and Reconstruction for this protocol. + required: false + choices: + - disable + - enable + + smtp_emulator: + description: + - Enable/disable the virus emulator. + required: false + choices: + - disable + - enable + + smtp_executables: + description: + - Treat Windows executable files as viruses for the purpose of blocking or monitoring. + required: false + choices: + - default + - virus + + smtp_options: + description: + - Enable/disable SMTP AntiVirus scanning, monitoring, and quarantine. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - scan + - quarantine + - avmonitor + + smtp_outbreak_prevention: + description: + - Enable FortiGuard Virus Outbreak Prevention service. + required: false + choices: + - disabled + - files + - full-archive +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_av: + name: "Ansible_AV_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_av: + name: "Ansible_AV_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + inspection_mode: "proxy" + ftgd_analytics: "everything" + av_block_log: "enable" + av_virus_log: "enable" + scan_mode: "full" + mobile_malware_db: "enable" + ftp_archive_block: "encrypted" + ftp_outbreak_prevention: "files" + ftp_archive_log: "timeout" + ftp_emulator: "disable" + ftp_options: "scan" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_antivirus_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/antivirus/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + else: + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/antivirus/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + return response + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + scan_mode=dict(required=False, type="str", choices=["quick", "full"]), + replacemsg_group=dict(required=False, type="dict"), + name=dict(required=False, type="str"), + mobile_malware_db=dict(required=False, type="str", choices=["disable", "enable"]), + inspection_mode=dict(required=False, type="str", choices=["proxy", "flow-based"]), + ftgd_analytics=dict(required=False, type="str", choices=["disable", "suspicious", "everything"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + av_virus_log=dict(required=False, type="str", choices=["disable", "enable"]), + av_block_log=dict(required=False, type="str", choices=["disable", "enable"]), + analytics_wl_filetype=dict(required=False, type="dict"), + analytics_max_upload=dict(required=False, type="int"), + analytics_db=dict(required=False, type="str", choices=["disable", "enable"]), + analytics_bl_filetype=dict(required=False, type="dict"), + content_disarm=dict(required=False, type="list"), + content_disarm_cover_page=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_detect_only=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_embed=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_hylink=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_linked=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_office_macro=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_original_file_destination=dict(required=False, type="str", choices=["fortisandbox", + "quarantine", + "discard"]), + content_disarm_pdf_act_form=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_gotor=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_java=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_launch=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_movie=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_act_sound=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_embedfile=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_hyperlink=dict(required=False, type="str", choices=["disable", "enable"]), + content_disarm_pdf_javacode=dict(required=False, type="str", choices=["disable", "enable"]), + ftp=dict(required=False, type="list"), + ftp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + ftp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + ftp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + ftp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + http=dict(required=False, type="list"), + http_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + http_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + http_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + http_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + http_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + http_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + imap=dict(required=False, type="list"), + imap_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + imap_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + imap_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + imap_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + imap_executables=dict(required=False, type="str", choices=["default", "virus"]), + imap_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + imap_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + mapi=dict(required=False, type="list"), + mapi_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + mapi_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + mapi_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_executables=dict(required=False, type="str", choices=["default", "virus"]), + mapi_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + mapi_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + nac_quar=dict(required=False, type="list"), + nac_quar_expiry=dict(required=False, type="str"), + nac_quar_infected=dict(required=False, type="str", choices=["none", "quar-src-ip"]), + nac_quar_log=dict(required=False, type="str", choices=["disable", "enable"]), + nntp=dict(required=False, type="list"), + nntp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + nntp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + nntp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + nntp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + nntp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + pop3=dict(required=False, type="list"), + pop3_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + pop3_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + pop3_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_executables=dict(required=False, type="str", choices=["default", "virus"]), + pop3_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + pop3_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + smb=dict(required=False, type="list"), + smb_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smb_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smb_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + smb_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + smb_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + smtp=dict(required=False, type="list"), + smtp_archive_block=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smtp_archive_log=dict(required=False, type="str", choices=["encrypted", + "corrupted", + "multipart", + "nested", + "mailbomb", + "unhandled", + "partiallycorrupted", + "fileslimit", + "timeout"]), + smtp_content_disarm=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_emulator=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_executables=dict(required=False, type="str", choices=["default", "virus"]), + smtp_options=dict(required=False, type="str", choices=["scan", "quarantine", "avmonitor"]), + smtp_outbreak_prevention=dict(required=False, type="str", choices=["disabled", "files", "full-archive"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "scan-mode": module.params["scan_mode"], + "replacemsg-group": module.params["replacemsg_group"], + "name": module.params["name"], + "mobile-malware-db": module.params["mobile_malware_db"], + "inspection-mode": module.params["inspection_mode"], + "ftgd-analytics": module.params["ftgd_analytics"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "av-virus-log": module.params["av_virus_log"], + "av-block-log": module.params["av_block_log"], + "analytics-wl-filetype": module.params["analytics_wl_filetype"], + "analytics-max-upload": module.params["analytics_max_upload"], + "analytics-db": module.params["analytics_db"], + "analytics-bl-filetype": module.params["analytics_bl_filetype"], + "content-disarm": { + "cover-page": module.params["content_disarm_cover_page"], + "detect-only": module.params["content_disarm_detect_only"], + "office-embed": module.params["content_disarm_office_embed"], + "office-hylink": module.params["content_disarm_office_hylink"], + "office-linked": module.params["content_disarm_office_linked"], + "office-macro": module.params["content_disarm_office_macro"], + "original-file-destination": module.params["content_disarm_original_file_destination"], + "pdf-act-form": module.params["content_disarm_pdf_act_form"], + "pdf-act-gotor": module.params["content_disarm_pdf_act_gotor"], + "pdf-act-java": module.params["content_disarm_pdf_act_java"], + "pdf-act-launch": module.params["content_disarm_pdf_act_launch"], + "pdf-act-movie": module.params["content_disarm_pdf_act_movie"], + "pdf-act-sound": module.params["content_disarm_pdf_act_sound"], + "pdf-embedfile": module.params["content_disarm_pdf_embedfile"], + "pdf-hyperlink": module.params["content_disarm_pdf_hyperlink"], + "pdf-javacode": module.params["content_disarm_pdf_javacode"], + }, + "ftp": { + "archive-block": module.params["ftp_archive_block"], + "archive-log": module.params["ftp_archive_log"], + "emulator": module.params["ftp_emulator"], + "options": module.params["ftp_options"], + "outbreak-prevention": module.params["ftp_outbreak_prevention"], + }, + "http": { + "archive-block": module.params["http_archive_block"], + "archive-log": module.params["http_archive_log"], + "content-disarm": module.params["http_content_disarm"], + "emulator": module.params["http_emulator"], + "options": module.params["http_options"], + "outbreak-prevention": module.params["http_outbreak_prevention"], + }, + "imap": { + "archive-block": module.params["imap_archive_block"], + "archive-log": module.params["imap_archive_log"], + "content-disarm": module.params["imap_content_disarm"], + "emulator": module.params["imap_emulator"], + "executables": module.params["imap_executables"], + "options": module.params["imap_options"], + "outbreak-prevention": module.params["imap_outbreak_prevention"], + }, + "mapi": { + "archive-block": module.params["mapi_archive_block"], + "archive-log": module.params["mapi_archive_log"], + "emulator": module.params["mapi_emulator"], + "executables": module.params["mapi_executables"], + "options": module.params["mapi_options"], + "outbreak-prevention": module.params["mapi_outbreak_prevention"], + }, + "nac-quar": { + "expiry": module.params["nac_quar_expiry"], + "infected": module.params["nac_quar_infected"], + "log": module.params["nac_quar_log"], + }, + "nntp": { + "archive-block": module.params["nntp_archive_block"], + "archive-log": module.params["nntp_archive_log"], + "emulator": module.params["nntp_emulator"], + "options": module.params["nntp_options"], + "outbreak-prevention": module.params["nntp_outbreak_prevention"], + }, + "pop3": { + "archive-block": module.params["pop3_archive_block"], + "archive-log": module.params["pop3_archive_log"], + "content-disarm": module.params["pop3_content_disarm"], + "emulator": module.params["pop3_emulator"], + "executables": module.params["pop3_executables"], + "options": module.params["pop3_options"], + "outbreak-prevention": module.params["pop3_outbreak_prevention"], + }, + "smb": { + "archive-block": module.params["smb_archive_block"], + "archive-log": module.params["smb_archive_log"], + "emulator": module.params["smb_emulator"], + "options": module.params["smb_options"], + "outbreak-prevention": module.params["smb_outbreak_prevention"], + }, + "smtp": { + "archive-block": module.params["smtp_archive_block"], + "archive-log": module.params["smtp_archive_log"], + "content-disarm": module.params["smtp_content_disarm"], + "emulator": module.params["smtp_emulator"], + "executables": module.params["smtp_executables"], + "options": module.params["smtp_options"], + "outbreak-prevention": module.params["smtp_outbreak_prevention"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ["content-disarm", "ftp", "http", "imap", "mapi", "nac-quar", "nntp", "pop3", "smb", "smtp"] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_antivirus_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_dns.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_dns.py new file mode 100644 index 00000000..80558e15 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_dns.py @@ -0,0 +1,339 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_dns +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage DNS security profiles in FortiManager +description: + - Manage DNS security profiles in FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + youtube_restrict: + type: str + description: + - Set safe search for YouTube restriction level. + - choice | strict | Enable strict safe seach for YouTube. + - choice | moderate | Enable moderate safe search for YouTube. + required: false + choices: ["strict", "moderate"] + + sdns_ftgd_err_log: + type: str + description: + - Enable/disable FortiGuard SDNS rating error logging. + - choice | disable | Disable FortiGuard SDNS rating error logging. + - choice | enable | Enable FortiGuard SDNS rating error logging. + required: false + choices: ["disable", "enable"] + + sdns_domain_log: + type: str + description: + - Enable/disable domain filtering and botnet domain logging. + - choice | disable | Disable domain filtering and botnet domain logging. + - choice | enable | Enable domain filtering and botnet domain logging. + required: false + choices: ["disable", "enable"] + + safe_search: + type: str + description: + - Enable/disable Google, Bing, and YouTube safe search. + - choice | disable | Disable Google, Bing, and YouTube safe search. + - choice | enable | Enable Google, Bing, and YouTube safe search. + required: false + choices: ["disable", "enable"] + + redirect_portal: + type: str + description: + - IP address of the SDNS redirect portal. + required: false + + name: + type: str + description: + - Profile name. + required: false + + log_all_domain: + type: str + description: + - Enable/disable logging of all domains visited (detailed DNS logging). + - choice | disable | Disable logging of all domains visited. + - choice | enable | Enable logging of all domains visited. + required: false + choices: ["disable", "enable"] + + external_ip_blocklist: + type: str + description: + - One or more external IP block lists. + required: false + + comment: + type: str + description: + - Comment for the security profile to show in the FortiManager GUI. + required: false + + block_botnet: + type: str + description: + - Enable/disable blocking botnet C&C; DNS lookups. + - choice | disable | Disable blocking botnet C&C; DNS lookups. + - choice | enable | Enable blocking botnet C&C; DNS lookups. + required: false + choices: ["disable", "enable"] + + block_action: + type: str + description: + - Action to take for blocked domains. + - choice | block | Return NXDOMAIN for blocked domains. + - choice | redirect | Redirect blocked domains to SDNS portal. + required: false + choices: ["block", "redirect"] + + domain_filter_domain_filter_table: + type: str + description: + - DNS domain filter table ID. + required: false + + ftgd_dns_options: + type: str + description: + - FortiGuard DNS filter options. + - FLAG Based Options. Specify multiple in list form. + - flag | error-allow | Allow all domains when FortiGuard DNS servers fail. + - flag | ftgd-disable | Disable FortiGuard DNS domain rating. + required: false + choices: ["error-allow", "ftgd-disable"] + + ftgd_dns_filters_action: + type: str + description: + - Action to take for DNS requests matching the category. + - choice | monitor | Allow DNS requests matching the category and log the result. + - choice | block | Block DNS requests matching the category. + required: false + choices: ["monitor", "block"] + + ftgd_dns_filters_category: + type: str + description: + - Category number. + required: false + + ftgd_dns_filters_log: + type: str + description: + - Enable/disable DNS filter logging for this DNS profile. + - choice | disable | Disable DNS filter logging. + - choice | enable | Enable DNS filter logging. + required: false + choices: ["disable", "enable"] + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_dns: + name: "Ansible_DNS_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_dns: + name: "Ansible_DNS_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + block_action: "block" + + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_dnsfilter_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + url = "" + datagram = {} + + response = DEFAULT_RESULT_OBJ + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/dnsfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/dnsfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + youtube_restrict=dict(required=False, type="str", choices=["strict", "moderate"]), + sdns_ftgd_err_log=dict(required=False, type="str", choices=["disable", "enable"]), + sdns_domain_log=dict(required=False, type="str", choices=["disable", "enable"]), + safe_search=dict(required=False, type="str", choices=["disable", "enable"]), + redirect_portal=dict(required=False, type="str"), + name=dict(required=False, type="str"), + log_all_domain=dict(required=False, type="str", choices=["disable", "enable"]), + external_ip_blocklist=dict(required=False, type="str"), + comment=dict(required=False, type="str"), + block_botnet=dict(required=False, type="str", choices=["disable", "enable"]), + block_action=dict(required=False, type="str", choices=["block", "redirect"]), + + domain_filter_domain_filter_table=dict(required=False, type="str"), + + ftgd_dns_options=dict(required=False, type="str", choices=["error-allow", "ftgd-disable"]), + + ftgd_dns_filters_action=dict(required=False, type="str", choices=["monitor", "block"]), + ftgd_dns_filters_category=dict(required=False, type="str"), + ftgd_dns_filters_log=dict(required=False, type="str", choices=["disable", "enable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "youtube-restrict": module.params["youtube_restrict"], + "sdns-ftgd-err-log": module.params["sdns_ftgd_err_log"], + "sdns-domain-log": module.params["sdns_domain_log"], + "safe-search": module.params["safe_search"], + "redirect-portal": module.params["redirect_portal"], + "name": module.params["name"], + "log-all-domain": module.params["log_all_domain"], + "external-ip-blocklist": module.params["external_ip_blocklist"], + "comment": module.params["comment"], + "block-botnet": module.params["block_botnet"], + "block-action": module.params["block_action"], + "domain-filter": { + "domain-filter-table": module.params["domain_filter_domain_filter_table"], + }, + "ftgd-dns": { + "options": module.params["ftgd_dns_options"], + "filters": { + "action": module.params["ftgd_dns_filters_action"], + "category": module.params["ftgd_dns_filters_category"], + "log": module.params["ftgd_dns_filters_log"], + } + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_dnsfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ips.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ips.py new file mode 100644 index 00000000..4aab6346 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ips.py @@ -0,0 +1,664 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_ips +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Managing IPS security profiles in FortiManager +description: + - Managing IPS security profiles in FortiManager + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + replacemsg_group: + description: + - Replacement message group. + required: false + + name: + description: + - Sensor name. + required: false + + extended_log: + description: + - Enable/disable extended logging. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + block_malicious_url: + description: + - Enable/disable malicious URL blocking. + required: false + choices: + - disable + - enable + + entries: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + entries_action: + description: + - Action taken with traffic in which signatures are detected. + required: false + choices: + - pass + - block + - reset + - default + + entries_application: + description: + - Applications to be protected. set application ? lists available applications. all includes + all applications. other includes all unlisted applications. + required: false + + entries_location: + description: + - Protect client or server traffic. + required: false + + entries_log: + description: + - Enable/disable logging of signatures included in filter. + required: false + choices: + - disable + - enable + + entries_log_attack_context: + description: + - Enable/disable logging of attack context| URL buffer, header buffer, body buffer, packet buffer. + required: false + choices: + - disable + - enable + + entries_log_packet: + description: + - Enable/disable packet logging. Enable to save the packet that triggers the filter. You can + download the packets in pcap format for diagnostic use. + required: false + choices: + - disable + - enable + + entries_os: + description: + - Operating systems to be protected. all includes all operating systems. other includes all + unlisted operating systems. + required: false + + entries_protocol: + description: + - Protocols to be examined. set protocol ? lists available protocols. all includes all protocols. + other includes all unlisted protocols. + required: false + + entries_quarantine: + description: + - Quarantine method. + required: false + choices: + - none + - attacker + + entries_quarantine_expiry: + description: + - Duration of quarantine. + required: false + + entries_quarantine_log: + description: + - Enable/disable quarantine logging. + required: false + choices: + - disable + - enable + + entries_rate_count: + description: + - Count of the rate. + required: false + + entries_rate_duration: + description: + - Duration (sec) of the rate. + required: false + + entries_rate_mode: + description: + - Rate limit mode. + required: false + choices: + - periodical + - continuous + + entries_rate_track: + description: + - Track the packet protocol field. + required: false + choices: + - none + - src-ip + - dest-ip + - dhcp-client-mac + - dns-domain + + entries_rule: + description: + - Identifies the predefined or custom IPS signatures to add to the sensor. + required: false + + entries_severity: + description: + - Relative severity of the signature, from info to critical. Log messages generated by the signature + include the severity. + required: false + + entries_status: + description: + - Status of the signatures included in filter. default enables the filter and only use filters + with default status of enable. Filters with default status of disable will not be used. + required: false + choices: + - disable + - enable + - default + + entries_exempt_ip_dst_ip: + description: + - Destination IP address and netmask. + required: false + + entries_exempt_ip_src_ip: + description: + - Source IP address and netmask. + required: false + + filter: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + filter_action: + description: + - Action of selected rules. + required: false + choices: + - pass + - block + - default + - reset + + filter_application: + description: + - Vulnerable application filter. + required: false + + filter_location: + description: + - Vulnerability location filter. + required: false + + filter_log: + description: + - Enable/disable logging of selected rules. + required: false + choices: + - disable + - enable + + filter_log_packet: + description: + - Enable/disable packet logging of selected rules. + required: false + choices: + - disable + - enable + + filter_name: + description: + - Filter name. + required: false + + filter_os: + description: + - Vulnerable OS filter. + required: false + + filter_protocol: + description: + - Vulnerable protocol filter. + required: false + + filter_quarantine: + description: + - Quarantine IP or interface. + required: false + choices: + - none + - attacker + + filter_quarantine_expiry: + description: + - Duration of quarantine in minute. + required: false + + filter_quarantine_log: + description: + - Enable/disable logging of selected quarantine. + required: false + choices: + - disable + - enable + + filter_severity: + description: + - Vulnerability severity filter. + required: false + + filter_status: + description: + - Selected rules status. + required: false + choices: + - disable + - enable + - default + + override: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + override_action: + description: + - Action of override rule. + required: false + choices: + - pass + - block + - reset + + override_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + override_log_packet: + description: + - Enable/disable packet logging. + required: false + choices: + - disable + - enable + + override_quarantine: + description: + - Quarantine IP or interface. + required: false + choices: + - none + - attacker + + override_quarantine_expiry: + description: + - Duration of quarantine in minute. + required: false + + override_quarantine_log: + description: + - Enable/disable logging of selected quarantine. + required: false + choices: + - disable + - enable + + override_rule_id: + description: + - Override rule ID. + required: false + + override_status: + description: + - Enable/disable status of override rule. + required: false + choices: + - disable + - enable + + override_exempt_ip_dst_ip: + description: + - Destination IP address and netmask. + required: false + + override_exempt_ip_src_ip: + description: + - Source IP address and netmask. + required: false +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_ips: + name: "Ansible_IPS_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_ips: + name: "Ansible_IPS_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + block_malicious_url: "enable" + entries: [{severity: "high", action: "block", log-packet: "enable"}, {severity: "medium", action: "pass"}] +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_ips_sensor_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/ips/sensor'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/ips/sensor/{name}'.format( + adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], + type="str", default="add"), + + replacemsg_group=dict(required=False, type="str"), + name=dict(required=False, type="str"), + extended_log=dict(required=False, type="str", + choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + block_malicious_url=dict(required=False, type="str", choices=[ + "disable", "enable"]), + entries=dict(required=False, type="list"), + entries_action=dict(required=False, type="str", choices=[ + "pass", "block", "reset", "default"]), + entries_application=dict(required=False, type="str"), + entries_location=dict(required=False, type="str"), + entries_log=dict(required=False, type="str", + choices=["disable", "enable"]), + entries_log_attack_context=dict( + required=False, type="str", choices=["disable", "enable"]), + entries_log_packet=dict(required=False, type="str", choices=[ + "disable", "enable"]), + entries_os=dict(required=False, type="str"), + entries_protocol=dict(required=False, type="str"), + entries_quarantine=dict(required=False, type="str", choices=[ + "none", "attacker"]), + entries_quarantine_expiry=dict(required=False, type="str"), + entries_quarantine_log=dict( + required=False, type="str", choices=["disable", "enable"]), + entries_rate_count=dict(required=False, type="int"), + entries_rate_duration=dict(required=False, type="int"), + entries_rate_mode=dict(required=False, type="str", choices=[ + "periodical", "continuous"]), + entries_rate_track=dict(required=False, type="str", + choices=["none", "src-ip", "dest-ip", "dhcp-client-mac", "dns-domain"]), + entries_rule=dict(required=False, type="str"), + entries_severity=dict(required=False, type="str"), + entries_status=dict(required=False, type="str", choices=[ + "disable", "enable", "default"]), + + entries_exempt_ip_dst_ip=dict(required=False, type="str"), + entries_exempt_ip_src_ip=dict(required=False, type="str"), + filter=dict(required=False, type="list"), + filter_action=dict(required=False, type="str", choices=[ + "pass", "block", "default", "reset"]), + filter_application=dict(required=False, type="str"), + filter_location=dict(required=False, type="str"), + filter_log=dict(required=False, type="str", + choices=["disable", "enable"]), + filter_log_packet=dict(required=False, type="str", + choices=["disable", "enable"]), + filter_name=dict(required=False, type="str"), + filter_os=dict(required=False, type="str"), + filter_protocol=dict(required=False, type="str"), + filter_quarantine=dict(required=False, type="str", + choices=["none", "attacker"]), + filter_quarantine_expiry=dict(required=False, type="int"), + filter_quarantine_log=dict(required=False, type="str", choices=[ + "disable", "enable"]), + filter_severity=dict(required=False, type="str"), + filter_status=dict(required=False, type="str", choices=[ + "disable", "enable", "default"]), + override=dict(required=False, type="list"), + override_action=dict(required=False, type="str", + choices=["pass", "block", "reset"]), + override_log=dict(required=False, type="str", + choices=["disable", "enable"]), + override_log_packet=dict(required=False, type="str", choices=[ + "disable", "enable"]), + override_quarantine=dict(required=False, type="str", choices=[ + "none", "attacker"]), + override_quarantine_expiry=dict(required=False, type="int"), + override_quarantine_log=dict( + required=False, type="str", choices=["disable", "enable"]), + override_rule_id=dict(required=False, type="str"), + override_status=dict(required=False, type="str", + choices=["disable", "enable"]), + + override_exempt_ip_dst_ip=dict(required=False, type="str"), + override_exempt_ip_src_ip=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "replacemsg-group": module.params["replacemsg_group"], + "name": module.params["name"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "block-malicious-url": module.params["block_malicious_url"], + "entries": { + "action": module.params["entries_action"], + "application": module.params["entries_application"], + "location": module.params["entries_location"], + "log": module.params["entries_log"], + "log-attack-context": module.params["entries_log_attack_context"], + "log-packet": module.params["entries_log_packet"], + "os": module.params["entries_os"], + "protocol": module.params["entries_protocol"], + "quarantine": module.params["entries_quarantine"], + "quarantine-expiry": module.params["entries_quarantine_expiry"], + "quarantine-log": module.params["entries_quarantine_log"], + "rate-count": module.params["entries_rate_count"], + "rate-duration": module.params["entries_rate_duration"], + "rate-mode": module.params["entries_rate_mode"], + "rate-track": module.params["entries_rate_track"], + "rule": module.params["entries_rule"], + "severity": module.params["entries_severity"], + "status": module.params["entries_status"], + "exempt-ip": { + "dst-ip": module.params["entries_exempt_ip_dst_ip"], + "src-ip": module.params["entries_exempt_ip_src_ip"], + }, + }, + "filter": { + "action": module.params["filter_action"], + "application": module.params["filter_application"], + "location": module.params["filter_location"], + "log": module.params["filter_log"], + "log-packet": module.params["filter_log_packet"], + "name": module.params["filter_name"], + "os": module.params["filter_os"], + "protocol": module.params["filter_protocol"], + "quarantine": module.params["filter_quarantine"], + "quarantine-expiry": module.params["filter_quarantine_expiry"], + "quarantine-log": module.params["filter_quarantine_log"], + "severity": module.params["filter_severity"], + "status": module.params["filter_status"], + }, + "override": { + "action": module.params["override_action"], + "log": module.params["override_log"], + "log-packet": module.params["override_log_packet"], + "quarantine": module.params["override_quarantine"], + "quarantine-expiry": module.params["override_quarantine_expiry"], + "quarantine-log": module.params["override_quarantine_log"], + "rule-id": module.params["override_rule_id"], + "status": module.params["override_status"], + "exempt-ip": { + "dst-ip": module.params["override_exempt_ip_dst_ip"], + "src-ip": module.params["override_exempt_ip_src_ip"], + } + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['entries', 'filter', 'override'] + + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_ips_sensor_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_profile_group.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_profile_group.py new file mode 100644 index 00000000..cef9d6d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_profile_group.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_profile_group +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage security profiles within FortiManager +description: + - Manage security profile group which allows you to create a group of security profiles and apply that to a policy. + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values. + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + webfilter_profile: + type: str + description: + - Name of an existing Web filter profile. + required: false + + waf_profile: + type: str + description: + - Name of an existing Web application firewall profile. + required: false + + voip_profile: + type: str + description: + - Name of an existing VoIP profile. + required: false + + ssl_ssh_profile: + type: str + description: + - Name of an existing SSL SSH profile. + required: false + + ssh_filter_profile: + type: str + description: + - Name of an existing SSH filter profile. + required: false + + spamfilter_profile: + type: str + description: + - Name of an existing Spam filter profile. + required: false + + profile_protocol_options: + type: str + description: + - Name of an existing Protocol options profile. + required: false + + name: + type: str + description: + - Profile group name. + required: false + + mms_profile: + type: str + description: + - Name of an existing MMS profile. + required: false + + ips_sensor: + type: str + description: + - Name of an existing IPS sensor. + required: false + + icap_profile: + type: str + description: + - Name of an existing ICAP profile. + required: false + + dnsfilter_profile: + type: str + description: + - Name of an existing DNS filter profile. + required: false + + dlp_sensor: + type: str + description: + - Name of an existing DLP sensor. + required: false + + av_profile: + type: str + description: + - Name of an existing Antivirus profile. + required: false + + application_list: + type: str + description: + - Name of an existing Application list. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_profile_group: + name: "Ansible_TEST_Profile_Group" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_profile_group: + name: "Ansible_TEST_Profile_Group" + mode: "set" + av_profile: "Ansible_AV_Profile" + profile_protocol_options: "default" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_firewall_profile_group_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + url = "" + datagram = {} + + response = DEFAULT_RESULT_OBJ + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/profile-group'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/profile-group/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + webfilter_profile=dict(required=False, type="str"), + waf_profile=dict(required=False, type="str"), + voip_profile=dict(required=False, type="str"), + ssl_ssh_profile=dict(required=False, type="str"), + ssh_filter_profile=dict(required=False, type="str"), + spamfilter_profile=dict(required=False, type="str"), + profile_protocol_options=dict(required=False, type="str"), + name=dict(required=False, type="str"), + mms_profile=dict(required=False, type="str"), + ips_sensor=dict(required=False, type="str"), + icap_profile=dict(required=False, type="str"), + dnsfilter_profile=dict(required=False, type="str"), + dlp_sensor=dict(required=False, type="str"), + av_profile=dict(required=False, type="str"), + application_list=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "webfilter-profile": module.params["webfilter_profile"], + "waf-profile": module.params["waf_profile"], + "voip-profile": module.params["voip_profile"], + "ssl-ssh-profile": module.params["ssl_ssh_profile"], + "ssh-filter-profile": module.params["ssh_filter_profile"], + "spamfilter-profile": module.params["spamfilter_profile"], + "profile-protocol-options": module.params["profile_protocol_options"], + "name": module.params["name"], + "mms-profile": module.params["mms_profile"], + "ips-sensor": module.params["ips_sensor"], + "icap-profile": module.params["icap_profile"], + "dnsfilter-profile": module.params["dnsfilter_profile"], + "dlp-sensor": module.params["dlp_sensor"], + "av-profile": module.params["av_profile"], + "application-list": module.params["application_list"], + + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_firewall_profile_group_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_proxy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_proxy.py new file mode 100644 index 00000000..e7b6473a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_proxy.py @@ -0,0 +1,332 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_proxy +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage proxy security profiles in FortiManager +description: + - Manage proxy security profiles for FortiGates via FortiManager using the FMG API with playbooks + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + strip_encoding: + description: + - Enable/disable stripping unsupported encoding from the request header. + - choice | disable | Disable stripping of unsupported encoding from the request header. + - choice | enable | Enable stripping of unsupported encoding from the request header. + required: false + choices: ["disable", "enable"] + + name: + description: + - Profile name. + required: false + + log_header_change: + description: + - Enable/disable logging HTTP header changes. + - choice | disable | Disable Enable/disable logging HTTP header changes. + - choice | enable | Enable Enable/disable logging HTTP header changes. + required: false + choices: ["disable", "enable"] + + header_x_forwarded_for: + description: + - Action to take on the HTTP x-forwarded-for header in forwarded requests| forwards (pass), adds, or removes the + - HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_x_authenticated_user: + description: + - Action to take on the HTTP x-authenticated-user header in forwarded requests| forwards (pass), adds, or remove + - s the HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_x_authenticated_groups: + description: + - Action to take on the HTTP x-authenticated-groups header in forwarded requests| forwards (pass), adds, or remo + - ves the HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_via_response: + description: + - Action to take on the HTTP via header in forwarded responses| forwards (pass), adds, or removes the HTTP heade + - r. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_via_request: + description: + - Action to take on the HTTP via header in forwarded requests| forwards (pass), adds, or removes the HTTP header + - . + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_front_end_https: + description: + - Action to take on the HTTP front-end-HTTPS header in forwarded requests| forwards (pass), adds, or removes the + - HTTP header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + header_client_ip: + description: + - Actions to take on the HTTP client-IP header in forwarded requests| forwards (pass), adds, or removes the HTTP + - header. + - choice | pass | Forward the same HTTP header. + - choice | add | Add the HTTP header. + - choice | remove | Remove the HTTP header. + required: false + choices: ["pass", "add", "remove"] + + headers: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + headers_action: + description: + - Action when HTTP the header forwarded. + - choice | add-to-request | Add the HTTP header to request. + - choice | add-to-response | Add the HTTP header to response. + - choice | remove-from-request | Remove the HTTP header from request. + - choice | remove-from-response | Remove the HTTP header from response. + required: false + choices: ["add-to-request", "add-to-response", "remove-from-request", "remove-from-response"] + + headers_content: + description: + - HTTP header's content. + required: false + + headers_name: + description: + - HTTP forwarded header name. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_proxy: + name: "Ansible_Web_Proxy_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_proxy: + name: "Ansible_Web_Proxy_Profile" + mode: "set" + header_client_ip: "pass" + header_front_end_https: "add" + header_via_request: "remove" + header_via_response: "pass" + header_x_authenticated_groups: "add" + header_x_authenticated_user: "remove" + strip_encoding: "enable" + log_header_change: "enable" + header_x_forwarded_for: "pass" + headers_action: "add-to-request" + headers_content: "test" + headers_name: "test_header" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_web_proxy_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/web-proxy/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/web-proxy/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + strip_encoding=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + log_header_change=dict(required=False, type="str", choices=["disable", "enable"]), + header_x_forwarded_for=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_x_authenticated_user=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_x_authenticated_groups=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_via_response=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_via_request=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_front_end_https=dict(required=False, type="str", choices=["pass", "add", "remove"]), + header_client_ip=dict(required=False, type="str", choices=["pass", "add", "remove"]), + headers=dict(required=False, type="list"), + headers_action=dict(required=False, type="str", choices=["add-to-request", "add-to-response", + "remove-from-request", "remove-from-response"]), + headers_content=dict(required=False, type="str"), + headers_name=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "strip-encoding": module.params["strip_encoding"], + "name": module.params["name"], + "log-header-change": module.params["log_header_change"], + "header-x-forwarded-for": module.params["header_x_forwarded_for"], + "header-x-authenticated-user": module.params["header_x_authenticated_user"], + "header-x-authenticated-groups": module.params["header_x_authenticated_groups"], + "header-via-response": module.params["header_via_response"], + "header-via-request": module.params["header_via_request"], + "header-front-end-https": module.params["header_front_end_https"], + "header-client-ip": module.params["header_client_ip"], + "headers": { + "action": module.params["headers_action"], + "content": module.params["headers_content"], + "name": module.params["headers_name"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['headers'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + results = fmgr_web_proxy_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_spam.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_spam.py new file mode 100644 index 00000000..c2f9f819 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_spam.py @@ -0,0 +1,607 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_spam +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: spam filter profile for FMG +description: + - Manage spam filter security profiles within FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + spam_rbl_table: + description: + - Anti-spam DNSBL table ID. + required: false + + spam_mheader_table: + description: + - Anti-spam MIME header table ID. + required: false + + spam_log_fortiguard_response: + description: + - Enable/disable logging FortiGuard spam response. + required: false + choices: + - disable + - enable + + spam_log: + description: + - Enable/disable spam logging for email filtering. + required: false + choices: + - disable + - enable + + spam_iptrust_table: + description: + - Anti-spam IP trust table ID. + required: false + + spam_filtering: + description: + - Enable/disable spam filtering. + required: false + choices: + - disable + - enable + + spam_bword_threshold: + description: + - Spam banned word threshold. + required: false + + spam_bword_table: + description: + - Anti-spam banned word table ID. + required: false + + spam_bwl_table: + description: + - Anti-spam black/white list table ID. + required: false + + replacemsg_group: + description: + - Replacement message group. + required: false + + options: + description: + - None + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - bannedword + - spamfsip + - spamfssubmit + - spamfschksum + - spamfsurl + - spamhelodns + - spamraddrdns + - spamrbl + - spamhdrcheck + - spamfsphish + - spambwl + + name: + description: + - Profile name. + required: false + + flow_based: + description: + - Enable/disable flow-based spam filtering. + required: false + choices: + - disable + - enable + + external: + description: + - Enable/disable external Email inspection. + required: false + choices: + - disable + - enable + + comment: + description: + - Comment. + required: false + + gmail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + gmail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + imap: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imap_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + + imap_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + imap_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + imap_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_action: + description: + - Action for spam email. + required: false + choices: + - pass + - discard + + mapi_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + msn_hotmail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + msn_hotmail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + pop3: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + + pop3_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + pop3_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + pop3_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + smtp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtp_action: + description: + - Action for spam email. + required: false + choices: + - pass + - tag + - discard + + smtp_hdrip: + description: + - Enable/disable SMTP email header IP checks for spamfsip, spamrbl and spambwl filters. + required: false + choices: + - disable + - enable + + smtp_local_override: + description: + - Enable/disable local filter to override SMTP remote check result. + required: false + choices: + - disable + - enable + + smtp_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + smtp_tag_msg: + description: + - Subject text or header added to spam email. + required: false + + smtp_tag_type: + description: + - Tag subject or header for spam email. + - FLAG Based Options. Specify multiple in list form. + required: false + choices: + - subject + - header + - spaminfo + + yahoo_mail: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + yahoo_mail_log: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_spam: + name: "Ansible_Spam_Filter_Profile" + mode: "delete" + + - name: Create FMGR_SPAMFILTER_PROFILE + community.network.fmgr_secprof_spam: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + mode: "set" + adom: "root" + spam_log_fortiguard_response: "enable" + spam_iptrust_table: + spam_filtering: "enable" + spam_bword_threshold: 10 + options: ["bannedword", "spamfsip", "spamfsurl", "spamrbl", "spamfsphish", "spambwl"] + name: "Ansible_Spam_Filter_Profile" + flow_based: "enable" + external: "enable" + comment: "Created by Ansible" + gmail_log: "enable" + spam_log: "enable" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_spamfilter_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/spamfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/spamfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + spam_rbl_table=dict(required=False, type="str"), + spam_mheader_table=dict(required=False, type="str"), + spam_log_fortiguard_response=dict(required=False, type="str", choices=["disable", "enable"]), + spam_log=dict(required=False, type="str", choices=["disable", "enable"]), + spam_iptrust_table=dict(required=False, type="str"), + spam_filtering=dict(required=False, type="str", choices=["disable", "enable"]), + spam_bword_threshold=dict(required=False, type="int"), + spam_bword_table=dict(required=False, type="str"), + spam_bwl_table=dict(required=False, type="str"), + replacemsg_group=dict(required=False, type="str"), + options=dict(required=False, type="list", choices=["bannedword", + "spamfsip", + "spamfssubmit", + "spamfschksum", + "spamfsurl", + "spamhelodns", + "spamraddrdns", + "spamrbl", + "spamhdrcheck", + "spamfsphish", + "spambwl"]), + name=dict(required=False, type="str"), + flow_based=dict(required=False, type="str", choices=["disable", "enable"]), + external=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + gmail=dict(required=False, type="dict"), + gmail_log=dict(required=False, type="str", choices=["disable", "enable"]), + imap=dict(required=False, type="dict"), + imap_action=dict(required=False, type="str", choices=["pass", "tag"]), + imap_log=dict(required=False, type="str", choices=["disable", "enable"]), + imap_tag_msg=dict(required=False, type="str"), + imap_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + mapi=dict(required=False, type="dict"), + mapi_action=dict(required=False, type="str", choices=["pass", "discard"]), + mapi_log=dict(required=False, type="str", choices=["disable", "enable"]), + msn_hotmail=dict(required=False, type="dict"), + msn_hotmail_log=dict(required=False, type="str", choices=["disable", "enable"]), + pop3=dict(required=False, type="dict"), + pop3_action=dict(required=False, type="str", choices=["pass", "tag"]), + pop3_log=dict(required=False, type="str", choices=["disable", "enable"]), + pop3_tag_msg=dict(required=False, type="str"), + pop3_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + smtp=dict(required=False, type="dict"), + smtp_action=dict(required=False, type="str", choices=["pass", "tag", "discard"]), + smtp_hdrip=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_local_override=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_log=dict(required=False, type="str", choices=["disable", "enable"]), + smtp_tag_msg=dict(required=False, type="str"), + smtp_tag_type=dict(required=False, type="str", choices=["subject", "header", "spaminfo"]), + yahoo_mail=dict(required=False, type="dict"), + yahoo_mail_log=dict(required=False, type="str", choices=["disable", "enable"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "spam-rbl-table": module.params["spam_rbl_table"], + "spam-mheader-table": module.params["spam_mheader_table"], + "spam-log-fortiguard-response": module.params["spam_log_fortiguard_response"], + "spam-log": module.params["spam_log"], + "spam-iptrust-table": module.params["spam_iptrust_table"], + "spam-filtering": module.params["spam_filtering"], + "spam-bword-threshold": module.params["spam_bword_threshold"], + "spam-bword-table": module.params["spam_bword_table"], + "spam-bwl-table": module.params["spam_bwl_table"], + "replacemsg-group": module.params["replacemsg_group"], + "options": module.params["options"], + "name": module.params["name"], + "flow-based": module.params["flow_based"], + "external": module.params["external"], + "comment": module.params["comment"], + "gmail": { + "log": module.params["gmail_log"], + }, + "imap": { + "action": module.params["imap_action"], + "log": module.params["imap_log"], + "tag-msg": module.params["imap_tag_msg"], + "tag-type": module.params["imap_tag_type"], + }, + "mapi": { + "action": module.params["mapi_action"], + "log": module.params["mapi_log"], + }, + "msn-hotmail": { + "log": module.params["msn_hotmail_log"], + }, + "pop3": { + "action": module.params["pop3_action"], + "log": module.params["pop3_log"], + "tag-msg": module.params["pop3_tag_msg"], + "tag-type": module.params["pop3_tag_type"], + }, + "smtp": { + "action": module.params["smtp_action"], + "hdrip": module.params["smtp_hdrip"], + "local-override": module.params["smtp_local_override"], + "log": module.params["smtp_log"], + "tag-msg": module.params["smtp_tag_msg"], + "tag-type": module.params["smtp_tag_type"], + }, + "yahoo-mail": { + "log": module.params["yahoo_mail_log"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['gmail', 'imap', 'mapi', 'msn-hotmail', 'pop3', 'smtp', 'yahoo-mail'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + try: + + results = fmgr_spamfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ssl_ssh.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ssl_ssh.py new file mode 100644 index 00000000..ec421b5e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_ssl_ssh.py @@ -0,0 +1,954 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_ssl_ssh +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage SSL and SSH security profiles in FortiManager +description: + - Manage SSL and SSH security profiles in FortiManager via the FMG API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + whitelist: + description: + - Enable/disable exempting servers by FortiGuard whitelist. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + use_ssl_server: + description: + - Enable/disable the use of SSL server table for SSL offloading. + - choice | disable | Don't use SSL server configuration. + - choice | enable | Use SSL server configuration. + required: false + choices: ["disable", "enable"] + + untrusted_caname: + description: + - Untrusted CA certificate used by SSL Inspection. + required: false + + ssl_exemptions_log: + description: + - Enable/disable logging SSL exemptions. + - choice | disable | Disable logging SSL exemptions. + - choice | enable | Enable logging SSL exemptions. + required: false + choices: ["disable", "enable"] + + ssl_anomalies_log: + description: + - Enable/disable logging SSL anomalies. + - choice | disable | Disable logging SSL anomalies. + - choice | enable | Enable logging SSL anomalies. + required: false + choices: ["disable", "enable"] + + server_cert_mode: + description: + - Re-sign or replace the server's certificate. + - choice | re-sign | Multiple clients connecting to multiple servers. + - choice | replace | Protect an SSL server. + required: false + choices: ["re-sign", "replace"] + + server_cert: + description: + - Certificate used by SSL Inspection to replace server certificate. + required: false + + rpc_over_https: + description: + - Enable/disable inspection of RPC over HTTPS. + - choice | disable | Disable inspection of RPC over HTTPS. + - choice | enable | Enable inspection of RPC over HTTPS. + required: false + choices: ["disable", "enable"] + + name: + description: + - Name. + required: false + + mapi_over_https: + description: + - Enable/disable inspection of MAPI over HTTPS. + - choice | disable | Disable inspection of MAPI over HTTPS. + - choice | enable | Enable inspection of MAPI over HTTPS. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Optional comments. + required: false + + caname: + description: + - CA certificate used by SSL Inspection. + required: false + + ftps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ftps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ftps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + ftps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ftps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ftps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + https: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + https_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + https_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + https_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + https_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | certificate-inspection | Inspect SSL handshake only. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "certificate-inspection", "deep-inspection"] + + https_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + https_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + imaps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + imaps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + imaps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + imaps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + imaps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + imaps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + imaps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + pop3s: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + pop3s_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + pop3s_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + pop3s_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + pop3s_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + pop3s_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + pop3s_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + smtps: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + smtps_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + smtps_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + smtps_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + smtps_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + smtps_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + smtps_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + ssh: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssh_inspect_all: + description: + - Level of SSL inspection. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ssh_ports: + description: + - Ports to use for scanning (1 - 65535, default = 443). + required: false + + ssh_ssh_algorithm: + description: + - Relative strength of encryption algorithms accepted during negotiation. + - choice | compatible | Allow a broader set of encryption algorithms for best compatibility. + - choice | high-encryption | Allow only AES-CTR, AES-GCM ciphers and high encryption algorithms. + required: false + choices: ["compatible", "high-encryption"] + + ssh_ssh_policy_check: + description: + - Enable/disable SSH policy check. + - choice | disable | Disable SSH policy check. + - choice | enable | Enable SSH policy check. + required: false + choices: ["disable", "enable"] + + ssh_ssh_tun_policy_check: + description: + - Enable/disable SSH tunnel policy check. + - choice | disable | Disable SSH tunnel policy check. + - choice | enable | Enable SSH tunnel policy check. + required: false + choices: ["disable", "enable"] + + ssh_status: + description: + - Configure protocol inspection status. + - choice | disable | Disable. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "deep-inspection"] + + ssh_unsupported_version: + description: + - Action based on SSH version being unsupported. + - choice | block | Block. + - choice | bypass | Bypass. + required: false + choices: ["block", "bypass"] + + ssl: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_allow_invalid_server_cert: + description: + - When enabled, allows SSL sessions whose server certificate validation failed. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ssl_client_cert_request: + description: + - Action based on client certificate request failure. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_inspect_all: + description: + - Level of SSL inspection. + - choice | disable | Disable. + - choice | certificate-inspection | Inspect SSL handshake only. + - choice | deep-inspection | Full SSL inspection. + required: false + choices: ["disable", "certificate-inspection", "deep-inspection"] + + ssl_unsupported_ssl: + description: + - Action based on the SSL encryption used being unsupported. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_untrusted_cert: + description: + - Allow, ignore, or block the untrusted SSL session server certificate. + - choice | allow | Allow the untrusted server certificate. + - choice | block | Block the connection when an untrusted server certificate is detected. + - choice | ignore | Always take the server certificate as trusted. + required: false + choices: ["allow", "block", "ignore"] + + ssl_exempt: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_exempt_address: + description: + - IPv4 address object. + required: false + + ssl_exempt_address6: + description: + - IPv6 address object. + required: false + + ssl_exempt_fortiguard_category: + description: + - FortiGuard category ID. + required: false + + ssl_exempt_regex: + description: + - Exempt servers by regular expression. + required: false + + ssl_exempt_type: + description: + - Type of address object (IPv4 or IPv6) or FortiGuard category. + - choice | fortiguard-category | FortiGuard category. + - choice | address | Firewall IPv4 address. + - choice | address6 | Firewall IPv6 address. + - choice | wildcard-fqdn | Fully Qualified Domain Name with wildcard characters. + - choice | regex | Regular expression FQDN. + required: false + choices: ["fortiguard-category", "address", "address6", "wildcard-fqdn", "regex"] + + ssl_exempt_wildcard_fqdn: + description: + - Exempt servers by wildcard FQDN. + required: false + + ssl_server: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ssl_server_ftps_client_cert_request: + description: + - Action based on client certificate request failure during the FTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_https_client_cert_request: + description: + - Action based on client certificate request failure during the HTTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_imaps_client_cert_request: + description: + - Action based on client certificate request failure during the IMAPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_ip: + description: + - IPv4 address of the SSL server. + required: false + + ssl_server_pop3s_client_cert_request: + description: + - Action based on client certificate request failure during the POP3S handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_smtps_client_cert_request: + description: + - Action based on client certificate request failure during the SMTPS handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + ssl_server_ssl_other_client_cert_request: + description: + - Action based on client certificate request failure during an SSL protocol handshake. + - choice | bypass | Bypass. + - choice | inspect | Inspect. + - choice | block | Block. + required: false + choices: ["bypass", "inspect", "block"] + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_ssl_ssh: + name: Ansible_SSL_SSH_Profile + mode: delete + + - name: CREATE Profile + community.network.fmgr_secprof_ssl_ssh: + name: Ansible_SSL_SSH_Profile + comment: "Created by Ansible Module TEST" + mode: set + mapi_over_https: enable + rpc_over_https: enable + server_cert_mode: replace + ssl_anomalies_log: enable + ssl_exemptions_log: enable + use_ssl_server: enable + whitelist: enable +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + +############### +# START METHODS +############### + + +def fmgr_firewall_ssl_ssh_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/firewall/ssl-ssh-profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/firewall/ssl-ssh-profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + whitelist=dict(required=False, type="str", choices=["disable", "enable"]), + use_ssl_server=dict(required=False, type="str", choices=["disable", "enable"]), + untrusted_caname=dict(required=False, type="str"), + ssl_exemptions_log=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_anomalies_log=dict(required=False, type="str", choices=["disable", "enable"]), + server_cert_mode=dict(required=False, type="str", choices=["re-sign", "replace"]), + server_cert=dict(required=False, type="str"), + rpc_over_https=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + mapi_over_https=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + caname=dict(required=False, type="str"), + ftps=dict(required=False, type="list"), + ftps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + ftps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ftps_ports=dict(required=False, type="str"), + ftps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ftps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ftps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + https=dict(required=False, type="list"), + https_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + https_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + https_ports=dict(required=False, type="str"), + https_status=dict(required=False, type="str", choices=["disable", "certificate-inspection", "deep-inspection"]), + https_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + https_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + imaps=dict(required=False, type="list"), + imaps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + imaps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + imaps_ports=dict(required=False, type="str"), + imaps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + imaps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + imaps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + pop3s=dict(required=False, type="list"), + pop3s_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + pop3s_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + pop3s_ports=dict(required=False, type="str"), + pop3s_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + pop3s_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + pop3s_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + smtps=dict(required=False, type="list"), + smtps_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + smtps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + smtps_ports=dict(required=False, type="str"), + smtps_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + smtps_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + smtps_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + ssh=dict(required=False, type="list"), + ssh_inspect_all=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ssh_ports=dict(required=False, type="str"), + ssh_ssh_algorithm=dict(required=False, type="str", choices=["compatible", "high-encryption"]), + ssh_ssh_policy_check=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_ssh_tun_policy_check=dict(required=False, type="str", choices=["disable", "enable"]), + ssh_status=dict(required=False, type="str", choices=["disable", "deep-inspection"]), + ssh_unsupported_version=dict(required=False, type="str", choices=["block", "bypass"]), + ssl=dict(required=False, type="list"), + ssl_allow_invalid_server_cert=dict(required=False, type="str", choices=["disable", "enable"]), + ssl_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_inspect_all=dict(required=False, type="str", choices=["disable", "certificate-inspection", + "deep-inspection"]), + ssl_unsupported_ssl=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_untrusted_cert=dict(required=False, type="str", choices=["allow", "block", "ignore"]), + ssl_exempt=dict(required=False, type="list"), + ssl_exempt_address=dict(required=False, type="str"), + ssl_exempt_address6=dict(required=False, type="str"), + ssl_exempt_fortiguard_category=dict(required=False, type="str"), + ssl_exempt_regex=dict(required=False, type="str"), + ssl_exempt_type=dict(required=False, type="str", choices=["fortiguard-category", "address", "address6", + "wildcard-fqdn", "regex"]), + ssl_exempt_wildcard_fqdn=dict(required=False, type="str"), + ssl_server=dict(required=False, type="list"), + ssl_server_ftps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_https_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_imaps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_ip=dict(required=False, type="str"), + ssl_server_pop3s_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_smtps_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", "block"]), + ssl_server_ssl_other_client_cert_request=dict(required=False, type="str", choices=["bypass", "inspect", + "block"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "whitelist": module.params["whitelist"], + "use-ssl-server": module.params["use_ssl_server"], + "untrusted-caname": module.params["untrusted_caname"], + "ssl-exemptions-log": module.params["ssl_exemptions_log"], + "ssl-anomalies-log": module.params["ssl_anomalies_log"], + "server-cert-mode": module.params["server_cert_mode"], + "server-cert": module.params["server_cert"], + "rpc-over-https": module.params["rpc_over_https"], + "name": module.params["name"], + "mapi-over-https": module.params["mapi_over_https"], + "comment": module.params["comment"], + "caname": module.params["caname"], + "ftps": { + "allow-invalid-server-cert": module.params["ftps_allow_invalid_server_cert"], + "client-cert-request": module.params["ftps_client_cert_request"], + "ports": module.params["ftps_ports"], + "status": module.params["ftps_status"], + "unsupported-ssl": module.params["ftps_unsupported_ssl"], + "untrusted-cert": module.params["ftps_untrusted_cert"], + }, + "https": { + "allow-invalid-server-cert": module.params["https_allow_invalid_server_cert"], + "client-cert-request": module.params["https_client_cert_request"], + "ports": module.params["https_ports"], + "status": module.params["https_status"], + "unsupported-ssl": module.params["https_unsupported_ssl"], + "untrusted-cert": module.params["https_untrusted_cert"], + }, + "imaps": { + "allow-invalid-server-cert": module.params["imaps_allow_invalid_server_cert"], + "client-cert-request": module.params["imaps_client_cert_request"], + "ports": module.params["imaps_ports"], + "status": module.params["imaps_status"], + "unsupported-ssl": module.params["imaps_unsupported_ssl"], + "untrusted-cert": module.params["imaps_untrusted_cert"], + }, + "pop3s": { + "allow-invalid-server-cert": module.params["pop3s_allow_invalid_server_cert"], + "client-cert-request": module.params["pop3s_client_cert_request"], + "ports": module.params["pop3s_ports"], + "status": module.params["pop3s_status"], + "unsupported-ssl": module.params["pop3s_unsupported_ssl"], + "untrusted-cert": module.params["pop3s_untrusted_cert"], + }, + "smtps": { + "allow-invalid-server-cert": module.params["smtps_allow_invalid_server_cert"], + "client-cert-request": module.params["smtps_client_cert_request"], + "ports": module.params["smtps_ports"], + "status": module.params["smtps_status"], + "unsupported-ssl": module.params["smtps_unsupported_ssl"], + "untrusted-cert": module.params["smtps_untrusted_cert"], + }, + "ssh": { + "inspect-all": module.params["ssh_inspect_all"], + "ports": module.params["ssh_ports"], + "ssh-algorithm": module.params["ssh_ssh_algorithm"], + "ssh-policy-check": module.params["ssh_ssh_policy_check"], + "ssh-tun-policy-check": module.params["ssh_ssh_tun_policy_check"], + "status": module.params["ssh_status"], + "unsupported-version": module.params["ssh_unsupported_version"], + }, + "ssl": { + "allow-invalid-server-cert": module.params["ssl_allow_invalid_server_cert"], + "client-cert-request": module.params["ssl_client_cert_request"], + "inspect-all": module.params["ssl_inspect_all"], + "unsupported-ssl": module.params["ssl_unsupported_ssl"], + "untrusted-cert": module.params["ssl_untrusted_cert"], + }, + "ssl-exempt": { + "address": module.params["ssl_exempt_address"], + "address6": module.params["ssl_exempt_address6"], + "fortiguard-category": module.params["ssl_exempt_fortiguard_category"], + "regex": module.params["ssl_exempt_regex"], + "type": module.params["ssl_exempt_type"], + "wildcard-fqdn": module.params["ssl_exempt_wildcard_fqdn"], + }, + "ssl-server": { + "ftps-client-cert-request": module.params["ssl_server_ftps_client_cert_request"], + "https-client-cert-request": module.params["ssl_server_https_client_cert_request"], + "imaps-client-cert-request": module.params["ssl_server_imaps_client_cert_request"], + "ip": module.params["ssl_server_ip"], + "pop3s-client-cert-request": module.params["ssl_server_pop3s_client_cert_request"], + "smtps-client-cert-request": module.params["ssl_server_smtps_client_cert_request"], + "ssl-other-client-cert-request": module.params["ssl_server_ssl_other_client_cert_request"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['ftps', 'https', 'imaps', 'pop3s', 'smtps', 'ssh', 'ssl', 'ssl-exempt', 'ssl-server'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + + results = fmgr_firewall_ssl_ssh_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_voip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_voip.py new file mode 100644 index 00000000..99ef71e9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_voip.py @@ -0,0 +1,1198 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_voip +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: VOIP security profiles in FMG +description: + - Manage VOIP security profiles in FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + name: + description: + - Profile name. + required: false + + comment: + description: + - Comment. + required: false + + sccp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + sccp_block_mcast: + description: + - Enable/disable block multicast RTP connections. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_log_call_summary: + description: + - Enable/disable log summary of SCCP calls. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_log_violations: + description: + - Enable/disable logging of SCCP violations. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_max_calls: + description: + - Maximum calls per minute per SCCP client (max 65535). + required: false + + sccp_status: + description: + - Enable/disable SCCP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sccp_verify_header: + description: + - Enable/disable verify SCCP header content. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + sip_ack_rate: + description: + - ACK request rate limit (per second, per policy). + required: false + + sip_block_ack: + description: + - Enable/disable block ACK requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_bye: + description: + - Enable/disable block BYE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_cancel: + description: + - Enable/disable block CANCEL requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_geo_red_options: + description: + - Enable/disable block OPTIONS requests, but OPTIONS requests still notify for redundancy. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_info: + description: + - Enable/disable block INFO requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_invite: + description: + - Enable/disable block INVITE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_long_lines: + description: + - Enable/disable block requests with headers exceeding max-line-length. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_message: + description: + - Enable/disable block MESSAGE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_notify: + description: + - Enable/disable block NOTIFY requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_options: + description: + - Enable/disable block OPTIONS requests and no OPTIONS as notifying message for redundancy either. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_prack: + description: + - Enable/disable block prack requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_publish: + description: + - Enable/disable block PUBLISH requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_refer: + description: + - Enable/disable block REFER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_register: + description: + - Enable/disable block REGISTER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_subscribe: + description: + - Enable/disable block SUBSCRIBE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_unknown: + description: + - Block unrecognized SIP requests (enabled by default). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_block_update: + description: + - Enable/disable block UPDATE requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_bye_rate: + description: + - BYE request rate limit (per second, per policy). + required: false + + sip_call_keepalive: + description: + - Continue tracking calls with no RTP for this many minutes. + required: false + + sip_cancel_rate: + description: + - CANCEL request rate limit (per second, per policy). + required: false + + sip_contact_fixup: + description: + - Fixup contact anyway even if contact's IP|port doesn't match session's IP|port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_hnt_restrict_source_ip: + description: + - Enable/disable restrict RTP source IP to be the same as SIP source IP when HNT is enabled. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_hosted_nat_traversal: + description: + - Hosted NAT Traversal (HNT). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_info_rate: + description: + - INFO request rate limit (per second, per policy). + required: false + + sip_invite_rate: + description: + - INVITE request rate limit (per second, per policy). + required: false + + sip_ips_rtp: + description: + - Enable/disable allow IPS on RTP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_log_call_summary: + description: + - Enable/disable logging of SIP call summary. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_log_violations: + description: + - Enable/disable logging of SIP violations. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_malformed_header_allow: + description: + - Action for malformed Allow header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_call_id: + description: + - Action for malformed Call-ID header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_contact: + description: + - Action for malformed Contact header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_content_length: + description: + - Action for malformed Content-Length header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_content_type: + description: + - Action for malformed Content-Type header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_cseq: + description: + - Action for malformed CSeq header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_expires: + description: + - Action for malformed Expires header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_from: + description: + - Action for malformed From header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_max_forwards: + description: + - Action for malformed Max-Forwards header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_p_asserted_identity: + description: + - Action for malformed P-Asserted-Identity header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_rack: + description: + - Action for malformed RAck header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_record_route: + description: + - Action for malformed Record-Route header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_route: + description: + - Action for malformed Route header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_rseq: + description: + - Action for malformed RSeq header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_a: + description: + - Action for malformed SDP a line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_b: + description: + - Action for malformed SDP b line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_c: + description: + - Action for malformed SDP c line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_i: + description: + - Action for malformed SDP i line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_k: + description: + - Action for malformed SDP k line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_m: + description: + - Action for malformed SDP m line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_o: + description: + - Action for malformed SDP o line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_r: + description: + - Action for malformed SDP r line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_s: + description: + - Action for malformed SDP s line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_t: + description: + - Action for malformed SDP t line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_v: + description: + - Action for malformed SDP v line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_sdp_z: + description: + - Action for malformed SDP z line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_to: + description: + - Action for malformed To header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_header_via: + description: + - Action for malformed VIA header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_malformed_request_line: + description: + - Action for malformed request line. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_max_body_length: + description: + - Maximum SIP message body length (0 meaning no limit). + required: false + + sip_max_dialogs: + description: + - Maximum number of concurrent calls/dialogs (per policy). + required: false + + sip_max_idle_dialogs: + description: + - Maximum number established but idle dialogs to retain (per policy). + required: false + + sip_max_line_length: + description: + - Maximum SIP header line length (78-4096). + required: false + + sip_message_rate: + description: + - MESSAGE request rate limit (per second, per policy). + required: false + + sip_nat_trace: + description: + - Enable/disable preservation of original IP in SDP i line. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_no_sdp_fixup: + description: + - Enable/disable no SDP fix-up. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_notify_rate: + description: + - NOTIFY request rate limit (per second, per policy). + required: false + + sip_open_contact_pinhole: + description: + - Enable/disable open pinhole for non-REGISTER Contact port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_record_route_pinhole: + description: + - Enable/disable open pinhole for Record-Route port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_register_pinhole: + description: + - Enable/disable open pinhole for REGISTER Contact port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_open_via_pinhole: + description: + - Enable/disable open pinhole for Via port. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_options_rate: + description: + - OPTIONS request rate limit (per second, per policy). + required: false + + sip_prack_rate: + description: + - PRACK request rate limit (per second, per policy). + required: false + + sip_preserve_override: + description: + - Override i line to preserve original IPS (default| append). + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_provisional_invite_expiry_time: + description: + - Expiry time for provisional INVITE (10 - 3600 sec). + required: false + + sip_publish_rate: + description: + - PUBLISH request rate limit (per second, per policy). + required: false + + sip_refer_rate: + description: + - REFER request rate limit (per second, per policy). + required: false + + sip_register_contact_trace: + description: + - Enable/disable trace original IP/port within the contact header of REGISTER requests. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_register_rate: + description: + - REGISTER request rate limit (per second, per policy). + required: false + + sip_rfc2543_branch: + description: + - Enable/disable support via branch compliant with RFC 2543. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_rtp: + description: + - Enable/disable create pinholes for RTP traffic to traverse firewall. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_ssl_algorithm: + description: + - Relative strength of encryption algorithms accepted in negotiation. + - choice | high | High encryption. Allow only AES and ChaCha. + - choice | medium | Medium encryption. Allow AES, ChaCha, 3DES, and RC4. + - choice | low | Low encryption. Allow AES, ChaCha, 3DES, RC4, and DES. + required: false + choices: ["high", "medium", "low"] + + sip_ssl_auth_client: + description: + - Require a client certificate and authenticate it with the peer/peergrp. + required: false + + sip_ssl_auth_server: + description: + - Authenticate the server's certificate with the peer/peergrp. + required: false + + sip_ssl_client_certificate: + description: + - Name of Certificate to offer to server if requested. + required: false + + sip_ssl_client_renegotiation: + description: + - Allow/block client renegotiation by server. + - choice | allow | Allow a SSL client to renegotiate. + - choice | deny | Abort any SSL connection that attempts to renegotiate. + - choice | secure | Reject any SSL connection that does not offer a RFC 5746 Secure Renegotiation Indication. + required: false + choices: ["allow", "deny", "secure"] + + sip_ssl_max_version: + description: + - Highest SSL/TLS version to negotiate. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + sip_ssl_min_version: + description: + - Lowest SSL/TLS version to negotiate. + - choice | ssl-3.0 | SSL 3.0. + - choice | tls-1.0 | TLS 1.0. + - choice | tls-1.1 | TLS 1.1. + - choice | tls-1.2 | TLS 1.2. + required: false + choices: ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"] + + sip_ssl_mode: + description: + - SSL/TLS mode for encryption & decryption of traffic. + - choice | off | No SSL. + - choice | full | Client to FortiGate and FortiGate to Server SSL. + required: false + choices: ["off", "full"] + + sip_ssl_pfs: + description: + - SSL Perfect Forward Secrecy. + - choice | require | PFS mandatory. + - choice | deny | PFS rejected. + - choice | allow | PFS allowed. + required: false + choices: ["require", "deny", "allow"] + + sip_ssl_send_empty_frags: + description: + - Send empty fragments to avoid attack on CBC IV (SSL 3.0 & TLS 1.0 only). + - choice | disable | Do not send empty fragments. + - choice | enable | Send empty fragments. + required: false + choices: ["disable", "enable"] + + sip_ssl_server_certificate: + description: + - Name of Certificate return to the client in every SSL connection. + required: false + + sip_status: + description: + - Enable/disable SIP. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_strict_register: + description: + - Enable/disable only allow the registrar to connect. + - choice | disable | Disable status. + - choice | enable | Enable status. + required: false + choices: ["disable", "enable"] + + sip_subscribe_rate: + description: + - SUBSCRIBE request rate limit (per second, per policy). + required: false + + sip_unknown_header: + description: + - Action for unknown SIP header. + - choice | pass | Bypass malformed messages. + - choice | discard | Discard malformed messages. + - choice | respond | Respond with error code. + required: false + choices: ["pass", "discard", "respond"] + + sip_update_rate: + description: + - UPDATE request rate limit (per second, per policy). + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_voip: + name: "Ansible_VOIP_Profile" + mode: "delete" + + - name: Create FMGR_VOIP_PROFILE + community.network.fmgr_secprof_voip: + mode: "set" + adom: "root" + name: "Ansible_VOIP_Profile" + comment: "Created by Ansible" + sccp: {block-mcast: "enable", log-call-summary: "enable", log-violations: "enable", status: "enable"} +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_voip_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/voip/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/voip/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + name=dict(required=False, type="str"), + comment=dict(required=False, type="str"), + sccp=dict(required=False, type="dict"), + sccp_block_mcast=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_log_call_summary=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_log_violations=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_max_calls=dict(required=False, type="int"), + sccp_status=dict(required=False, type="str", choices=["disable", "enable"]), + sccp_verify_header=dict(required=False, type="str", choices=["disable", "enable"]), + sip=dict(required=False, type="dict"), + sip_ack_rate=dict(required=False, type="int"), + sip_block_ack=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_bye=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_cancel=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_geo_red_options=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_info=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_invite=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_long_lines=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_message=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_notify=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_options=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_prack=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_publish=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_refer=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_register=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_subscribe=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_unknown=dict(required=False, type="str", choices=["disable", "enable"]), + sip_block_update=dict(required=False, type="str", choices=["disable", "enable"]), + sip_bye_rate=dict(required=False, type="int"), + sip_call_keepalive=dict(required=False, type="int"), + sip_cancel_rate=dict(required=False, type="int"), + sip_contact_fixup=dict(required=False, type="str", choices=["disable", "enable"]), + sip_hnt_restrict_source_ip=dict(required=False, type="str", choices=["disable", "enable"]), + sip_hosted_nat_traversal=dict(required=False, type="str", choices=["disable", "enable"]), + sip_info_rate=dict(required=False, type="int"), + sip_invite_rate=dict(required=False, type="int"), + sip_ips_rtp=dict(required=False, type="str", choices=["disable", "enable"]), + sip_log_call_summary=dict(required=False, type="str", choices=["disable", "enable"]), + sip_log_violations=dict(required=False, type="str", choices=["disable", "enable"]), + sip_malformed_header_allow=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_call_id=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_contact=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_content_length=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_content_type=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_cseq=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_expires=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_from=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_max_forwards=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_p_asserted_identity=dict(required=False, type="str", choices=["pass", + "discard", + "respond"]), + sip_malformed_header_rack=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_record_route=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_route=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_rseq=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_a=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_b=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_c=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_i=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_k=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_m=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_o=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_r=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_s=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_t=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_v=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_sdp_z=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_to=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_header_via=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_malformed_request_line=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_max_body_length=dict(required=False, type="int"), + sip_max_dialogs=dict(required=False, type="int"), + sip_max_idle_dialogs=dict(required=False, type="int"), + sip_max_line_length=dict(required=False, type="int"), + sip_message_rate=dict(required=False, type="int"), + sip_nat_trace=dict(required=False, type="str", choices=["disable", "enable"]), + sip_no_sdp_fixup=dict(required=False, type="str", choices=["disable", "enable"]), + sip_notify_rate=dict(required=False, type="int"), + sip_open_contact_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_record_route_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_register_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_open_via_pinhole=dict(required=False, type="str", choices=["disable", "enable"]), + sip_options_rate=dict(required=False, type="int"), + sip_prack_rate=dict(required=False, type="int"), + sip_preserve_override=dict(required=False, type="str", choices=["disable", "enable"]), + sip_provisional_invite_expiry_time=dict(required=False, type="int"), + sip_publish_rate=dict(required=False, type="int"), + sip_refer_rate=dict(required=False, type="int"), + sip_register_contact_trace=dict(required=False, type="str", choices=["disable", "enable"]), + sip_register_rate=dict(required=False, type="int"), + sip_rfc2543_branch=dict(required=False, type="str", choices=["disable", "enable"]), + sip_rtp=dict(required=False, type="str", choices=["disable", "enable"]), + sip_ssl_algorithm=dict(required=False, type="str", choices=["high", "medium", "low"]), + sip_ssl_auth_client=dict(required=False, type="str"), + sip_ssl_auth_server=dict(required=False, type="str"), + sip_ssl_client_certificate=dict(required=False, type="str"), + sip_ssl_client_renegotiation=dict(required=False, type="str", choices=["allow", "deny", "secure"]), + sip_ssl_max_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + sip_ssl_min_version=dict(required=False, type="str", choices=["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]), + sip_ssl_mode=dict(required=False, type="str", choices=["off", "full"]), + sip_ssl_pfs=dict(required=False, type="str", choices=["require", "deny", "allow"]), + sip_ssl_send_empty_frags=dict(required=False, type="str", choices=["disable", "enable"]), + sip_ssl_server_certificate=dict(required=False, type="str"), + sip_status=dict(required=False, type="str", choices=["disable", "enable"]), + sip_strict_register=dict(required=False, type="str", choices=["disable", "enable"]), + sip_subscribe_rate=dict(required=False, type="int"), + sip_unknown_header=dict(required=False, type="str", choices=["pass", "discard", "respond"]), + sip_update_rate=dict(required=False, type="int"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "name": module.params["name"], + "comment": module.params["comment"], + "sccp": { + "block-mcast": module.params["sccp_block_mcast"], + "log-call-summary": module.params["sccp_log_call_summary"], + "log-violations": module.params["sccp_log_violations"], + "max-calls": module.params["sccp_max_calls"], + "status": module.params["sccp_status"], + "verify-header": module.params["sccp_verify_header"], + }, + "sip": { + "ack-rate": module.params["sip_ack_rate"], + "block-ack": module.params["sip_block_ack"], + "block-bye": module.params["sip_block_bye"], + "block-cancel": module.params["sip_block_cancel"], + "block-geo-red-options": module.params["sip_block_geo_red_options"], + "block-info": module.params["sip_block_info"], + "block-invite": module.params["sip_block_invite"], + "block-long-lines": module.params["sip_block_long_lines"], + "block-message": module.params["sip_block_message"], + "block-notify": module.params["sip_block_notify"], + "block-options": module.params["sip_block_options"], + "block-prack": module.params["sip_block_prack"], + "block-publish": module.params["sip_block_publish"], + "block-refer": module.params["sip_block_refer"], + "block-register": module.params["sip_block_register"], + "block-subscribe": module.params["sip_block_subscribe"], + "block-unknown": module.params["sip_block_unknown"], + "block-update": module.params["sip_block_update"], + "bye-rate": module.params["sip_bye_rate"], + "call-keepalive": module.params["sip_call_keepalive"], + "cancel-rate": module.params["sip_cancel_rate"], + "contact-fixup": module.params["sip_contact_fixup"], + "hnt-restrict-source-ip": module.params["sip_hnt_restrict_source_ip"], + "hosted-nat-traversal": module.params["sip_hosted_nat_traversal"], + "info-rate": module.params["sip_info_rate"], + "invite-rate": module.params["sip_invite_rate"], + "ips-rtp": module.params["sip_ips_rtp"], + "log-call-summary": module.params["sip_log_call_summary"], + "log-violations": module.params["sip_log_violations"], + "malformed-header-allow": module.params["sip_malformed_header_allow"], + "malformed-header-call-id": module.params["sip_malformed_header_call_id"], + "malformed-header-contact": module.params["sip_malformed_header_contact"], + "malformed-header-content-length": module.params["sip_malformed_header_content_length"], + "malformed-header-content-type": module.params["sip_malformed_header_content_type"], + "malformed-header-cseq": module.params["sip_malformed_header_cseq"], + "malformed-header-expires": module.params["sip_malformed_header_expires"], + "malformed-header-from": module.params["sip_malformed_header_from"], + "malformed-header-max-forwards": module.params["sip_malformed_header_max_forwards"], + "malformed-header-p-asserted-identity": module.params["sip_malformed_header_p_asserted_identity"], + "malformed-header-rack": module.params["sip_malformed_header_rack"], + "malformed-header-record-route": module.params["sip_malformed_header_record_route"], + "malformed-header-route": module.params["sip_malformed_header_route"], + "malformed-header-rseq": module.params["sip_malformed_header_rseq"], + "malformed-header-sdp-a": module.params["sip_malformed_header_sdp_a"], + "malformed-header-sdp-b": module.params["sip_malformed_header_sdp_b"], + "malformed-header-sdp-c": module.params["sip_malformed_header_sdp_c"], + "malformed-header-sdp-i": module.params["sip_malformed_header_sdp_i"], + "malformed-header-sdp-k": module.params["sip_malformed_header_sdp_k"], + "malformed-header-sdp-m": module.params["sip_malformed_header_sdp_m"], + "malformed-header-sdp-o": module.params["sip_malformed_header_sdp_o"], + "malformed-header-sdp-r": module.params["sip_malformed_header_sdp_r"], + "malformed-header-sdp-s": module.params["sip_malformed_header_sdp_s"], + "malformed-header-sdp-t": module.params["sip_malformed_header_sdp_t"], + "malformed-header-sdp-v": module.params["sip_malformed_header_sdp_v"], + "malformed-header-sdp-z": module.params["sip_malformed_header_sdp_z"], + "malformed-header-to": module.params["sip_malformed_header_to"], + "malformed-header-via": module.params["sip_malformed_header_via"], + "malformed-request-line": module.params["sip_malformed_request_line"], + "max-body-length": module.params["sip_max_body_length"], + "max-dialogs": module.params["sip_max_dialogs"], + "max-idle-dialogs": module.params["sip_max_idle_dialogs"], + "max-line-length": module.params["sip_max_line_length"], + "message-rate": module.params["sip_message_rate"], + "nat-trace": module.params["sip_nat_trace"], + "no-sdp-fixup": module.params["sip_no_sdp_fixup"], + "notify-rate": module.params["sip_notify_rate"], + "open-contact-pinhole": module.params["sip_open_contact_pinhole"], + "open-record-route-pinhole": module.params["sip_open_record_route_pinhole"], + "open-register-pinhole": module.params["sip_open_register_pinhole"], + "open-via-pinhole": module.params["sip_open_via_pinhole"], + "options-rate": module.params["sip_options_rate"], + "prack-rate": module.params["sip_prack_rate"], + "preserve-override": module.params["sip_preserve_override"], + "provisional-invite-expiry-time": module.params["sip_provisional_invite_expiry_time"], + "publish-rate": module.params["sip_publish_rate"], + "refer-rate": module.params["sip_refer_rate"], + "register-contact-trace": module.params["sip_register_contact_trace"], + "register-rate": module.params["sip_register_rate"], + "rfc2543-branch": module.params["sip_rfc2543_branch"], + "rtp": module.params["sip_rtp"], + "ssl-algorithm": module.params["sip_ssl_algorithm"], + "ssl-auth-client": module.params["sip_ssl_auth_client"], + "ssl-auth-server": module.params["sip_ssl_auth_server"], + "ssl-client-certificate": module.params["sip_ssl_client_certificate"], + "ssl-client-renegotiation": module.params["sip_ssl_client_renegotiation"], + "ssl-max-version": module.params["sip_ssl_max_version"], + "ssl-min-version": module.params["sip_ssl_min_version"], + "ssl-mode": module.params["sip_ssl_mode"], + "ssl-pfs": module.params["sip_ssl_pfs"], + "ssl-send-empty-frags": module.params["sip_ssl_send_empty_frags"], + "ssl-server-certificate": module.params["sip_ssl_server_certificate"], + "status": module.params["sip_status"], + "strict-register": module.params["sip_strict_register"], + "subscribe-rate": module.params["sip_subscribe_rate"], + "unknown-header": module.params["sip_unknown_header"], + "update-rate": module.params["sip_update_rate"], + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['sccp', 'sip'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + module.paramgram = paramgram + + results = DEFAULT_RESULT_OBJ + try: + + results = fmgr_voip_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_waf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_waf.py new file mode 100644 index 00000000..5eaa61f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_waf.py @@ -0,0 +1,1477 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of` +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_waf +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: FortiManager web application firewall security profile +description: + - Manage web application firewall security profiles for FGTs via FMG + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + name: + description: + - WAF Profile name. + required: false + + external: + description: + - Disable/Enable external HTTP Inspection. + - choice | disable | Disable external inspection. + - choice | enable | Enable external inspection. + required: false + choices: ["disable", "enable"] + + extended_log: + description: + - Enable/disable extended logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Comment. + required: false + + address_list: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + address_list_blocked_address: + description: + - Blocked address. + required: false + + address_list_blocked_log: + description: + - Enable/disable logging on blocked addresses. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + address_list_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_trusted_address: + description: + - Trusted address. + required: false + + constraint: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + constraint_content_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_content_length_length: + description: + - Length of HTTP content in bytes (0 to 2147483647). + required: false + + constraint_content_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_content_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_content_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_address: + description: + - Host address. + required: false + + constraint_exception_content_length: + description: + - HTTP content length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_header_length: + description: + - HTTP header length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_hostname: + description: + - Enable/disable hostname check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_line_length: + description: + - HTTP line length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_malformed: + description: + - Enable/disable malformed HTTP request check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_cookie: + description: + - Maximum number of cookies in HTTP request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_header_line: + description: + - Maximum number of HTTP header line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_range_segment: + description: + - Maximum number of range segments in HTTP range line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_url_param: + description: + - Maximum number of parameters in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_method: + description: + - Enable/disable HTTP method check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_param_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_pattern: + description: + - URL pattern. + required: false + + constraint_exception_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_url_param_length: + description: + - Maximum length of parameter in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_version: + description: + - Enable/disable HTTP version check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_header_length_length: + description: + - Length of HTTP header in bytes (0 to 2147483647). + required: false + + constraint_header_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_header_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_action: + description: + - Action for a hostname constraint. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_hostname_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_hostname_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_line_length_length: + description: + - Length of HTTP line in bytes (0 to 2147483647). + required: false + + constraint_line_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_line_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_malformed_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_malformed_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_cookie_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_max_cookie: + description: + - Maximum number of cookies in HTTP request (0 to 2147483647). + required: false + + constraint_max_cookie_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_cookie_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_header_line_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_max_header_line: + description: + - Maximum number HTTP header lines (0 to 2147483647). + required: false + + constraint_max_header_line_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_header_line_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_range_segment_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_max_range_segment: + description: + - Maximum number of range segments in HTTP range line (0 to 2147483647). + required: false + + constraint_max_range_segment_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_range_segment_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_url_param_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_max_url_param: + description: + - Maximum number of parameters in URL (0 to 2147483647). + required: false + + constraint_max_url_param_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_url_param_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_method_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_param_length_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body in bytes (0 to 2147483647). + required: false + + constraint_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_url_param_length_length: + description: + - Maximum length of URL parameter in bytes (0 to 2147483647). + required: false + + constraint_url_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_url_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_version_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_version_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + method_default_allowed_methods: + description: + - Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_severity: + description: + - Severity. + - choice | low | low severity + - choice | medium | medium severity + - choice | high | High severity + required: false + choices: ["low", "medium", "high"] + + method_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_method_policy_address: + description: + - Host address. + required: false + + method_method_policy_allowed_methods: + description: + - Allowed Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_method_policy_pattern: + description: + - URL pattern. + required: false + + method_method_policy_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + signature_credit_card_detection_threshold: + description: + - The minimum number of Credit cards to detect violation. + required: false + + signature_disabled_signature: + description: + - Disabled signatures + required: false + + signature_disabled_sub_class: + description: + - Disabled signature subclasses. + required: false + + signature_custom_signature_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_custom_signature_case_sensitivity: + description: + - Case sensitivity in pattern. + - choice | disable | Case insensitive in pattern. + - choice | enable | Case sensitive in pattern. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_direction: + description: + - Traffic direction. + - choice | request | Match HTTP request. + - choice | response | Match HTTP response. + required: false + choices: ["request", "response"] + + signature_custom_signature_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_name: + description: + - Signature name. + required: false + + signature_custom_signature_pattern: + description: + - Match pattern. + required: false + + signature_custom_signature_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_custom_signature_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_target: + description: + - Match HTTP target. + - FLAG Based Options. Specify multiple in list form. + - flag | arg | HTTP arguments. + - flag | arg-name | Names of HTTP arguments. + - flag | req-body | HTTP request body. + - flag | req-cookie | HTTP request cookies. + - flag | req-cookie-name | HTTP request cookie names. + - flag | req-filename | HTTP request file name. + - flag | req-header | HTTP request headers. + - flag | req-header-name | HTTP request header names. + - flag | req-raw-uri | Raw URI of HTTP request. + - flag | req-uri | URI of HTTP request. + - flag | resp-body | HTTP response body. + - flag | resp-hdr | HTTP response headers. + - flag | resp-status | HTTP response status. + required: false + choices: ["arg","arg-name","req-body","req-cookie","req-cookie-name","req-filename","req-header","req-header-name", + "req-raw-uri","req-uri","resp-body","resp-hdr","resp-status"] + + signature_main_class_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_main_class_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_main_class_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_main_class_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + url_access_action: + description: + - Action. + - choice | bypass | Allow the HTTP request, also bypass further WAF scanning. + - choice | permit | Allow the HTTP request, and continue further WAF scanning. + - choice | block | Block HTTP request. + required: false + choices: ["bypass", "permit", "block"] + + url_access_address: + description: + - Host address. + required: false + + url_access_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + url_access_access_pattern_negate: + description: + - Enable/disable match negation. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_pattern: + description: + - URL pattern. + required: false + + url_access_access_pattern_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_srcaddr: + description: + - Source address. + required: false + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_waf: + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_waf: + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_waf_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/waf/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/waf/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + name=dict(required=False, type="str"), + external=dict(required=False, type="str", choices=["disable", "enable"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + address_list=dict(required=False, type="list"), + address_list_blocked_address=dict(required=False, type="str"), + address_list_blocked_log=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + address_list_status=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_trusted_address=dict(required=False, type="str"), + constraint=dict(required=False, type="list"), + + constraint_content_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_content_length_length=dict(required=False, type="int"), + constraint_content_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_content_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_content_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_exception_address=dict(required=False, type="str"), + constraint_exception_content_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_header_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_hostname=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_line_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_malformed=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_cookie=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_header_line=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_range_segment=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_url_param=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_method=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_pattern=dict(required=False, type="str"), + constraint_exception_regex=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_url_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_version=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_header_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_header_length_length=dict(required=False, type="int"), + constraint_header_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_header_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_header_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_hostname_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_hostname_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_hostname_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_hostname_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_line_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_line_length_length=dict(required=False, type="int"), + constraint_line_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_line_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_line_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_malformed_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_malformed_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_malformed_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_malformed_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_cookie_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_cookie_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_cookie_max_cookie=dict(required=False, type="int"), + constraint_max_cookie_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_cookie_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_header_line_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_header_line_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_header_line_max_header_line=dict(required=False, type="int"), + constraint_max_header_line_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_header_line_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_range_segment_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_range_segment_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_range_segment_max_range_segment=dict(required=False, type="int"), + constraint_max_range_segment_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_range_segment_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_url_param_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_url_param_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_url_param_max_url_param=dict(required=False, type="int"), + constraint_max_url_param_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_url_param_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_method_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_method_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_param_length_length=dict(required=False, type="int"), + constraint_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_url_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_url_param_length_length=dict(required=False, type="int"), + constraint_url_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_url_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_url_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_version_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_version_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_version_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_version_status=dict(required=False, type="str", choices=["disable", "enable"]), + method=dict(required=False, type="list"), + method_default_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_log=dict(required=False, type="str", choices=["disable", "enable"]), + method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + method_method_policy_address=dict(required=False, type="str"), + method_method_policy_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_method_policy_pattern=dict(required=False, type="str"), + method_method_policy_regex=dict(required=False, type="str", choices=["disable", "enable"]), + signature=dict(required=False, type="list"), + signature_credit_card_detection_threshold=dict(required=False, type="int"), + signature_disabled_signature=dict(required=False, type="str"), + signature_disabled_sub_class=dict(required=False, type="str"), + + signature_custom_signature_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_custom_signature_case_sensitivity=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_direction=dict(required=False, type="str", choices=["request", "response"]), + signature_custom_signature_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_name=dict(required=False, type="str"), + signature_custom_signature_pattern=dict(required=False, type="str"), + signature_custom_signature_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_custom_signature_status=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_target=dict(required=False, type="str", choices=["arg", + "arg-name", + "req-body", + "req-cookie", + "req-cookie-name", + "req-filename", + "req-header", + "req-header-name", + "req-raw-uri", + "req-uri", + "resp-body", + "resp-hdr", + "resp-status"]), + + signature_main_class_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_main_class_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_main_class_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_main_class_status=dict(required=False, type="str", choices=["disable", "enable"]), + url_access=dict(required=False, type="list"), + url_access_action=dict(required=False, type="str", choices=["bypass", "permit", "block"]), + url_access_address=dict(required=False, type="str"), + url_access_log=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + + url_access_access_pattern_negate=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_pattern=dict(required=False, type="str"), + url_access_access_pattern_regex=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_srcaddr=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "name": module.params["name"], + "external": module.params["external"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "address-list": { + "blocked-address": module.params["address_list_blocked_address"], + "blocked-log": module.params["address_list_blocked_log"], + "severity": module.params["address_list_severity"], + "status": module.params["address_list_status"], + "trusted-address": module.params["address_list_trusted_address"], + }, + "constraint": { + "content-length": { + "action": module.params["constraint_content_length_action"], + "length": module.params["constraint_content_length_length"], + "log": module.params["constraint_content_length_log"], + "severity": module.params["constraint_content_length_severity"], + "status": module.params["constraint_content_length_status"], + }, + "exception": { + "address": module.params["constraint_exception_address"], + "content-length": module.params["constraint_exception_content_length"], + "header-length": module.params["constraint_exception_header_length"], + "hostname": module.params["constraint_exception_hostname"], + "line-length": module.params["constraint_exception_line_length"], + "malformed": module.params["constraint_exception_malformed"], + "max-cookie": module.params["constraint_exception_max_cookie"], + "max-header-line": module.params["constraint_exception_max_header_line"], + "max-range-segment": module.params["constraint_exception_max_range_segment"], + "max-url-param": module.params["constraint_exception_max_url_param"], + "method": module.params["constraint_exception_method"], + "param-length": module.params["constraint_exception_param_length"], + "pattern": module.params["constraint_exception_pattern"], + "regex": module.params["constraint_exception_regex"], + "url-param-length": module.params["constraint_exception_url_param_length"], + "version": module.params["constraint_exception_version"], + }, + "header-length": { + "action": module.params["constraint_header_length_action"], + "length": module.params["constraint_header_length_length"], + "log": module.params["constraint_header_length_log"], + "severity": module.params["constraint_header_length_severity"], + "status": module.params["constraint_header_length_status"], + }, + "hostname": { + "action": module.params["constraint_hostname_action"], + "log": module.params["constraint_hostname_log"], + "severity": module.params["constraint_hostname_severity"], + "status": module.params["constraint_hostname_status"], + }, + "line-length": { + "action": module.params["constraint_line_length_action"], + "length": module.params["constraint_line_length_length"], + "log": module.params["constraint_line_length_log"], + "severity": module.params["constraint_line_length_severity"], + "status": module.params["constraint_line_length_status"], + }, + "malformed": { + "action": module.params["constraint_malformed_action"], + "log": module.params["constraint_malformed_log"], + "severity": module.params["constraint_malformed_severity"], + "status": module.params["constraint_malformed_status"], + }, + "max-cookie": { + "action": module.params["constraint_max_cookie_action"], + "log": module.params["constraint_max_cookie_log"], + "max-cookie": module.params["constraint_max_cookie_max_cookie"], + "severity": module.params["constraint_max_cookie_severity"], + "status": module.params["constraint_max_cookie_status"], + }, + "max-header-line": { + "action": module.params["constraint_max_header_line_action"], + "log": module.params["constraint_max_header_line_log"], + "max-header-line": module.params["constraint_max_header_line_max_header_line"], + "severity": module.params["constraint_max_header_line_severity"], + "status": module.params["constraint_max_header_line_status"], + }, + "max-range-segment": { + "action": module.params["constraint_max_range_segment_action"], + "log": module.params["constraint_max_range_segment_log"], + "max-range-segment": module.params["constraint_max_range_segment_max_range_segment"], + "severity": module.params["constraint_max_range_segment_severity"], + "status": module.params["constraint_max_range_segment_status"], + }, + "max-url-param": { + "action": module.params["constraint_max_url_param_action"], + "log": module.params["constraint_max_url_param_log"], + "max-url-param": module.params["constraint_max_url_param_max_url_param"], + "severity": module.params["constraint_max_url_param_severity"], + "status": module.params["constraint_max_url_param_status"], + }, + "method": { + "action": module.params["constraint_method_action"], + "log": module.params["constraint_method_log"], + "severity": module.params["constraint_method_severity"], + "status": module.params["constraint_method_status"], + }, + "param-length": { + "action": module.params["constraint_param_length_action"], + "length": module.params["constraint_param_length_length"], + "log": module.params["constraint_param_length_log"], + "severity": module.params["constraint_param_length_severity"], + "status": module.params["constraint_param_length_status"], + }, + "url-param-length": { + "action": module.params["constraint_url_param_length_action"], + "length": module.params["constraint_url_param_length_length"], + "log": module.params["constraint_url_param_length_log"], + "severity": module.params["constraint_url_param_length_severity"], + "status": module.params["constraint_url_param_length_status"], + }, + "version": { + "action": module.params["constraint_version_action"], + "log": module.params["constraint_version_log"], + "severity": module.params["constraint_version_severity"], + "status": module.params["constraint_version_status"], + }, + }, + "method": { + "default-allowed-methods": module.params["method_default_allowed_methods"], + "log": module.params["method_log"], + "severity": module.params["method_severity"], + "status": module.params["method_status"], + "method-policy": { + "address": module.params["method_method_policy_address"], + "allowed-methods": module.params["method_method_policy_allowed_methods"], + "pattern": module.params["method_method_policy_pattern"], + "regex": module.params["method_method_policy_regex"], + }, + }, + "signature": { + "credit-card-detection-threshold": module.params["signature_credit_card_detection_threshold"], + "disabled-signature": module.params["signature_disabled_signature"], + "disabled-sub-class": module.params["signature_disabled_sub_class"], + "custom-signature": { + "action": module.params["signature_custom_signature_action"], + "case-sensitivity": module.params["signature_custom_signature_case_sensitivity"], + "direction": module.params["signature_custom_signature_direction"], + "log": module.params["signature_custom_signature_log"], + "name": module.params["signature_custom_signature_name"], + "pattern": module.params["signature_custom_signature_pattern"], + "severity": module.params["signature_custom_signature_severity"], + "status": module.params["signature_custom_signature_status"], + "target": module.params["signature_custom_signature_target"], + }, + "main-class": { + "action": module.params["signature_main_class_action"], + "log": module.params["signature_main_class_log"], + "severity": module.params["signature_main_class_severity"], + "status": module.params["signature_main_class_status"], + }, + }, + "url-access": { + "action": module.params["url_access_action"], + "address": module.params["url_access_address"], + "log": module.params["url_access_log"], + "severity": module.params["url_access_severity"], + "access-pattern": { + "negate": module.params["url_access_access_pattern_negate"], + "pattern": module.params["url_access_access_pattern_pattern"], + "regex": module.params["url_access_access_pattern_regex"], + "srcaddr": module.params["url_access_access_pattern_srcaddr"], + } + } + } + + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['address-list', 'constraint', 'method', 'signature', 'url-access'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_waf_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_wanopt.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_wanopt.py new file mode 100644 index 00000000..c6b3ca05 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_wanopt.py @@ -0,0 +1,685 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_wanopt +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: WAN optimization +description: + - Manage WanOpt security profiles in FortiManager via API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + transparent: + description: + - Enable/disable transparent mode. + required: false + choices: + - disable + - enable + + name: + description: + - Profile name. + required: false + + comments: + description: + - Comment. + required: false + + auth_group: + description: + - Optionally add an authentication group to restrict access to the WAN Optimization tunnel to + peers in the authentication group. + required: false + + cifs: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + cifs_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + cifs_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + cifs_port: + description: + - Single port number or port number range for CIFS. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + cifs_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + cifs_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + cifs_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + cifs_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + ftp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftp_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + ftp_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + ftp_port: + description: + - Single port number or port number range for FTP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + ftp_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + ftp_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + ftp_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + ftp_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + http: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + http_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + http_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + http_port: + description: + - Single port number or port number range for HTTP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + http_prefer_chunking: + description: + - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + required: false + choices: + - dynamic + - fix + + http_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + http_ssl: + description: + - Enable/disable SSL/TLS offloading (hardware acceleration) for HTTPS traffic in this tunnel. + required: false + choices: + - disable + - enable + + http_ssl_port: + description: + - Port on which to expect HTTPS traffic for SSL/TLS offloading. + required: false + + http_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + http_tunnel_non_http: + description: + - Configure how to process non-HTTP traffic when a profile configured for HTTP traffic accepts + a non-HTTP session. Can occur if an application sends non-HTTP traffic using an HTTP destination port. + required: false + choices: + - disable + - enable + + http_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + http_unknown_http_version: + description: + - How to handle HTTP sessions that do not comply with HTTP 0.9, 1.0, or 1.1. + required: false + choices: + - best-effort + - reject + - tunnel + + mapi: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + mapi_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + mapi_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + mapi_port: + description: + - Single port number or port number range for MAPI. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + mapi_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + mapi_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + mapi_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + + tcp: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + tcp_byte_caching: + description: + - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching + file data sent across the WAN and in future serving if from the cache. + required: false + choices: + - disable + - enable + + tcp_byte_caching_opt: + description: + - Select whether TCP byte-caching uses system memory only or both memory and disk space. + required: false + choices: + - mem-only + - mem-disk + + tcp_log_traffic: + description: + - Enable/disable logging. + required: false + choices: + - disable + - enable + + tcp_port: + description: + - Single port number or port number range for TCP. Only packets with a destination port number + that matches this port number or range are accepted by this profile. + required: false + + tcp_secure_tunnel: + description: + - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the + same TCP port (7810). + required: false + choices: + - disable + - enable + + tcp_ssl: + description: + - Enable/disable SSL/TLS offloading. + required: false + choices: + - disable + - enable + + tcp_ssl_port: + description: + - Port on which to expect HTTPS traffic for SSL/TLS offloading. + required: false + + tcp_status: + description: + - Enable/disable HTTP WAN Optimization. + required: false + choices: + - disable + - enable + + tcp_tunnel_sharing: + description: + - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + required: false + choices: + - private + - shared + - express-shared + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_wanopt: + name: "Ansible_WanOpt_Profile" + mode: "delete" + + - name: Create FMGR_WANOPT_PROFILE + community.network.fmgr_secprof_wanopt: + mode: "set" + adom: "root" + transparent: "enable" + name: "Ansible_WanOpt_Profile" + comments: "Created by Ansible" + cifs: {byte-caching: "enable", + log-traffic: "enable", + port: 80, + prefer-chunking: "dynamic", + status: "enable", + tunnel-sharing: "private"} + ftp: {byte-caching: "enable", + log-traffic: "enable", + port: 80, + prefer-chunking: "dynamic", + secure-tunnel: "disable", + status: "enable", + tunnel-sharing: "private"} +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +############### +# START METHODS +############### + + +def fmgr_wanopt_profile_modify(fmgr, paramgram): + """ + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict + """ + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/wanopt/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/wanopt/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + transparent=dict(required=False, type="str", choices=["disable", "enable"]), + name=dict(required=False, type="str"), + comments=dict(required=False, type="str"), + auth_group=dict(required=False, type="str"), + cifs=dict(required=False, type="dict"), + cifs_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_port=dict(required=False, type="str"), + cifs_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + cifs_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_status=dict(required=False, type="str", choices=["disable", "enable"]), + cifs_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + ftp=dict(required=False, type="dict"), + ftp_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_port=dict(required=False, type="str"), + ftp_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + ftp_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_status=dict(required=False, type="str", choices=["disable", "enable"]), + ftp_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + http=dict(required=False, type="dict"), + http_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + http_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + http_port=dict(required=False, type="str"), + http_prefer_chunking=dict(required=False, type="str", choices=["dynamic", "fix"]), + http_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + http_ssl=dict(required=False, type="str", choices=["disable", "enable"]), + http_ssl_port=dict(required=False, type="str"), + http_status=dict(required=False, type="str", choices=["disable", "enable"]), + http_tunnel_non_http=dict(required=False, type="str", choices=["disable", "enable"]), + http_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + http_unknown_http_version=dict(required=False, type="str", choices=["best-effort", "reject", "tunnel"]), + mapi=dict(required=False, type="dict"), + mapi_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_port=dict(required=False, type="str"), + mapi_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_status=dict(required=False, type="str", choices=["disable", "enable"]), + mapi_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + tcp=dict(required=False, type="dict"), + tcp_byte_caching=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_byte_caching_opt=dict(required=False, type="str", choices=["mem-only", "mem-disk"]), + tcp_log_traffic=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_port=dict(required=False, type="str"), + tcp_secure_tunnel=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_ssl=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_ssl_port=dict(required=False, type="str"), + tcp_status=dict(required=False, type="str", choices=["disable", "enable"]), + tcp_tunnel_sharing=dict(required=False, type="str", choices=["private", "shared", "express-shared"]), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "transparent": module.params["transparent"], + "name": module.params["name"], + "comments": module.params["comments"], + "auth-group": module.params["auth_group"], + "cifs": { + "byte-caching": module.params["cifs_byte_caching"], + "log-traffic": module.params["cifs_log_traffic"], + "port": module.params["cifs_port"], + "prefer-chunking": module.params["cifs_prefer_chunking"], + "secure-tunnel": module.params["cifs_secure_tunnel"], + "status": module.params["cifs_status"], + "tunnel-sharing": module.params["cifs_tunnel_sharing"], + }, + "ftp": { + "byte-caching": module.params["ftp_byte_caching"], + "log-traffic": module.params["ftp_log_traffic"], + "port": module.params["ftp_port"], + "prefer-chunking": module.params["ftp_prefer_chunking"], + "secure-tunnel": module.params["ftp_secure_tunnel"], + "status": module.params["ftp_status"], + "tunnel-sharing": module.params["ftp_tunnel_sharing"], + }, + "http": { + "byte-caching": module.params["http_byte_caching"], + "log-traffic": module.params["http_log_traffic"], + "port": module.params["http_port"], + "prefer-chunking": module.params["http_prefer_chunking"], + "secure-tunnel": module.params["http_secure_tunnel"], + "ssl": module.params["http_ssl"], + "ssl-port": module.params["http_ssl_port"], + "status": module.params["http_status"], + "tunnel-non-http": module.params["http_tunnel_non_http"], + "tunnel-sharing": module.params["http_tunnel_sharing"], + "unknown-http-version": module.params["http_unknown_http_version"], + }, + "mapi": { + "byte-caching": module.params["mapi_byte_caching"], + "log-traffic": module.params["mapi_log_traffic"], + "port": module.params["mapi_port"], + "secure-tunnel": module.params["mapi_secure_tunnel"], + "status": module.params["mapi_status"], + "tunnel-sharing": module.params["mapi_tunnel_sharing"], + }, + "tcp": { + "byte-caching": module.params["tcp_byte_caching"], + "byte-caching-opt": module.params["tcp_byte_caching_opt"], + "log-traffic": module.params["tcp_log_traffic"], + "port": module.params["tcp_port"], + "secure-tunnel": module.params["tcp_secure_tunnel"], + "ssl": module.params["tcp_ssl"], + "ssl-port": module.params["tcp_ssl_port"], + "status": module.params["tcp_status"], + "tunnel-sharing": module.params["tcp_tunnel_sharing"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['cifs', 'ftp', 'http', 'mapi', 'tcp'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + results = fmgr_wanopt_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_web.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_web.py new file mode 100644 index 00000000..cb2432e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/fortimanager/fmgr_secprof_web.py @@ -0,0 +1,1081 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_web +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: Manage web filter security profiles in FortiManager +description: + - Manage web filter security profiles in FortiManager through playbooks using the FMG API + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + youtube_channel_status: + description: + - YouTube channel filter status. + - choice | disable | Disable YouTube channel filter. + - choice | blacklist | Block matches. + - choice | whitelist | Allow matches. + required: false + choices: ["disable", "blacklist", "whitelist"] + + wisp_servers: + description: + - WISP servers. + required: false + + wisp_algorithm: + description: + - WISP server selection algorithm. + - choice | auto-learning | Select the lightest loading healthy server. + - choice | primary-secondary | Select the first healthy server in order. + - choice | round-robin | Select the next healthy server. + required: false + choices: ["auto-learning", "primary-secondary", "round-robin"] + + wisp: + description: + - Enable/disable web proxy WISP. + - choice | disable | Disable web proxy WISP. + - choice | enable | Enable web proxy WISP. + required: false + choices: ["disable", "enable"] + + web_url_log: + description: + - Enable/disable logging URL filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_invalid_domain_log: + description: + - Enable/disable logging invalid domain names. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_ftgd_quota_usage: + description: + - Enable/disable logging daily quota usage. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_ftgd_err_log: + description: + - Enable/disable logging rating errors. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_vbs_log: + description: + - Enable/disable logging VBS scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_unknown_log: + description: + - Enable/disable logging unknown scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_referer_log: + description: + - Enable/disable logging referrers. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_jscript_log: + description: + - Enable/disable logging JScripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_js_log: + description: + - Enable/disable logging Java scripts. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_cookie_removal_log: + description: + - Enable/disable logging blocked cookies. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_cookie_log: + description: + - Enable/disable logging cookie filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_command_block_log: + description: + - Enable/disable logging blocked commands. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_applet_log: + description: + - Enable/disable logging Java applets. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_filter_activex_log: + description: + - Enable/disable logging ActiveX. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_extended_all_action_log: + description: + - Enable/disable extended any filter action logging for web filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_content_log: + description: + - Enable/disable logging logging blocked web content. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + replacemsg_group: + description: + - Replacement message group. + required: false + + post_action: + description: + - Action taken for HTTP POST traffic. + - choice | normal | Normal, POST requests are allowed. + - choice | block | POST requests are blocked. + required: false + choices: ["normal", "block"] + + ovrd_perm: + description: + - FLAG Based Options. Specify multiple in list form. + - flag | bannedword-override | Banned word override. + - flag | urlfilter-override | URL filter override. + - flag | fortiguard-wf-override | FortiGuard Web Filter override. + - flag | contenttype-check-override | Content-type header override. + required: false + choices: + - bannedword-override + - urlfilter-override + - fortiguard-wf-override + - contenttype-check-override + + options: + description: + - FLAG Based Options. Specify multiple in list form. + - flag | block-invalid-url | Block sessions contained an invalid domain name. + - flag | jscript | Javascript block. + - flag | js | JS block. + - flag | vbs | VB script block. + - flag | unknown | Unknown script block. + - flag | wf-referer | Referring block. + - flag | intrinsic | Intrinsic script block. + - flag | wf-cookie | Cookie block. + - flag | per-user-bwl | Per-user black/white list filter + - flag | activexfilter | ActiveX filter. + - flag | cookiefilter | Cookie filter. + - flag | javafilter | Java applet filter. + required: false + choices: + - block-invalid-url + - jscript + - js + - vbs + - unknown + - wf-referer + - intrinsic + - wf-cookie + - per-user-bwl + - activexfilter + - cookiefilter + - javafilter + + name: + description: + - Profile name. + required: false + + log_all_url: + description: + - Enable/disable logging all URLs visited. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + inspection_mode: + description: + - Web filtering inspection mode. + - choice | proxy | Proxy. + - choice | flow-based | Flow based. + required: false + choices: ["proxy", "flow-based"] + + https_replacemsg: + description: + - Enable replacement messages for HTTPS. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + extended_log: + description: + - Enable/disable extended logging for web filtering. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Optional comments. + required: false + + ftgd_wf: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + ftgd_wf_exempt_quota: + description: + - Do not stop quota for these categories. + required: false + + ftgd_wf_max_quota_timeout: + description: + - Maximum FortiGuard quota used by single page view in seconds (excludes streams). + required: false + + ftgd_wf_options: + description: + - Options for FortiGuard Web Filter. + - FLAG Based Options. Specify multiple in list form. + - flag | error-allow | Allow web pages with a rating error to pass through. + - flag | rate-server-ip | Rate the server IP in addition to the domain name. + - flag | connect-request-bypass | Bypass connection which has CONNECT request. + - flag | ftgd-disable | Disable FortiGuard scanning. + required: false + choices: ["error-allow", "rate-server-ip", "connect-request-bypass", "ftgd-disable"] + + ftgd_wf_ovrd: + description: + - Allow web filter profile overrides. + required: false + + ftgd_wf_rate_crl_urls: + description: + - Enable/disable rating CRL by URL. + - choice | disable | Disable rating CRL by URL. + - choice | enable | Enable rating CRL by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_css_urls: + description: + - Enable/disable rating CSS by URL. + - choice | disable | Disable rating CSS by URL. + - choice | enable | Enable rating CSS by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_image_urls: + description: + - Enable/disable rating images by URL. + - choice | disable | Disable rating images by URL (blocked images are replaced with blanks). + - choice | enable | Enable rating images by URL (blocked images are replaced with blanks). + required: false + choices: ["disable", "enable"] + + ftgd_wf_rate_javascript_urls: + description: + - Enable/disable rating JavaScript by URL. + - choice | disable | Disable rating JavaScript by URL. + - choice | enable | Enable rating JavaScript by URL. + required: false + choices: ["disable", "enable"] + + ftgd_wf_filters_action: + description: + - Action to take for matches. + - choice | block | Block access. + - choice | monitor | Allow access while logging the action. + - choice | warning | Allow access after warning the user. + - choice | authenticate | Authenticate user before allowing access. + required: false + choices: ["block", "monitor", "warning", "authenticate"] + + ftgd_wf_filters_auth_usr_grp: + description: + - Groups with permission to authenticate. + required: false + + ftgd_wf_filters_category: + description: + - Categories and groups the filter examines. + required: false + + ftgd_wf_filters_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + ftgd_wf_filters_override_replacemsg: + description: + - Override replacement message. + required: false + + ftgd_wf_filters_warn_duration: + description: + - Duration of warnings. + required: false + + ftgd_wf_filters_warning_duration_type: + description: + - Re-display warning after closing browser or after a timeout. + - choice | session | After session ends. + - choice | timeout | After timeout occurs. + required: false + choices: ["session", "timeout"] + + ftgd_wf_filters_warning_prompt: + description: + - Warning prompts in each category or each domain. + - choice | per-domain | Per-domain warnings. + - choice | per-category | Per-category warnings. + required: false + choices: ["per-domain", "per-category"] + + ftgd_wf_quota_category: + description: + - FortiGuard categories to apply quota to (category action must be set to monitor). + required: false + + ftgd_wf_quota_duration: + description: + - Duration of quota. + required: false + + ftgd_wf_quota_override_replacemsg: + description: + - Override replacement message. + required: false + + ftgd_wf_quota_type: + description: + - Quota type. + - choice | time | Use a time-based quota. + - choice | traffic | Use a traffic-based quota. + required: false + choices: ["time", "traffic"] + + ftgd_wf_quota_unit: + description: + - Traffic quota unit of measurement. + - choice | B | Quota in bytes. + - choice | KB | Quota in kilobytes. + - choice | MB | Quota in megabytes. + - choice | GB | Quota in gigabytes. + required: false + choices: ["B", "KB", "MB", "GB"] + + ftgd_wf_quota_value: + description: + - Traffic quota value. + required: false + + override: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + override_ovrd_cookie: + description: + - Allow/deny browser-based (cookie) overrides. + - choice | deny | Deny browser-based (cookie) override. + - choice | allow | Allow browser-based (cookie) override. + required: false + choices: ["deny", "allow"] + + override_ovrd_dur: + description: + - Override duration. + required: false + + override_ovrd_dur_mode: + description: + - Override duration mode. + - choice | constant | Constant mode. + - choice | ask | Prompt for duration when initiating an override. + required: false + choices: ["constant", "ask"] + + override_ovrd_scope: + description: + - Override scope. + - choice | user | Override for the user. + - choice | user-group | Override for the user's group. + - choice | ip | Override for the initiating IP. + - choice | ask | Prompt for scope when initiating an override. + - choice | browser | Create browser-based (cookie) override. + required: false + choices: ["user", "user-group", "ip", "ask", "browser"] + + override_ovrd_user_group: + description: + - User groups with permission to use the override. + required: false + + override_profile: + description: + - Web filter profile with permission to create overrides. + required: false + + override_profile_attribute: + description: + - Profile attribute to retrieve from the RADIUS server. + - choice | User-Name | Use this attribute. + - choice | NAS-IP-Address | Use this attribute. + - choice | Framed-IP-Address | Use this attribute. + - choice | Framed-IP-Netmask | Use this attribute. + - choice | Filter-Id | Use this attribute. + - choice | Login-IP-Host | Use this attribute. + - choice | Reply-Message | Use this attribute. + - choice | Callback-Number | Use this attribute. + - choice | Callback-Id | Use this attribute. + - choice | Framed-Route | Use this attribute. + - choice | Framed-IPX-Network | Use this attribute. + - choice | Class | Use this attribute. + - choice | Called-Station-Id | Use this attribute. + - choice | Calling-Station-Id | Use this attribute. + - choice | NAS-Identifier | Use this attribute. + - choice | Proxy-State | Use this attribute. + - choice | Login-LAT-Service | Use this attribute. + - choice | Login-LAT-Node | Use this attribute. + - choice | Login-LAT-Group | Use this attribute. + - choice | Framed-AppleTalk-Zone | Use this attribute. + - choice | Acct-Session-Id | Use this attribute. + - choice | Acct-Multi-Session-Id | Use this attribute. + required: false + choices: + - User-Name + - NAS-IP-Address + - Framed-IP-Address + - Framed-IP-Netmask + - Filter-Id + - Login-IP-Host + - Reply-Message + - Callback-Number + - Callback-Id + - Framed-Route + - Framed-IPX-Network + - Class + - Called-Station-Id + - Calling-Station-Id + - NAS-Identifier + - Proxy-State + - Login-LAT-Service + - Login-LAT-Node + - Login-LAT-Group + - Framed-AppleTalk-Zone + - Acct-Session-Id + - Acct-Multi-Session-Id + + override_profile_type: + description: + - Override profile type. + - choice | list | Profile chosen from list. + - choice | radius | Profile determined by RADIUS server. + required: false + choices: ["list", "radius"] + + url_extraction: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + url_extraction_redirect_header: + description: + - HTTP header name to use for client redirect on blocked requests + required: false + + url_extraction_redirect_no_content: + description: + - Enable / Disable empty message-body entity in HTTP response + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_extraction_redirect_url: + description: + - HTTP header value to use for client redirect on blocked requests + required: false + + url_extraction_server_fqdn: + description: + - URL extraction server FQDN (fully qualified domain name) + required: false + + url_extraction_status: + description: + - Enable URL Extraction + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + web_blacklist: + description: + - Enable/disable automatic addition of URLs detected by FortiSandbox to blacklist. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_bword_table: + description: + - Banned word table ID. + required: false + + web_bword_threshold: + description: + - Banned word score threshold. + required: false + + web_content_header_list: + description: + - Content header list. + required: false + + web_keyword_match: + description: + - Search keywords to log when match is found. + required: false + + web_log_search: + description: + - Enable/disable logging all search phrases. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + web_safe_search: + description: + - Safe search type. + - FLAG Based Options. Specify multiple in list form. + - flag | url | Insert safe search string into URL. + - flag | header | Insert safe search header. + required: false + choices: ["url", "header"] + + web_urlfilter_table: + description: + - URL filter table ID. + required: false + + web_whitelist: + description: + - FortiGuard whitelist settings. + - FLAG Based Options. Specify multiple in list form. + - flag | exempt-av | Exempt antivirus. + - flag | exempt-webcontent | Exempt web content. + - flag | exempt-activex-java-cookie | Exempt ActiveX-JAVA-Cookie. + - flag | exempt-dlp | Exempt DLP. + - flag | exempt-rangeblock | Exempt RangeBlock. + - flag | extended-log-others | Support extended log. + required: false + choices: + - exempt-av + - exempt-webcontent + - exempt-activex-java-cookie + - exempt-dlp + - exempt-rangeblock + - extended-log-others + + web_youtube_restrict: + description: + - YouTube EDU filter level. + - choice | strict | Strict access for YouTube. + - choice | none | Full access for YouTube. + - choice | moderate | Moderate access for YouTube. + required: false + choices: ["strict", "none", "moderate"] + + youtube_channel_filter: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + youtube_channel_filter_channel_id: + description: + - YouTube channel ID to be filtered. + required: false + + youtube_channel_filter_comment: + description: + - Comment. + required: false + + +''' + +EXAMPLES = ''' + - name: DELETE Profile + community.network.fmgr_secprof_web: + name: "Ansible_Web_Filter_Profile" + mode: "delete" + + - name: CREATE Profile + community.network.fmgr_secprof_web: + name: "Ansible_Web_Filter_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" + extended_log: "enable" + inspection_mode: "proxy" + log_all_url: "enable" + options: "js" + ovrd_perm: "bannedword-override" + post_action: "block" + web_content_log: "enable" + web_extended_all_action_log: "enable" + web_filter_activex_log: "enable" + web_filter_applet_log: "enable" + web_filter_command_block_log: "enable" + web_filter_cookie_log: "enable" + web_filter_cookie_removal_log: "enable" + web_filter_js_log: "enable" + web_filter_jscript_log: "enable" + web_filter_referer_log: "enable" + web_filter_unknown_log: "enable" + web_filter_vbs_log: "enable" + web_ftgd_err_log: "enable" + web_ftgd_quota_usage: "enable" + web_invalid_domain_log: "enable" + web_url_log: "enable" + wisp: "enable" + wisp_algorithm: "auto-learning" + youtube_channel_status: "blacklist" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: str +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.fortimanager import FortiManagerHandler +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGBaseException +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRCommon +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FMGRMethods +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import prepare_dict +from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import scrub_dict + + +def fmgr_webfilter_profile_modify(fmgr, paramgram): + + mode = paramgram["mode"] + adom = paramgram["adom"] + + response = DEFAULT_RESULT_OBJ + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/webfilter/profile'.format(adom=adom) + datagram = scrub_dict(prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/webfilter/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + response = fmgr.process_request(url, datagram, paramgram["mode"]) + + return response + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + youtube_channel_status=dict(required=False, type="str", choices=["disable", "blacklist", "whitelist"]), + wisp_servers=dict(required=False, type="str"), + wisp_algorithm=dict(required=False, type="str", choices=["auto-learning", "primary-secondary", "round-robin"]), + wisp=dict(required=False, type="str", choices=["disable", "enable"]), + web_url_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_invalid_domain_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_ftgd_quota_usage=dict(required=False, type="str", choices=["disable", "enable"]), + web_ftgd_err_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_vbs_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_unknown_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_referer_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_jscript_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_js_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_cookie_removal_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_cookie_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_command_block_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_applet_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_filter_activex_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_extended_all_action_log=dict(required=False, type="str", choices=["disable", "enable"]), + web_content_log=dict(required=False, type="str", choices=["disable", "enable"]), + replacemsg_group=dict(required=False, type="str"), + post_action=dict(required=False, type="str", choices=["normal", "block"]), + ovrd_perm=dict(required=False, type="list", choices=["bannedword-override", + "urlfilter-override", + "fortiguard-wf-override", + "contenttype-check-override"]), + options=dict(required=False, type="list", choices=["block-invalid-url", + "jscript", + "js", + "vbs", + "unknown", + "wf-referer", + "intrinsic", + "wf-cookie", + "per-user-bwl", + "activexfilter", + "cookiefilter", + "javafilter"]), + name=dict(required=False, type="str"), + log_all_url=dict(required=False, type="str", choices=["disable", "enable"]), + inspection_mode=dict(required=False, type="str", choices=["proxy", "flow-based"]), + https_replacemsg=dict(required=False, type="str", choices=["disable", "enable"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + ftgd_wf=dict(required=False, type="list"), + ftgd_wf_exempt_quota=dict(required=False, type="str"), + ftgd_wf_max_quota_timeout=dict(required=False, type="int"), + ftgd_wf_options=dict(required=False, type="str", choices=["error-allow", "rate-server-ip", + "connect-request-bypass", "ftgd-disable"]), + ftgd_wf_ovrd=dict(required=False, type="str"), + ftgd_wf_rate_crl_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_css_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_image_urls=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_rate_javascript_urls=dict(required=False, type="str", choices=["disable", "enable"]), + + ftgd_wf_filters_action=dict(required=False, type="str", choices=["block", "monitor", + "warning", "authenticate"]), + ftgd_wf_filters_auth_usr_grp=dict(required=False, type="str"), + ftgd_wf_filters_category=dict(required=False, type="str"), + ftgd_wf_filters_log=dict(required=False, type="str", choices=["disable", "enable"]), + ftgd_wf_filters_override_replacemsg=dict(required=False, type="str"), + ftgd_wf_filters_warn_duration=dict(required=False, type="str"), + ftgd_wf_filters_warning_duration_type=dict(required=False, type="str", choices=["session", "timeout"]), + ftgd_wf_filters_warning_prompt=dict(required=False, type="str", choices=["per-domain", "per-category"]), + + ftgd_wf_quota_category=dict(required=False, type="str"), + ftgd_wf_quota_duration=dict(required=False, type="str"), + ftgd_wf_quota_override_replacemsg=dict(required=False, type="str"), + ftgd_wf_quota_type=dict(required=False, type="str", choices=["time", "traffic"]), + ftgd_wf_quota_unit=dict(required=False, type="str", choices=["B", "KB", "MB", "GB"]), + ftgd_wf_quota_value=dict(required=False, type="int"), + override=dict(required=False, type="list"), + override_ovrd_cookie=dict(required=False, type="str", choices=["deny", "allow"]), + override_ovrd_dur=dict(required=False, type="str"), + override_ovrd_dur_mode=dict(required=False, type="str", choices=["constant", "ask"]), + override_ovrd_scope=dict(required=False, type="str", choices=["user", "user-group", "ip", "ask", "browser"]), + override_ovrd_user_group=dict(required=False, type="str"), + override_profile=dict(required=False, type="str"), + override_profile_attribute=dict(required=False, type="list", choices=["User-Name", + "NAS-IP-Address", + "Framed-IP-Address", + "Framed-IP-Netmask", + "Filter-Id", + "Login-IP-Host", + "Reply-Message", + "Callback-Number", + "Callback-Id", + "Framed-Route", + "Framed-IPX-Network", + "Class", + "Called-Station-Id", + "Calling-Station-Id", + "NAS-Identifier", + "Proxy-State", + "Login-LAT-Service", + "Login-LAT-Node", + "Login-LAT-Group", + "Framed-AppleTalk-Zone", + "Acct-Session-Id", + "Acct-Multi-Session-Id"]), + override_profile_type=dict(required=False, type="str", choices=["list", "radius"]), + url_extraction=dict(required=False, type="list"), + url_extraction_redirect_header=dict(required=False, type="str"), + url_extraction_redirect_no_content=dict(required=False, type="str", choices=["disable", "enable"]), + url_extraction_redirect_url=dict(required=False, type="str"), + url_extraction_server_fqdn=dict(required=False, type="str"), + url_extraction_status=dict(required=False, type="str", choices=["disable", "enable"]), + web=dict(required=False, type="list"), + web_blacklist=dict(required=False, type="str", choices=["disable", "enable"]), + web_bword_table=dict(required=False, type="str"), + web_bword_threshold=dict(required=False, type="int"), + web_content_header_list=dict(required=False, type="str"), + web_keyword_match=dict(required=False, type="str"), + web_log_search=dict(required=False, type="str", choices=["disable", "enable"]), + web_safe_search=dict(required=False, type="str", choices=["url", "header"]), + web_urlfilter_table=dict(required=False, type="str"), + web_whitelist=dict(required=False, type="list", choices=["exempt-av", + "exempt-webcontent", + "exempt-activex-java-cookie", + "exempt-dlp", + "exempt-rangeblock", + "extended-log-others"]), + web_youtube_restrict=dict(required=False, type="str", choices=["strict", "none", "moderate"]), + youtube_channel_filter=dict(required=False, type="list"), + youtube_channel_filter_channel_id=dict(required=False, type="str"), + youtube_channel_filter_comment=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "youtube-channel-status": module.params["youtube_channel_status"], + "wisp-servers": module.params["wisp_servers"], + "wisp-algorithm": module.params["wisp_algorithm"], + "wisp": module.params["wisp"], + "web-url-log": module.params["web_url_log"], + "web-invalid-domain-log": module.params["web_invalid_domain_log"], + "web-ftgd-quota-usage": module.params["web_ftgd_quota_usage"], + "web-ftgd-err-log": module.params["web_ftgd_err_log"], + "web-filter-vbs-log": module.params["web_filter_vbs_log"], + "web-filter-unknown-log": module.params["web_filter_unknown_log"], + "web-filter-referer-log": module.params["web_filter_referer_log"], + "web-filter-jscript-log": module.params["web_filter_jscript_log"], + "web-filter-js-log": module.params["web_filter_js_log"], + "web-filter-cookie-removal-log": module.params["web_filter_cookie_removal_log"], + "web-filter-cookie-log": module.params["web_filter_cookie_log"], + "web-filter-command-block-log": module.params["web_filter_command_block_log"], + "web-filter-applet-log": module.params["web_filter_applet_log"], + "web-filter-activex-log": module.params["web_filter_activex_log"], + "web-extended-all-action-log": module.params["web_extended_all_action_log"], + "web-content-log": module.params["web_content_log"], + "replacemsg-group": module.params["replacemsg_group"], + "post-action": module.params["post_action"], + "ovrd-perm": module.params["ovrd_perm"], + "options": module.params["options"], + "name": module.params["name"], + "log-all-url": module.params["log_all_url"], + "inspection-mode": module.params["inspection_mode"], + "https-replacemsg": module.params["https_replacemsg"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "ftgd-wf": { + "exempt-quota": module.params["ftgd_wf_exempt_quota"], + "max-quota-timeout": module.params["ftgd_wf_max_quota_timeout"], + "options": module.params["ftgd_wf_options"], + "ovrd": module.params["ftgd_wf_ovrd"], + "rate-crl-urls": module.params["ftgd_wf_rate_crl_urls"], + "rate-css-urls": module.params["ftgd_wf_rate_css_urls"], + "rate-image-urls": module.params["ftgd_wf_rate_image_urls"], + "rate-javascript-urls": module.params["ftgd_wf_rate_javascript_urls"], + "filters": { + "action": module.params["ftgd_wf_filters_action"], + "auth-usr-grp": module.params["ftgd_wf_filters_auth_usr_grp"], + "category": module.params["ftgd_wf_filters_category"], + "log": module.params["ftgd_wf_filters_log"], + "override-replacemsg": module.params["ftgd_wf_filters_override_replacemsg"], + "warn-duration": module.params["ftgd_wf_filters_warn_duration"], + "warning-duration-type": module.params["ftgd_wf_filters_warning_duration_type"], + "warning-prompt": module.params["ftgd_wf_filters_warning_prompt"], + }, + "quota": { + "category": module.params["ftgd_wf_quota_category"], + "duration": module.params["ftgd_wf_quota_duration"], + "override-replacemsg": module.params["ftgd_wf_quota_override_replacemsg"], + "type": module.params["ftgd_wf_quota_type"], + "unit": module.params["ftgd_wf_quota_unit"], + "value": module.params["ftgd_wf_quota_value"], + }, + }, + "override": { + "ovrd-cookie": module.params["override_ovrd_cookie"], + "ovrd-dur": module.params["override_ovrd_dur"], + "ovrd-dur-mode": module.params["override_ovrd_dur_mode"], + "ovrd-scope": module.params["override_ovrd_scope"], + "ovrd-user-group": module.params["override_ovrd_user_group"], + "profile": module.params["override_profile"], + "profile-attribute": module.params["override_profile_attribute"], + "profile-type": module.params["override_profile_type"], + }, + "url-extraction": { + "redirect-header": module.params["url_extraction_redirect_header"], + "redirect-no-content": module.params["url_extraction_redirect_no_content"], + "redirect-url": module.params["url_extraction_redirect_url"], + "server-fqdn": module.params["url_extraction_server_fqdn"], + "status": module.params["url_extraction_status"], + }, + "web": { + "blacklist": module.params["web_blacklist"], + "bword-table": module.params["web_bword_table"], + "bword-threshold": module.params["web_bword_threshold"], + "content-header-list": module.params["web_content_header_list"], + "keyword-match": module.params["web_keyword_match"], + "log-search": module.params["web_log_search"], + "safe-search": module.params["web_safe_search"], + "urlfilter-table": module.params["web_urlfilter_table"], + "whitelist": module.params["web_whitelist"], + "youtube-restrict": module.params["web_youtube_restrict"], + }, + "youtube-channel-filter": { + "channel-id": module.params["youtube_channel_filter_channel_id"], + "comment": module.params["youtube_channel_filter_comment"], + } + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() + else: + module.fail_json(**FAIL_SOCKET_MSG) + + list_overrides = ['ftgd-wf', 'override', 'url-extraction', 'web', 'youtube-channel-filter'] + paramgram = fmgr.tools.paramgram_child_list_override(list_overrides=list_overrides, + paramgram=paramgram, module=module) + + results = DEFAULT_RESULT_OBJ + + try: + + results = fmgr_webfilter_profile_modify(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, + ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram)) + + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_configuration.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_configuration.py new file mode 100644 index 00000000..322f8e3e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_configuration.py @@ -0,0 +1,135 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ftd_configuration +short_description: Manages configuration on Cisco FTD devices over REST API +description: + - Manages configuration on Cisco FTD devices including creating, updating, removing configuration objects, + scheduling and staring jobs, deploying pending changes, etc. All operations are performed over REST API. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. Commonly, the operation starts with 'add', 'edit', 'get', 'upsert' + or 'delete' verbs, but can have an arbitrary name too. + required: true + type: str + data: + description: + - Key-value pairs that should be sent as body parameters in a REST API call + type: dict + query_params: + description: + - Key-value pairs that should be sent as query parameters in a REST API call. + type: dict + path_params: + description: + - Key-value pairs that should be sent as path parameters in a REST API call. + type: dict + register_as: + description: + - Specifies Ansible fact name that is used to register received response from the FTD device. + type: str + filters: + description: + - Key-value dict that represents equality filters. Every key is a property name and value is its desired value. + If multiple filters are present, they are combined with logical operator AND. + type: dict +''' + +EXAMPLES = """ +- name: Create a network object + community.network.ftd_configuration: + operation: "addNetworkObject" + data: + name: "Ansible-network-host" + description: "From Ansible with love" + subType: "HOST" + value: "192.168.2.0" + dnsResolution: "IPV4_AND_IPV6" + type: "networkobject" + isSystemDefined: false + register_as: "hostNetwork" + +- name: Delete the network object + community.network.ftd_configuration: + operation: "deleteNetworkObject" + path_params: + objId: "{{ hostNetwork['id'] }}" +""" + +RETURN = """ +response: + description: HTTP response returned from the API call. + returned: success + type: dict +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.configuration import BaseConfigurationResource, CheckModeException, \ + FtdInvalidOperationNameError +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import ValidationError +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import construct_ansible_facts, FtdConfigurationError, \ + FtdServerError, FtdUnexpectedResponse + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + data=dict(type='dict'), + query_params=dict(type='dict'), + path_params=dict(type='dict'), + register_as=dict(type='str'), + filters=dict(type='dict') + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + + connection = Connection(module._socket_path) + resource = BaseConfigurationResource(connection, module.check_mode) + op_name = params['operation'] + try: + resp = resource.execute_operation(op_name, params) + module.exit_json(changed=resource.config_changed, response=resp, + ansible_facts=construct_ansible_facts(resp, module.params)) + except FtdInvalidOperationNameError as e: + module.fail_json(msg='Invalid operation name provided: %s' % e.operation_name) + except FtdConfigurationError as e: + module.fail_json(msg='Failed to execute %s operation because of the configuration error: %s' % (op_name, e.msg)) + except FtdServerError as e: + module.fail_json(msg='Server returned an error trying to execute %s operation. Status code: %s. ' + 'Server response: %s' % (op_name, e.code, e.response)) + except FtdUnexpectedResponse as e: + module.fail_json(msg=e.args[0]) + except ValidationError as e: + module.fail_json(msg=e.args[0]) + except CheckModeException: + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_download.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_download.py new file mode 100644 index 00000000..78fbdbc0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_download.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ftd_file_download +short_description: Downloads files from Cisco FTD devices over HTTP(S) +description: + - Downloads files from Cisco FTD devices including pending changes, disk files, certificates, + troubleshoot reports, and backups. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. + - Only operations that return a file can be used in this module. + required: true + type: str + path_params: + description: + - Key-value pairs that should be sent as path parameters in a REST API call. + type: dict + destination: + description: + - Absolute path of where to download the file to. + - If destination is a directory, the module uses a filename from 'Content-Disposition' header specified by + the server. + required: true + type: path +''' + +EXAMPLES = """ +- name: Download pending changes + community.network.ftd_file_download: + operation: 'getdownload' + path_params: + objId: 'default' + destination: /tmp/ +""" + +RETURN = """ +msg: + description: The error message describing why the module failed. + returned: error + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import FtdServerError, HTTPMethod +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import OperationField, ValidationError, FILE_MODEL_NAME + + +def is_download_operation(op_spec): + return op_spec[OperationField.METHOD] == HTTPMethod.GET and op_spec[OperationField.MODEL_NAME] == FILE_MODEL_NAME + + +def validate_params(connection, op_name, path_params): + field_name = 'Invalid path_params provided' + try: + is_valid, validation_report = connection.validate_path_params(op_name, path_params) + if not is_valid: + raise ValidationError({ + field_name: validation_report + }) + except Exception as e: + raise ValidationError({ + field_name: str(e) + }) + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + path_params=dict(type='dict'), + destination=dict(type='path', required=True) + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + connection = Connection(module._socket_path) + + op_name = params['operation'] + op_spec = connection.get_operation_spec(op_name) + if op_spec is None: + module.fail_json(msg='Operation with specified name is not found: %s' % op_name) + if not is_download_operation(op_spec): + module.fail_json( + msg='Invalid download operation: %s. The operation must make GET request and return a file.' % + op_name) + + try: + path_params = params['path_params'] + validate_params(connection, op_name, path_params) + if module.check_mode: + module.exit_json(changed=False) + connection.download_file(op_spec[OperationField.URL], params['destination'], path_params) + module.exit_json(changed=False) + except FtdServerError as e: + module.fail_json(msg='Download request for %s operation failed. Status code: %s. ' + 'Server response: %s' % (op_name, e.code, e.response)) + except ValidationError as e: + module.fail_json(msg=e.args[0]) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_upload.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_upload.py new file mode 100644 index 00000000..c9b124f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_file_upload.py @@ -0,0 +1,103 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ftd_file_upload +short_description: Uploads files to Cisco FTD devices over HTTP(S) +description: + - Uploads files to Cisco FTD devices including disk files, backups, and upgrades. +author: "Cisco Systems, Inc. (@annikulin)" +options: + operation: + description: + - The name of the operation to execute. + - Only operations that upload file can be used in this module. + required: true + type: str + file_to_upload: + description: + - Absolute path to the file that should be uploaded. + required: true + type: path + register_as: + description: + - Specifies Ansible fact name that is used to register received response from the FTD device. + type: str +''' + +EXAMPLES = """ +- name: Upload disk file + community.network.ftd_file_upload: + operation: 'postuploaddiskfile' + file_to_upload: /tmp/test1.txt +""" + +RETURN = """ +msg: + description: The error message describing why the module failed. + returned: error + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.community.network.plugins.module_utils.network.ftd.common import construct_ansible_facts, FtdServerError, HTTPMethod +from ansible_collections.community.network.plugins.module_utils.network.ftd.fdm_swagger_client import OperationField + + +def is_upload_operation(op_spec): + return op_spec[OperationField.METHOD] == HTTPMethod.POST or 'UploadStatus' in op_spec[OperationField.MODEL_NAME] + + +def main(): + fields = dict( + operation=dict(type='str', required=True), + file_to_upload=dict(type='path', required=True), + register_as=dict(type='str'), + ) + module = AnsibleModule(argument_spec=fields, + supports_check_mode=True) + params = module.params + connection = Connection(module._socket_path) + + op_spec = connection.get_operation_spec(params['operation']) + if op_spec is None: + module.fail_json(msg='Operation with specified name is not found: %s' % params['operation']) + if not is_upload_operation(op_spec): + module.fail_json( + msg='Invalid upload operation: %s. The operation must make POST request and return UploadStatus model.' % + params['operation']) + + try: + if module.check_mode: + module.exit_json() + resp = connection.upload_file(params['file_to_upload'], op_spec[OperationField.URL]) + module.exit_json(changed=True, response=resp, ansible_facts=construct_ansible_facts(resp, module.params)) + except FtdServerError as e: + module.fail_json(msg='Upload request for %s operation failed. Status code: %s. ' + 'Server response: %s' % (params['operation'], e.code, e.response)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_install.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_install.py new file mode 100644 index 00000000..42b4997a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ftd/ftd_install.py @@ -0,0 +1,290 @@ +#!/usr/bin/python + +# Copyright (c) 2019 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: ftd_install +short_description: Installs FTD pkg image on the firewall +description: + - Provisioning module for FTD devices that installs ROMMON image (if needed) and + FTD pkg image on the firewall. + - Can be used with `httpapi` and `local` connection types. The `httpapi` is preferred, + the `local` connection should be used only when the device cannot be accessed via + REST API. +requirements: [ "python >= 3.5", "firepower-kickstart" ] +notes: + - Requires `firepower-kickstart` library that should be installed separately and requires Python >= 3.5. + - On localhost, Ansible can be still run with Python >= 2.7, but the interpreter for this particular module must be + Python >= 3.5. + - Python interpreter for the module can overwritten in `ansible_python_interpreter` variable. +author: "Cisco Systems, Inc. (@annikulin)" +options: + device_hostname: + description: + - Hostname of the device as appears in the prompt (e.g., 'firepower-5516'). + required: true + type: str + device_username: + description: + - Username to login on the device. + - Defaulted to 'admin' if not specified. + required: false + type: str + default: admin + device_password: + description: + - Password to login on the device. + required: true + type: str + device_sudo_password: + description: + - Root password for the device. If not specified, `device_password` is used. + required: false + type: str + device_new_password: + description: + - New device password to set after image installation. + - If not specified, current password from `device_password` property is reused. + - Not applicable for ASA5500-X series devices. + required: false + type: str + device_ip: + description: + - Device IP address of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_gateway: + description: + - Device gateway of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_netmask: + description: + - Device netmask of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + device_model: + description: + - Platform model of the device (e.g., 'Cisco ASA5506-X Threat Defense'). + - If not specified and connection is 'httpapi`, the module tries to fetch the device model via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + choices: + - Cisco ASA5506-X Threat Defense + - Cisco ASA5508-X Threat Defense + - Cisco ASA5516-X Threat Defense + - Cisco Firepower 2110 Threat Defense + - Cisco Firepower 2120 Threat Defense + - Cisco Firepower 2130 Threat Defense + - Cisco Firepower 2140 Threat Defense + dns_server: + description: + - DNS IP address of management interface. + - If not specified and connection is 'httpapi`, the module tries to fetch the existing value via REST API. + - For 'local' connection type, this parameter is mandatory. + required: false + type: str + console_ip: + description: + - IP address of a terminal server. + - Used to set up an SSH connection with device's console port through the terminal server. + required: true + type: str + console_port: + description: + - Device's port on a terminal server. + required: true + type: str + console_username: + description: + - Username to login on a terminal server. + required: true + type: str + console_password: + description: + - Password to login on a terminal server. + required: true + type: str + rommon_file_location: + description: + - Path to the boot (ROMMON) image on TFTP server. + - Only TFTP is supported. + required: true + type: str + image_file_location: + description: + - Path to the FTD pkg image on the server to be downloaded. + - FTP, SCP, SFTP, TFTP, or HTTP protocols are usually supported, but may depend on the device model. + required: true + type: str + image_version: + description: + - Version of FTD image to be installed. + - Helps to compare target and current FTD versions to prevent unnecessary reinstalls. + required: true + type: str + force_install: + description: + - Forces the FTD image to be installed even when the same version is already installed on the firewall. + - By default, the module stops execution when the target version is installed in the device. + required: false + type: bool + default: false + search_domains: + description: + - Search domains delimited by comma. + - Defaulted to 'cisco.com' if not specified. + required: false + type: str + default: cisco.com +''' + +EXAMPLES = """ + - name: Install image v6.3.0 on FTD 5516 + community.network.ftd_install: + device_hostname: firepower + device_password: pass + device_ip: 192.168.0.1 + device_netmask: 255.255.255.0 + device_gateway: 192.168.0.254 + dns_server: 8.8.8.8 + + console_ip: 10.89.0.0 + console_port: 2004 + console_username: console_user + console_password: console_pass + + rommon_file_location: 'tftp://10.89.0.11/installers/ftd-boot-9.10.1.3.lfbff' + image_file_location: 'https://10.89.0.11/installers/ftd-6.3.0-83.pkg' + image_version: 6.3.0-83 +""" + +RETURN = """ +msg: + description: The message saying whether the image was installed or explaining why the installation failed. + returned: always + type: str +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.six import iteritems + +from ansible_collections.community.network.plugins.module_utils.network.ftd.configuration import BaseConfigurationResource, ParamName +from ansible_collections.community.network.plugins.module_utils.network.ftd.device import assert_kick_is_installed, FtdPlatformFactory, FtdModel +from ansible_collections.community.network.plugins.module_utils.network.ftd.operation import FtdOperations, get_system_info + +REQUIRED_PARAMS_FOR_LOCAL_CONNECTION = ['device_ip', 'device_netmask', 'device_gateway', 'device_model', 'dns_server'] + + +def main(): + fields = dict( + device_hostname=dict(type='str', required=True), + device_username=dict(type='str', required=False, default='admin'), + device_password=dict(type='str', required=True, no_log=True), + device_sudo_password=dict(type='str', required=False, no_log=True), + device_new_password=dict(type='str', required=False, no_log=True), + device_ip=dict(type='str', required=False), + device_netmask=dict(type='str', required=False), + device_gateway=dict(type='str', required=False), + device_model=dict(type='str', required=False, choices=FtdModel.supported_models()), + dns_server=dict(type='str', required=False), + search_domains=dict(type='str', required=False, default='cisco.com'), + + console_ip=dict(type='str', required=True), + console_port=dict(type='str', required=True), + console_username=dict(type='str', required=True), + console_password=dict(type='str', required=True, no_log=True), + + rommon_file_location=dict(type='str', required=True), + image_file_location=dict(type='str', required=True), + image_version=dict(type='str', required=True), + force_install=dict(type='bool', required=False, default=False) + ) + module = AnsibleModule(argument_spec=fields) + assert_kick_is_installed(module) + + use_local_connection = module._socket_path is None + if use_local_connection: + check_required_params_for_local_connection(module, module.params) + platform_model = module.params['device_model'] + check_that_model_is_supported(module, platform_model) + else: + connection = Connection(module._socket_path) + resource = BaseConfigurationResource(connection, module.check_mode) + system_info = get_system_info(resource) + + platform_model = module.params['device_model'] or system_info['platformModel'] + check_that_model_is_supported(module, platform_model) + check_that_update_is_needed(module, system_info) + check_management_and_dns_params(resource, module.params) + + ftd_platform = FtdPlatformFactory.create(platform_model, module.params) + ftd_platform.install_ftd_image(module.params) + + module.exit_json(changed=True, + msg='Successfully installed FTD image %s on the firewall device.' % module.params["image_version"]) + + +def check_required_params_for_local_connection(module, params): + missing_params = [k for k, v in iteritems(params) if k in REQUIRED_PARAMS_FOR_LOCAL_CONNECTION and v is None] + if missing_params: + message = "The following parameters are mandatory when the module is used with 'local' connection: %s." % \ + ', '.join(sorted(missing_params)) + module.fail_json(msg=message) + + +def check_that_model_is_supported(module, platform_model): + if platform_model not in FtdModel.supported_models(): + module.fail_json(msg="Platform model '%s' is not supported by this module." % platform_model) + + +def check_that_update_is_needed(module, system_info): + target_ftd_version = module.params["image_version"] + if not module.params["force_install"] and target_ftd_version == system_info['softwareVersion']: + module.exit_json(changed=False, msg="FTD already has %s version of software installed." % target_ftd_version) + + +def check_management_and_dns_params(resource, params): + if not all([params['device_ip'], params['device_netmask'], params['device_gateway']]): + management_ip = resource.execute_operation(FtdOperations.GET_MANAGEMENT_IP_LIST, {})['items'][0] + params['device_ip'] = params['device_ip'] or management_ip['ipv4Address'] + params['device_netmask'] = params['device_netmask'] or management_ip['ipv4NetMask'] + params['device_gateway'] = params['device_gateway'] or management_ip['ipv4Gateway'] + if not params['dns_server']: + dns_setting = resource.execute_operation(FtdOperations.GET_DNS_SETTING_LIST, {})['items'][0] + dns_server_group_id = dns_setting['dnsServerGroup']['id'] + dns_server_group = resource.execute_operation(FtdOperations.GET_DNS_SERVER_GROUP, + {ParamName.PATH_PARAMS: {'objId': dns_server_group_id}}) + params['dns_server'] = dns_server_group['dnsServers'][0]['ipAddress'] + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_banner.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_banner.py new file mode 100644 index 00000000..f3ba4d0c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_banner.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_banner +author: "Ruckus Wireless (@Commscope)" +short_description: Manage multiline banners on Ruckus ICX 7000 series switches +description: + - This will configure both login and motd banners on remote + ruckus ICX 7000 series switches. It allows playbooks to add or remove + banner text from the active running configuration. +notes: + - Tested against ICX 10.1 +options: + banner: + description: + - Specifies which banner should be configured on the remote device. + type: str + required: true + choices: ['motd', 'exec', 'incoming'] + text: + description: + - The banner text that should be + present in the remote device running configuration. + This argument accepts a multiline string, with no empty lines. + type: str + state: + description: + - Specifies whether or not the configuration is + present in the current devices active running configuration. + type: str + default: present + choices: ['present', 'absent'] + enterkey: + description: + - Specifies whether or not the motd configuration should accept + the require-enter-key + - Default is false. + type: bool + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, + by specifying it as module parameter. + type: bool + default: yes + +''' + +EXAMPLES = """ +- name: Configure the motd banner + community.network.icx_banner: + banner: motd + text: | + this is my motd banner + that contains a multiline + string + state: present + +- name: Remove the motd banner + community.network.icx_banner: + banner: motd + state: absent + +- name: Configure require-enter-key for motd + community.network.icx_banner: + banner: motd + enterkey: True + +- name: Remove require-enter-key for motd + community.network.icx_banner: + banner: motd + enterkey: False +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner motd + - this is my motd banner + - that contains a multiline + - string +""" + +import re +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import exec_command +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + want, have = updates + + if module.params['banner'] != 'motd' and module.params['enterkey']: + module.fail_json(msg=module.params['banner'] + " banner can have text only, got enterkey") + + if state == 'absent': + if 'text' in have.keys() and have['text']: + commands.append('no banner %s' % module.params['banner']) + if(module.params['enterkey'] is False): + commands.append('no banner %s require-enter-key' % module.params['banner']) + + elif state == 'present': + if module.params['text'] is None and module.params['enterkey'] is None: + module.fail_json(msg=module.params['banner'] + " one of the following is required: text, enterkey:only if motd") + + if module.params["banner"] == "motd" and want['enterkey'] != have['enterkey']: + if(module.params['enterkey']): + commands.append('banner %s require-enter-key' % module.params['banner']) + + if want['text'] and (want['text'] != have.get('text')): + module.params["enterkey"] = None + banner_cmd = 'banner %s' % module.params['banner'] + banner_cmd += ' $\n' + banner_cmd += module.params['text'].strip() + banner_cmd += '\n$' + commands.append(banner_cmd) + return commands + + +def map_config_to_obj(module): + compare = module.params.get('check_running_config') + obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False} + exec_command(module, 'skip') + output_text = '' + output_re = '' + out = get_config(module, flags=['| begin banner %s' + % module.params['banner']], compare=module.params['check_running_config']) + if out: + try: + output_re = re.search(r'banner %s( require-enter-key)' % module.params['banner'], out, re.S).group(0) + obj['enterkey'] = True + except BaseException: + pass + try: + output_text = re.search(r'banner %s (\$([^\$])+\$){1}' % module.params['banner'], out, re.S).group(1).strip('$\n') + except BaseException: + pass + + else: + output_text = None + if output_text: + obj['text'] = output_text + obj['state'] = 'present' + if module.params['check_running_config'] is False: + obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False, 'text': 'JUNK'} + return obj + + +def map_params_to_obj(module): + text = module.params['text'] + if text: + text = str(text).strip() + + return { + 'banner': module.params['banner'], + 'text': text, + 'state': module.params['state'], + 'enterkey': module.params['enterkey'] + } + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + banner=dict(required=True, choices=['motd', 'exec', 'incoming']), + text=dict(), + enterkey=dict(type='bool'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + required_one_of = [['text', 'enterkey', 'state']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + supports_check_mode=True) + + warnings = list() + results = {'changed': False} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + results['commands'] = commands + + if commands: + if not module.check_mode: + response = load_config(module, commands) + + results['changed'] = True + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_command.py new file mode 100644 index 00000000..85c85fc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_command.py @@ -0,0 +1,227 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: icx_command +author: "Ruckus Wireless (@Commscope)" +short_description: Run arbitrary commands on remote Ruckus ICX 7000 series switches +description: + - Sends arbitrary commands to an ICX node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +notes: + - Tested against ICX 10.1 +options: + commands: + description: + - List of commands to send to the remote ICX device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. If a command sent to the + device requires answering a prompt, checkall and newline if + multiple prompts, it is possible to pass + a dict containing I(command), I(answer), I(prompt), I(check_all) + and I(newline).Common answers are 'y' or "\\r" (carriage return, + must be double quotes). See examples. + type: list + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + type: str + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of times a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + type: int + default: 1 +""" + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.icx_command: + commands: show version + + - name: Run show version and check to see if output contains ICX + community.network.icx_command: + commands: show version + wait_for: result[0] contains ICX + + - name: Run multiple commands on remote nodes + community.network.icx_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.icx_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains ICX + - result[1] contains GigabitEthernet1/1/1 + - name: Run commands that require answering a prompt + community.network.icx_command: + commands: + - command: 'service password-encryption sha1' + prompt: 'Warning: Moving to higher password-encryption type,.*' + answer: 'y' + - name: Run commands that require answering multiple prompt + community.network.icx_command: + commands: + - command: 'username qqq password qqq' + prompt: + - 'User already exists. Do you want to modify:.*' + - 'To modify or remove user, enter current password:' + answer: + - 'y' + - 'qqq\\\r' + check_all: True + newline: False +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" + + +import re +import time +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, to_lines +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict(), + check_all=dict(type='bool', default='False'), + newline=dict(type='bool', default='True') + ), module) + commands = command(module.params['commands']) + for item in list(commands): + if module.check_mode: + if not item['command'].startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not executing configure terminal') + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + run_commands(module, ['skip']) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + + responses = run_commands(module, commands) + + for item in list(conditionals): + + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_config.py new file mode 100644 index 00000000..9486900f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_config.py @@ -0,0 +1,479 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_config +author: "Ruckus Wireless (@Commscope)" +short_description: Manage configuration sections of Ruckus ICX 7000 series switches +description: + - Ruckus ICX configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with ICX configuration sections in + a deterministic way. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + type: list + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + type: list + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + type: str + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + type: list + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + type: list + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + type: str + choices: ['line', 'strict', 'exact', 'none'] + default: line + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + type: str + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the ICX device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + type: str + default: "@" + backup: + description: + - 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 or role root directory, if + playbook is part of an ansible role. If the directory does not exist, + it is created. + type: bool + default: 'no' + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + type: str + aliases: ['config'] + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + start-up configuration and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the start-up configuration if it has changed since + the last save to configuration. If the argument is set to + I(never), the running-config will never be copied to the + configuration. If the argument is set to I(changed), then the running-config + will only be copied to the configuration if the task has made a change. + type: str + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the configuration. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + type: str + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + type: list + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + type: str +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.icx_config: + lines: hostname {{ inventory_hostname }} + +- name: Configure interface settings + community.network.icx_config: + lines: + - port-name test string + - ip address 172.31.1.1 255.255.255.0 + parents: interface ethernet 1/1/2 + +- name: Configure ip helpers on multiple interfaces + community.network.icx_config: + lines: + - ip helper-address 172.26.1.10 + - ip helper-address 172.26.3.8 + parents: "{{ item }}" + with_items: + - interface ethernet 1/1/2 + - interface ethernet 1/1/3 + +- name: Load new acl into device + community.network.icx_config: + lines: + - permit ip host 192.0.2.1 any log + - permit ip host 192.0.2.2 any log + - permit ip host 192.0.2.3 any log + - permit ip host 192.0.2.4 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.icx_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the configuration against the running-config + community.network.icx_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: For idempotency, use full-form commands + community.network.icx_config: + lines: + # - en + - enable + # parents: int eth1/0/11 + parents: interface ethernet 1/1/2 + +# Set boot image based on comparison to a group_var (version) and the version +# that is returned from the `icx_facts` module +- name: SETTING BOOT IMAGE + community.network.icx_config: + lines: + - no boot system + - boot system flash bootflash:{{new_image}} + host: "{{ inventory_hostname }}" + when: ansible_net_version != version + +- name: Render template onto an ICX device + community.network.icx_config: + backup: yes + src: "{{ lookup('file', 'config.j2') }}" +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/icx_config.2016-07-16@22:28:34 +""" + +import json +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands, get_config +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_defaults_flag, get_connection +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import check_args as icx_check_args +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def check_args(module, warnings): + icx_check_args(module, warnings) + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def edit_config_or_macro(connection, commands): + if "macro name" in commands[0]: + connection.edit_macro(candidate=commands) + else: + if commands[0] != '': + connection.edit_config(candidate=commands) + + +def get_candidate_config(module): + candidate = '' + if module.params['src']: + candidate = module.params['src'] + + elif module.params['lines']: + candidate_obj = NetworkConfig(indent=1) + parents = module.params['parents'] or list() + candidate_obj.add(module.params['lines'], parents=parents) + candidate = dumps(candidate_obj, 'raw') + + return candidate + + +def get_running_config(module, current_config=None, flags=None): + running = module.params['running_config'] + if not running: + if not module.params['defaults'] and current_config: + running = current_config + else: + running = get_config(module, flags=flags) + + return running + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'write memory') + else: + module.warn('Skipping command `copy running-config start-up configuration` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + src=dict(), + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + run_commands(module, 'skip') + diff_ignore_lines = module.params['diff_ignore_lines'] + config = None + contents = None + flags = None if module.params['defaults'] else [] + connection = get_connection(module) + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate_config(module) + running = get_running_config(module, contents, flags=flags) + try: + response = connection.get_diff(candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, path=path, + diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response['config_diff'] + banner_diff = response['banner_diff'] + + if config_diff or banner_diff: + commands = config_diff.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + result['banners'] = banner_diff + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + edit_config_or_macro(connection, commands) + if banner_diff: + connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter']) + + result['changed'] = True + + running_config = module.params['running_config'] + startup_config = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show configuration']) + + running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show configuration') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_copy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_copy.py new file mode 100644 index 00000000..ce67dbb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_copy.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_copy +author: "Ruckus Wireless (@Commscope)" +short_description: Transfer files from or to remote Ruckus ICX 7000 series switches +description: + - This module transfers files from or to remote devices running ICX. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + upload: + description: + - Name of the resource to be uploaded. Mutually exclusive with download. + type: str + choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary'] + download: + description: + - Name of the resource to be downloaded. Mutually exclusive with upload. + type: str + choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary', 'bootrom', 'fips-primary-sig', 'fips-secondary-sig', 'fips-bootrom-sig'] + protocol: + description: + - Data transfer protocol to be used + type: str + choices: ['scp', 'https'] + required: true + remote_server: + description: + - IP address of the remote server + type: str + required: true + remote_port: + description: + - The port number of the remote host. Default values will be selected based on protocol type. + Default scp:22, http:443 + type: str + remote_filename: + description: + - The name or path of the remote file/resource to be uploaded or downloaded. + type: str + required: true + remote_user: + description: + - remote username to be used for scp login. + type: str + remote_pass: + description: + - remote password to be used for scp login. + type: str + public_key: + description: + - public key type to be used to login to scp server + type: str + choices: ['rsa', 'dsa'] + +''' + +EXAMPLES = """ +- name: Upload running-config to the remote scp server + community.network.icx_copy: + upload: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download running-config from the remote scp server + community.network.icx_copy: + download: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download running-config from the remote scp server using rsa public key + community.network.icx_copy: + download: running-config + protocol: scp + remote_server: 172.16.10.49 + remote_filename: running.conf + remote_user: user1 + remote_pass: pass123 + public_key: rsa + +- name: Upload startup-config to the remote https server + community.network.icx_copy: + upload: startup-config + protocol: https + remote_server: 172.16.10.49 + remote_filename: config/running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Upload startup-config to the remote https server + community.network.icx_copy: + upload: startup-config + protocol: https + remote_server: 172.16.10.49 + remote_filename: config/running.conf + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the flash from remote scp ipv6 server + community.network.icx_copy: + download: startup-config + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the secondary flash from remote scp ipv6 server + community.network.icx_copy: + Download: flash_secondary + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the secondary flash from remote scp ipv6 server on port 5000 + community.network.icx_copy: + Download: flash_secondary + protocol: scp + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_port: 5000 + remote_filename: img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the primary flash from remote https ipv6 server + community.network.icx_copy: + Download: flash_primary + protocol: https + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_filename: images/img.bin + remote_user: user1 + remote_pass: pass123 + +- name: Download OS image into the primary flash from remote https ipv6 server on port 8080 + community.network.icx_copy: + Download: flash_primary + protocol: https + remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C + remote_port: 8080 + remote_filename: images/img.bin + remote_user: user1 + remote_pass: pass123 +""" + +RETURN = """ +changed: + description: true when downloaded any configuration or flash. false otherwise. + returned: always + type: bool +""" + + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError, exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import exec_scp, run_commands + + +def map_params_to_obj(module): + command = dict() + + if(module.params['protocol'] == 'scp'): + if(module.params['upload'] is not None): + module.params["upload"] = module.params["upload"].replace("flash_primary", "primary") + module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary") + if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'): + command["command"] = "copy %s scp %s%s %s%s" % (module.params['upload'], + module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "") + else: + command["command"] = "copy flash scp %s%s %s%s %s" % (module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "", + module.params["upload"]) + command["scp_user"] = module.params["remote_user"] + command["scp_pass"] = module.params["remote_pass"] + if(module.params['download'] is not None): + module.params["download"] = module.params["download"].replace("flash_primary", "primary") + module.params["download"] = module.params["download"].replace("flash_secondary", "secondary") + if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'): + command["command"] = "copy scp %s %s%s %s%s" % (module.params['download'], + module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "") + else: + command["command"] = "copy scp flash %s%s %s%s %s" % (module.params["remote_server"], + " " + module.params["remote_port"] if module.params["remote_port"] else "", + module.params["remote_filename"], + "public-key " + module.params["public_key"] if module.params["public_key"] else "", + module.params["download"]) + command["scp_user"] = module.params["remote_user"] + command["scp_pass"] = module.params["remote_pass"] + if(module.params['protocol'] == 'https'): + if(module.params['upload'] is not None): + module.params["upload"] = module.params["upload"].replace("flash_primary", "primary") + module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary") + if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'): + command["command"] = "copy %s https %s %s%s" % (module.params['upload'], + module.params["remote_server"], + module.params["remote_filename"], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + else: + command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"], + module.params["remote_filename"], + module.params['upload'], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + if(module.params['download'] is not None): + module.params["download"] = module.params["download"].replace("flash_primary", "primary") + module.params["download"] = module.params["download"].replace("flash_secondary", "secondary") + if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'): + command["command"] = "copy https %s %s %s%s" % (module.params['download'], + module.params["remote_server"], + module.params["remote_filename"], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + else: + command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"], + module.params["remote_filename"], + module.params['download'], + " port " + module.params["remote_port"] if module.params["remote_port"] else "") + return command + + +def checkValidations(module): + validation = dict( + scp=dict( + upload=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary'], + download=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary', + 'bootrom', + 'fips-primary-sig', + 'fips-secondary-sig', + 'fips-bootrom-sig']), + https=dict( + upload=[ + 'running-config', + 'startup-config'], + download=[ + 'flash_primary', + 'flash_secondary', + 'startup-config'])) + protocol = module.params['protocol'] + upload = module.params['upload'] + download = module.params['download'] + + if(protocol == 'scp' and module.params['remote_user'] is None): + module.fail_json(msg="While using scp remote_user argument is required") + if(upload is None and download is None): + module.fail_json(msg="Upload or download params are required.") + if(upload is not None and download is not None): + module.fail_json(msg="Only upload or download can be used at a time.") + if(upload): + if(not (upload in validation.get(protocol).get("upload"))): + module.fail_json(msg="Specified resource '" + upload + "' can't be uploaded to '" + protocol + "'") + if(download): + if(not (download in validation.get(protocol).get("download"))): + module.fail_json(msg="Specified resource '" + download + "' can't be downloaded from '" + protocol + "'") + + +def main(): + """entry point for module execution + """ + argument_spec = dict( + upload=dict( + type='str', + required=False, + choices=[ + 'running-config', + 'flash_primary', + 'flash_secondary', + 'startup-config']), + download=dict( + type='str', + required=False, + choices=[ + 'running-config', + 'startup-config', + 'flash_primary', + 'flash_secondary', + 'bootrom', + 'fips-primary-sig', + 'fips-secondary-sig', + 'fips-bootrom-sig']), + protocol=dict( + type='str', + required=True, + choices=[ + 'https', + 'scp']), + remote_server=dict( + type='str', + required=True), + remote_port=dict( + type='str', + required=False), + remote_filename=dict( + type='str', + required=True), + remote_user=dict( + type='str', + required=False), + remote_pass=dict( + type='str', + required=False, + no_log=True), + public_key=dict( + type='str', + required=False, + choices=[ + 'rsa', + 'dsa'])) + mutually_exclusive = [['upload', 'download']] + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, mutually_exclusive=mutually_exclusive) + + checkValidations(module) + warnings = list() + result = {'changed': False, 'warnings': warnings} + exec_command(module, 'skip') + + response = '' + try: + command = map_params_to_obj(module) + result['commands'] = [command["command"]] + + if(module.params['protocol'] == 'scp'): + response = exec_scp(module, command) + else: + response = run_commands(module, command) + if('Response Code: 404' in response): + module.fail_json(msg=response) + else: + result['response'] = "in progress..." + if(module.params["download"] is not None): + result['changed'] = True + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_facts.py new file mode 100644 index 00000000..0831f117 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_facts.py @@ -0,0 +1,544 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: icx_facts +author: "Ruckus Wireless (@Commscope)" +short_description: Collect facts from remote Ruckus ICX 7000 series switches +description: + - Collects a base set of device facts from a remote device that + is running ICX. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + type: list + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.icx_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.icx_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.icx_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str +ansible_net_stacked_models: + description: The model names of each device in the stack + returned: when multiple devices are configured in a stack + type: list +ansible_net_stacked_serialnums: + description: The serial numbers of each device in the stack + returned: when multiple devices are configured in a stack + type: list + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_filesystems_info: + description: A hash of all file systems containing info about each file system (e.g. free and total space) + returned: when hardware is configured + type: dict +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" + + +import re +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show running-config | include hostname'] + + def populate(self): + super(Default, self).run(['skip']) + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + self.facts['hostname'] = self.parse_hostname(self.responses[1]) + self.parse_stacks(data) + + def parse_version(self, data): + match = re.search(r'SW: Version ([0-9]+.[0-9]+.[0-9a-zA-Z]+)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'^hostname (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'HW: (\S+ \S+)', data, re.M) + if match: + return match.group(1) + + def parse_image(self, data): + match = re.search(r'\([0-9]+ bytes\) from \S+ (\S+)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial #:(\S+)', data) + if match: + return match.group(1) + + def parse_stacks(self, data): + match = re.findall(r'UNIT [1-9]+: SL [1-9]+: (\S+)', data, re.M) + if match: + self.facts['stacked_models'] = match + + match = re.findall(r'^System [Ss]erial [Nn]umber\s+: (\S+)', data, re.M) + if match: + self.facts['stacked_serialnums'] = match + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show memory', + 'show flash' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + self.facts['filesystems_info'] = self.parse_filesystems_info(self.responses[1]) + + if data: + if 'Invalid input detected' in data: + warnings.append('Unable to gather memory statistics') + else: + match = re.search(r'Dynamic memory: ([0-9]+) bytes total, ([0-9]+) bytes free, ([0-9]+%) used', data) + if match: + self.facts['memtotal_mb'] = int(match.group(1)) / 1024 + self.facts['memfree_mb'] = int(match.group(2)) / 1024 + + def parse_filesystems(self, data): + return "flash" + + def parse_filesystems_info(self, data): + facts = dict() + fs = '' + for line in data.split('\n'): + match = re.match(r'^(Stack unit \S+):', line) + if match: + fs = match.group(1) + facts[fs] = dict() + continue + match = re.match(r'\W+NAND Type: Micron NAND (\S+)', line) + if match: + facts[fs]['spacetotal'] = match.group(1) + match = re.match(r'\W+Code Flash Free Space = (\S+)', line) + if match: + facts[fs]['spacefree'] = int(int(match.group(1)) / 1024) + facts[fs]['spacefree'] = str(facts[fs]['spacefree']) + "Kb" + return {"flash": facts} + + +class Config(FactsBase): + + COMMANDS = ['skip', 'show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[1] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'skip', + 'show interfaces', + 'show running-config', + 'show lldp', + 'show media' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + data = self.responses[1] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_ipv4_interfaces(data) + + data = self.responses[2] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[3] + lldp_errs = ['Invalid input', 'LLDP is not enabled'] + + if data and not any(err in data for err in lldp_errs): + neighbors = self.run(['show lldp neighbors detail']) + if neighbors: + self.facts['neighbors'] = self.parse_neighbors(neighbors[0]) + + data = self.responses[4] + self.populate_mediatype(data) + + interfaceList = {} + for iface in self.facts['interfaces']: + if 'type' in self.facts['interfaces'][iface]: + newName = self.facts['interfaces'][iface]['type'] + iface + else: + newName = iface + interfaceList[newName] = self.facts['interfaces'][iface] + self.facts['interfaces'] = interfaceList + + def populate_mediatype(self, data): + lines = data.split("\n") + for line in lines: + match = re.match(r'Port (\S+):\W+Type\W+:\W+(.*)', line) + if match: + self.facts['interfaces'][match.group(1)]["mediatype"] = match.group(2) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = dict() + primary_address = addresses = [] + primary_address = re.findall(r'Internet address is (\S+/\S+), .*$', value, re.M) + addresses = re.findall(r'Secondary address (.+)$', value, re.M) + if len(primary_address) == 0: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'] = ipv4 + + def populate_ipv6_interfaces(self, data): + parts = data.split("\n") + for line in parts: + match = re.match(r'\W*interface \S+ (\S+)', line) + if match: + key = match.group(1) + try: + self.facts['interfaces'][key]['ipv6'] = list() + except KeyError: + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['ipv6'] = list() + self.facts['interfaces'][key]['ipv6'] = {} + continue + match = re.match(r'\W+ipv6 address (\S+)/(\S+)', line) + if match: + self.add_ip_address(match.group(1), "ipv6") + self.facts['interfaces'][key]['ipv6']["address"] = match.group(1) + self.facts['interfaces'][key]['ipv6']["subnet"] = match.group(2) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + for entry in neighbors.split('------------------------------------------------'): + if entry == '': + continue + intf = self.parse_lldp_intf(entry) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = self.parse_lldp_host(entry) + fact['port'] = self.parse_lldp_port(entry) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'\S+Ethernet(\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Port name is ([ \S]+)', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is \S+, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'Configured speed (\S+), actual (\S+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'configured duplex (\S+), actual (\S+)', data, re.M) + if match: + return match.group(2) + + def parse_mediatype(self, data): + match = re.search(r'media type is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lldp_intf(self, data): + match = re.search(r'^Local Intf: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_host(self, data): + match = re.search(r'System Name: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_port(self, data): + match = re.search(r'Port id: (.+)$', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +warnings = list() + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_interface.py new file mode 100644 index 00000000..df18a5ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_interface.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_interface +author: "Ruckus Wireless (@Commscope)" +short_description: Manage Interface on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of Interfaces + on ruckus icx devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the Interface. + type: str + description: + description: + - Name of the description. + type: str + enabled: + description: + - Interface link status + default: yes + type: bool + speed: + description: + - Interface link speed/duplex + choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] + type: str + stp: + description: + - enable/disable stp for the interface + type: bool + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + neighbors: + description: + - Check the operational state of given interface C(name) for CDP/LLDP neighbor. + - The following suboptions are available. + type: list + suboptions: + host: + description: + - "CDP/LLDP neighbor host for given interface C(name)." + type: str + port: + description: + - "CDP/LLDP neighbor port to which given interface C(name) is connected." + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + type: int + default: 10 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + type: str + choices: ['present', 'absent', 'up', 'down'] + power: + description: + - Inline power on Power over Ethernet (PoE) ports. + type: dict + suboptions: + by_class: + description: + - "The range is 0-4" + - "The power limit based on class value for given interface C(name)" + choices: ['0', '1', '2', '3', '4'] + type: str + limit: + description: + - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" + - "The power limit based on actual power value for given interface C(name)" + type: str + priority: + description: + - "The range is 1 (highest) to 3 (lowest)" + - "The priority for power management or given interface C(name)" + choices: ['1', '2', '3'] + type: str + enabled: + description: + - "enable/disable the poe of the given interface C(name)" + - "Default is false." + type: bool + aggregate: + description: + - List of Interfaces definitions. + type: list + suboptions: + name: + description: + - Name of the Interface. + type: str + description: + description: + - Name of the description. + type: str + enabled: + description: + - Interface link status + type: bool + speed: + description: + - Interface link speed/duplex + choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] + type: str + stp: + description: + - enable/disable stp for the interface + type: bool + tx_rate: + description: + - Transmit rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + rx_rate: + description: + - Receiver rate in bits per second (bps). + - This is state check parameter only. + - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) + type: str + neighbors: + description: + - Check the operational state of given interface C(name) for CDP/LLDP neighbor. + - The following suboptions are available. + type: list + suboptions: + host: + description: + - "CDP/LLDP neighbor host for given interface C(name)." + type: str + port: + description: + - "CDP/LLDP neighbor port to which given interface C(name) is connected." + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + type: int + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + type: str + choices: ['present', 'absent', 'up', 'down'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + - Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + power: + description: + - Inline power on Power over Ethernet (PoE) ports. + type: dict + suboptions: + by_class: + description: + - "The range is 0-4" + - "The power limit based on class value for given interface C(name)" + choices: ['0', '1', '2', '3', '4'] + type: str + limit: + description: + - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" + - "The power limit based on actual power value for given interface C(name)" + type: str + priority: + description: + - "The range is 1 (highest) to 3 (lowest)" + - "The priority for power management or given interface C(name)" + choices: ['1', '2', '3'] + type: str + enabled: + description: + - "enable/disable the poe of the given interface C(name)" + type: bool + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + - Module will use environment variable value(default:True), unless it is overridden, + by specifying it as module parameter. + default: yes + type: bool +''' + +EXAMPLES = """ +- name: Enable ethernet port and set name + community.network.icx_interface: + name: ethernet 1/1/1 + description: interface-1 + stp: true + enabled: true + +- name: Disable ethernet port 1/1/1 + community.network.icx_interface: + name: ethernet 1/1/1 + enabled: false + +- name: Enable ethernet port range, set name and speed + community.network.icx_interface: + name: ethernet 1/1/1 to 1/1/10 + description: interface-1 + speed: 100-full + enabled: true + +- name: Enable poe. Set class + community.network.icx_interface: + name: ethernet 1/1/1 + power: + by_class: 2 + +- name: Configure poe limit of interface + community.network.icx_interface: + name: ethernet 1/1/1 + power: + limit: 10000 + +- name: Disable poe of interface + community.network.icx_interface: + name: ethernet 1/1/1 + power: + enabled: false + +- name: Set lag name for a range of lags + community.network.icx_interface: + name: lag 1 to 10 + description: test lags + +- name: Disable lag + community.network.icx_interface: + name: lag 1 + enabled: false + +- name: Enable management interface + community.network.icx_interface: + name: management 1 + enabled: true + +- name: Enable loopback interface + community.network.icx_interface: + name: loopback 10 + enabled: true + +- name: Add interface using aggregate + community.network.icx_interface: + aggregate: + - { name: ethernet 1/1/1, description: test-interface-1, power: { by_class: 2 } } + - { name: ethernet 1/1/3, description: test-interface-3} + speed: 10-full + enabled: true + +- name: Check tx_rate, rx_rate intent arguments + community.network.icx_interface: + name: ethernet 1/1/10 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.icx_interface: + name: ethernet 1/1/10 + neighbors: + - port: 1/1/5 + host: netdev +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always + type: list + sample: + - interface ethernet 1/1/1 + - port-name interface-1 + - state present + - speed-duplex 100-full + - inline power priority 1 +""" + +import re +from copy import deepcopy +from time import sleep +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def parse_enable(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^disable', cfg, re.M) + if match: + return True + else: + return False + + +def parse_power_argument(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'(^inline power|^inline power(.*))+$', cfg, re.M) + if match: + return match.group(1) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def parse_stp_arguments(module, item): + rc, out, err = exec_command(module, 'show interfaces ' + item) + match = re.search(r'STP configured to (\S+),', out, re.S) + if match: + return True if match.group(1) == "ON" else False + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def validate_power(module, power): + count = 0 + for item in power: + if power.get(item) is not None: + count += 1 + if count > 1: + module.fail_json(msg='power parameters are mutually exclusive: class,limit,priority,enabled') + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, None, compare) + configobj = NetworkConfig(indent=1, contents=config) + match = re.findall(r'^interface (.+)$', config, re.M) + + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'port-name': parse_config_argument(configobj, item, 'port-name'), + 'speed-duplex': parse_config_argument(configobj, item, 'speed-duplex'), + 'stp': parse_stp_arguments(module, item), + 'disable': True if parse_enable(configobj, item) else False, + 'power': parse_power_argument(configobj, item), + 'state': 'present' + } + instances.append(obj) + return instances + + +def parse_poe_config(poe, power): + if poe.get('by_class') is not None: + power += 'power-by-class %s' % poe.get('by_class') + elif poe.get('limit') is not None: + power += 'power-limit %s' % poe.get('limit') + elif poe.get('priority') is not None: + power += 'priority %s' % poe.get('priority') + elif poe.get('enabled'): + power = 'inline power' + elif poe.get('enabled') is False: + power = 'no inline power' + return power + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + item['port-name'] = item.pop('description') + item['speed-duplex'] = item.pop('speed') + poe = item.get('power') + if poe: + + validate_power(module, poe) + power = 'inline power' + ' ' + power_arg = parse_poe_config(poe, power) + item.update({'power': power_arg}) + + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'port-name': module.params['description'], + 'speed-duplex': module.params['speed'], + 'stp': module.params['stp'], + 'delay': module.params['delay'], + 'state': module.params['state'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + poe = module.params.get('power') + if poe: + validate_power(module, poe) + power = 'inline power' + ' ' + power_arg = parse_poe_config(poe, power) + params.update({'power': power_arg}) + + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + + args = ('speed-duplex', 'port-name', 'power', 'stp') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + + if state == 'absent' and have == []: + commands.append('no ' + interface) + + elif state == 'absent' and obj_in_have: + commands.append('no ' + interface) + + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate == 'no inline power' and running is None: + candidate = None + if candidate != running: + if candidate: + if item == 'power': + cmd = str(candidate) + elif item == 'stp': + cmd = 'spanning-tree' if candidate else 'no spanning-tree' + else: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'disable', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'enable', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + if item == 'power': + commands.append(str(value)) + elif item == 'stp': + cmd = 'spanning-tree' if item else 'no spanning-tree' + else: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('disable') + if disable is False: + commands.append('enable') + + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors_lldp = None + have_neighbors_cdp = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interfaces %s' % w['name'] + rc, out, err = exec_command(module, command) + + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + if want_state in ('up', 'down'): + match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) + have_state = None + if match: + have_state = match.group(1) + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + if want_tx_rate: + match = re.search(r'%s (\d+)' % 'output rate:', out, re.M) + have_tx_rate = None + if match: + have_tx_rate = match.group(1) + + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + + if want_rx_rate: + match = re.search(r'%s (\d+)' % 'input rate:', out, re.M) + have_rx_rate = None + if match: + have_rx_rate = match.group(1) + + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + + if want_neighbors: + have_host = [] + have_port = [] + + 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_lldp: + lines = have_neighbors_lldp.strip().split('Local port: ') + + for line in lines: + field = line.split('\n') + if field[0].strip() == w['name'].split(' ')[1]: + for item in field: + match = re.search(r'\s*\+\s+System name\s+:\s+"(.*)"', item, re.M) + if match: + have_host.append(match.group(1)) + + match = re.search(r'\s*\+\s+Port description\s+:\s+"(.*)"', item, re.M) + if match: + have_port.append(match.group(1)) + + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + power_spec = dict( + by_class=dict(choices=['0', '1', '2', '3', '4']), + limit=dict(type='str'), + priority=dict(choices=['1', '2', '3']), + enabled=dict(type='bool') + ) + neighbors_spec = dict( + host=dict(), + port=dict() + ) + element_spec = dict( + name=dict(), + description=dict(), + enabled=dict(default=True, type='bool'), + speed=dict(type='str', choices=['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', + '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', + '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']), + stp=dict(type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']), + power=dict(type='dict', options=power_spec), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {} + result['changed'] = False + if warnings: + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_l3_interface.py new file mode 100644 index 00000000..8a2dc794 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_l3_interface.py @@ -0,0 +1,434 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_l3_interface +author: "Ruckus Wireless (@Commscope)" +short_description: Manage Layer-3 interfaces on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of Layer-3 interfaces + on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1 + type: str + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + type: str + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64. + type: str + mode: + description: + - Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf. + This should be specified only if ipv4 address is configured and if it is not secondary IP address. + choices: ['dynamic', 'ospf-ignore', 'ospf-passive'] + type: str + replace: + description: + - Replaces the configured primary IP address on the interface. + choices: ['yes', 'no'] + type: str + secondary: + description: + - Specifies that the configured address is a secondary IP address. + If this keyword is omitted, the configured address is the primary IP address. + choices: ['yes', 'no'] + type: str + aggregate: + description: + - List of Layer-3 interfaces definitions. Each of the entry in aggregate list should + define name of interface C(name) and a optional C(ipv4) or C(ipv6) address. + type: list + suboptions: + name: + description: + - Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1 + type: str + ipv4: + description: + - IPv4 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + type: str + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64. + type: str + mode: + description: + - Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf. + This should be specified only if ipv4 address is configured and if it is not secondary IP address. + choices: ['dynamic', 'ospf-ignore', 'ospf-passive'] + type: str + replace: + description: + - Replaces the configured primary IP address on the interface. + choices: ['yes', 'no'] + type: str + secondary: + description: + - Specifies that the configured address is a secondary IP address. + If this keyword is omitted, the configured address is the primary IP address. + choices: ['yes', 'no'] + type: str + state: + description: + - State of the Layer-3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + state: + description: + - State of the Layer-3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + default: present + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Remove ethernet 1/1/1 IPv4 and IPv6 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + ipv6: "fd5d:12c9:2201:1::1/64" + state: absent + +- name: Replace ethernet 1/1/1 primary IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + replace: yes + state: absent + +- name: Replace ethernet 1/1/1 dynamic IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + mode: dynamic + state: absent + +- name: Set ethernet 1/1/1 secondary IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + secondary: yes + state: absent + +- name: Set ethernet 1/1/1 IPv4 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv4: 192.168.0.1/24 + +- name: Set ethernet 1/1/1 IPv6 address + community.network.icx_l3_interface: + name: ethernet 1/1/1 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set IP addresses on aggregate + community.network.icx_l3_interface: + aggregate: + - { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 } + - { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.icx_l3_interface: + aggregate: + - { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 } + - { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent + + +- name: Set the ipv4 and ipv6 of a virtual ethernet(ve) + community.network.icx_l3_interface: + name: ve 100 + ipv4: 192.168.0.1 + ipv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface ethernet 1/1/1 + - ip address 192.168.0.1 255.255.255.0 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" + + +import re +from copy import deepcopy +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'], + 'replace': module.params['replace'], + 'mode': module.params['mode'], + 'secondary': module.params['secondary'], + }) + + validate_param_values(module, obj) + + return obj + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + + values = [] + matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M) + for match in matches: + match_str = match.group(1).strip() + if arg == 'ipv6 address': + values.append(match_str) + else: + values = match_str + break + + return values or None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, flags=['| begin interface'], compare=compare) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+ \S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1]))) + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + if 'replace' in w: + replace = w['replace'] == 'yes' + else: + replace = False + if w['mode'] is not None: + mode = ' ' + w['mode'] + else: + mode = '' + if w['secondary'] is not None: + secondary = w['secondary'] == 'yes' + else: + secondary = False + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and have == []: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1])) + commands.append('no ip address {ip}'.format(ip=ipv4)) + if ipv6: + commands.append('no ipv6 address {ip}'.format(ip=ipv6)) + + elif state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1])) + commands.append('no ip address {ip}'.format(ip=ipv4)) + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address {ip}'.format(ip=ipv6)) + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + address = ipv4.split('/') + if len(address) == 2: + ipv4 = '{0} {1}'.format(address[0], to_netmask(address[1])) + commands.append('ip address %s%s%s%s' % (format(ipv4), mode, ' replace' if (replace) else '', ' secondary' if (secondary) else '')) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]: + commands.append('ipv6 address {ip}'.format(ip=ipv6)) + + if commands[-1] == interface: + commands.pop(-1) + else: + commands.append("exit") + + return commands + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + replace=dict(choices=['yes', 'no']), + mode=dict(choices=['dynamic', 'ospf-ignore', 'ospf-passive']), + secondary=dict(choices=['yes', 'no']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])), + state=dict(default='present', + choices=['present', 'absent']), + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec) + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate'], ['secondary', 'replace'], ['secondary', 'mode']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + + if commands: + if not module.check_mode: + resp = load_config(module, commands) + warnings.extend((out for out in resp if out)) + + result['changed'] = True + + if warnings: + result['warnings'] = warnings + + result['commands'] = commands + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_linkagg.py new file mode 100644 index 00000000..7400d56d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_linkagg.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_linkagg +author: "Ruckus Wireless (@Commscope)" +short_description: Manage link aggregation groups on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of link aggregation groups + on Ruckus ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID + type: int + name: + description: + - Name of the LAG + type: str + mode: + description: + - Mode of the link aggregation group. + type: str + choices: ['dynamic', 'static'] + members: + description: + - List of port members or ranges of the link aggregation group. + type: list + state: + description: + - State of the link aggregation group. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes + aggregate: + description: + - List of link aggregation definitions. + type: list + suboptions: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID + type: int + name: + description: + - Name of the LAG + type: str + mode: + description: + - Mode of the link aggregation group. + type: str + choices: ['dynamic', 'static'] + members: + description: + - List of port members or ranges of the link aggregation group. + type: list + state: + description: + - State of the link aggregation group. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: no + +''' + +EXAMPLES = """ +- name: Create static link aggregation group + community.network.icx_linkagg: + group: 10 + mode: static + name: LAG1 + +- name: Create link aggregation group with auto id + community.network.icx_linkagg: + group: auto + mode: dynamic + name: LAG2 + +- name: Delete link aggregation group + community.network.icx_linkagg: + group: 10 + state: absent + +- name: Set members to LAG + community.network.icx_linkagg: + group: 200 + mode: static + members: + - ethernet 1/1/1 to 1/1/6 + - ethernet 1/1/10 + +- name: Remove links other then LAG id 100 and 3 using purge + community.network.icx_linkagg: + aggregate: + - { group: 3} + - { group: 100} + purge: true +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lag LAG1 dynamic id 11 + - ports ethernet 1/1/1 to 1/1/6 + - no ports ethernet 1/1/10 + - no lag LAG1 dynamic id 12 +""" + + +import re +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import ConnectionError, exec_command +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands, get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec + + +def range_to_members(ranges, prefix=""): + match = re.findall(r'(ethe[a-z]* [0-9]/[0-9]/[0-9]+)( to [0-9]/[0-9]/[0-9]+)?', ranges) + members = list() + for m in match: + start, end = m + if(end == ''): + start = start.replace("ethe ", "ethernet ") + members.append("%s%s" % (prefix, start)) + else: + start_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', start) + end_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', end) + start = int(start_tmp.group(1)) + end = int(end_tmp.group(1)) + 1 + for num in range(start, end): + members.append("%sethernet 1/1/%s" % (prefix, num)) + return members + + +def map_config_to_obj(module): + objs = dict() + compare = module.params['check_running_config'] + config = get_config(module, None, compare=compare) + obj = None + for line in config.split('\n'): + l = line.strip() + match1 = re.search(r'lag (\S+) (\S+) id (\S+)', l, re.M) + if match1: + obj = dict() + obj['name'] = match1.group(1) + obj['mode'] = match1.group(2) + obj['group'] = match1.group(3) + obj['state'] = 'present' + obj['members'] = list() + else: + match2 = re.search(r'ports .*', l, re.M) + if match2 and obj is not None: + obj['members'].extend(range_to_members(match2.group(0))) + elif obj is not None: + objs[obj['group']] = obj + obj = None + return objs + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + d = item.copy() + d['group'] = str(d['group']) + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'], + 'name': module.params['name'] + }) + + return obj + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + return None + + +def is_member(member, lst): + for li in lst: + ml = range_to_members(li) + if member in ml: + return True + return False + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + if have == {} and w['state'] == 'absent': + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + elif have.get(w['group']) is None: + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + if(w.get('members') is not None and w['state'] == 'present'): + for m in w['members']: + commands.append("ports %s" % (m)) + if w['state'] == 'present': + commands.append("exit") + else: + commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group'])) + if(w.get('members') is not None and w['state'] == 'present'): + for m in have[w['group']]['members']: + if not is_member(m, w['members']): + commands.append("no ports %s" % (m)) + for m in w['members']: + sm = range_to_members(ranges=m) + for smm in sm: + if smm not in have[w['group']]['members']: + commands.append("ports %s" % (smm)) + + if w['state'] == 'present': + commands.append("exit") + if purge: + for h in have: + if search_obj_in_list(have[h]['group'], want) is None: + commands.append("no lag %s %s id %s" % (have[h]['name'], have[h]['mode'], have[h]['group'])) + return commands + + +def main(): + element_spec = dict( + group=dict(type='int'), + name=dict(type='str'), + mode=dict(choices=['dynamic', 'static']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True, type='int') + + required_one_of = [['group', 'aggregate']] + required_together = [['name', 'group']] + mutually_exclusive = [['group', 'aggregate']] + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + exec_command(module, 'skip') + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_lldp.py new file mode 100644 index 00000000..43d10765 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_lldp.py @@ -0,0 +1,178 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_lldp +author: "Ruckus Wireless (@Commscope)" +short_description: Manage LLDP configuration on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of LLDP service on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + interfaces: + description: + - specify interfaces + suboptions: + name: + description: + - List of ethernet ports to enable lldp. To add a range of ports use 'to' keyword. See the example. + type: list + state: + description: + - State of lldp configuration for interfaces + type: str + choices: ['present', 'absent', 'enabled', 'disabled'] + type: list + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes + state: + description: + - Enables the receipt and transmission of Link Layer Discovery Protocol (LLDP) globally. + type: str + choices: ['present', 'absent', 'enabled', 'disabled'] +''' + +EXAMPLES = """ +- name: Disable LLDP + community.network.icx_lldp: + state: absent + +- name: Enable LLDP + community.network.icx_lldp: + state: present + +- name: Disable LLDP on ports 1/1/1 - 1/1/10, 1/1/20 + community.network.icx_lldp: + interfaces: + - name: + - ethernet 1/1/1 to 1/1/10 + - ethernet 1/1/20 + state: absent + state: present + +- name: Enable LLDP on ports 1/1/5 - 1/1/10 + community.network.icx_lldp: + interfaces: + - name: + - ethernet 1/1/1 to 1/1/10 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lldp run + - no lldp run +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, run_commands + + +def has_lldp(module): + run_commands(module, ['skip']) + output = run_commands(module, ['show lldp']) + is_lldp_enable = False + if len(output) > 0 and "LLDP is not running" not in output[0]: + is_lldp_enable = True + + return is_lldp_enable + + +def map_obj_to_commands(module, commands): + interfaces = module.params.get('interfaces') + for item in interfaces: + state = item.get('state') + if state == 'present': + for port in item.get('name'): + if 'all' in port: + module.fail_json(msg='cannot enable on all the ports') + else: + commands.append('lldp enable ports {0}'.format(str(port))) + elif state == 'absent': + for port in item.get('name'): + if 'all' in port: + module.fail_json(msg='cannot enable on all the ports') + else: + commands.append('no lldp enable ports {0}'.format(str(port))) + + +def main(): + """ main entry point for module execution + """ + interfaces_spec = dict( + name=dict(type='list'), + state=dict(choices=['present', 'absent', + 'enabled', 'disabled']) + ) + + argument_spec = dict( + interfaces=dict(type='list', elements='dict', options=interfaces_spec), + state=dict(choices=['present', 'absent', + 'enabled', 'disabled']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + if module.params['check_running_config'] is False: + HAS_LLDP = None + else: + HAS_LLDP = has_lldp(module) + + commands = [] + state = module.params['state'] + + if state is None: + if HAS_LLDP: + map_obj_to_commands(module, commands) + else: + module.fail_json(msg='LLDP is not running') + else: + if state == 'absent' and HAS_LLDP is None: + commands.append('no lldp run') + + if state == 'absent' and HAS_LLDP: + commands.append('no lldp run') + + elif state == 'present': + if not HAS_LLDP: + commands.append('lldp run') + if module.params.get('interfaces'): + map_obj_to_commands(module, commands) + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_logging.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_logging.py new file mode 100644 index 00000000..fbe3d73d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_logging.py @@ -0,0 +1,576 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_logging +author: "Ruckus Wireless (@Commscope)" +short_description: Manage logging on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of logging + on Ruckus ICX 7000 series switches. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + aggregate: + description: + - List of logging definitions. + type: list + suboptions: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + state: + description: + - State of the logging configuration. + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + state: + description: + - State of the logging configuration. + default: present + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure host logging. + community.network.icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 +- name: Remove host logging configuration. + community.network.icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 + state: absent +- name: Disables the real-time display of syslog messages. + community.network.icx_logging: + dest: console + state: absent +- name: Enables local syslog logging. + community.network.icx_logging: + dest : on + state: present +- name: Configure buffer level + community.network.icx_logging: + dest: buffered + level: critical +- name: Configure logging using aggregate + community.network.icx_logging: + aggregate: + - { dest: buffered, level: ['notifications','errors'] } +- name: Remove logging using aggregate + community.network.icx_logging: + aggregate: + - { dest: console } + - { dest: host, name: 172.16.0.1, udp_port: 5555 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging host 172.16.0.1 + - logging console +""" + +import re +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec, validate_ip_v6_address +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def diff_in_list(want, have): + adds = set() + removes = set() + for w in want: + if w['dest'] == 'buffered': + for h in have: + if h['dest'] == 'buffered': + adds = w['level'] - h['level'] + removes = h['level'] - w['level'] + return adds, removes + return adds, removes + + +def map_obj_to_commands(updates): + dest_group = ('host', 'console', 'persistence', 'enable') + commands = list() + want, have = updates + + for w in want: + dest = w['dest'] + name = w['name'] + level = w['level'] + state = w['state'] + udp_port = w['udp_port'] + facility = w['facility'] + del w['state'] + del w['facility'] + + facility_name = '' + facility_level = '' + if name is not None and validate_ip_v6_address(name): + name = 'ipv6 ' + name + + if facility: + for item in have: + if item['dest'] == 'facility': + facility_name = item['dest'] + facility_level = item['facility'] + + if state == 'absent': + if have == []: + if facility: + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + commands.append('no logging buffered {0}'.format(item)) + + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if facility: + if facility_name == 'facility' and facility_level != 'user': + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + if item in have_level: + commands.append('no logging buffered {0}'.format(item)) + + if w in have: + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if state == 'present': + if facility: + if facility != facility_level: + commands.append('logging facility {0}'.format(facility)) + if w not in have: + if dest == 'host': + if name and udp_port: + commands.append('logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('logging host {0}'.format(name)) + elif dest == 'buffered': + adds, removes = diff_in_list(want, have) + for item in adds: + commands.append('logging buffered {0}'.format(item)) + for item in removes: + commands.append('no logging buffered {0}'.format(item)) + elif dest == 'rfc5424': + commands.append('logging enable {0}'.format(dest)) + else: + commands.append('logging {0}'.format(dest)) + + return commands + + +def parse_port(line, dest): + port = None + if dest == 'host': + match = re.search(r'logging host \S+\s+udp-port\s+(\d+)', line, re.M) + if match: + port = match.group(1) + else: + match_port = re.search(r'logging host ipv6 \S+\s+udp-port\s+(\d+)', line, re.M) + if match_port: + port = match_port.group(1) + return port + + +def parse_name(line, dest): + name = None + if dest == 'host': + match = re.search(r'logging host (\S+)', line, re.M) + if match: + if match.group(1) == 'ipv6': + ipv6_add = re.search(r'logging host ipv6 (\S+)', line, re.M) + name = ipv6_add.group(1) + else: + name = match.group(1) + + return name + + +def parse_address(line, dest): + if dest == 'host': + match = re.search(r'^logging host ipv6 (\S+)', line.strip(), re.M) + if match: + return True + return False + + +def map_config_to_obj(module): + obj = [] + facility = '' + addr6 = False + dest_group = ('host', 'console', 'buffered', 'persistence', 'enable') + dest_level = ('alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', 'notifications', 'warnings') + buff_level = list() + if module.params['check_running_config'] is False: + return [] + data = get_config(module, flags=['| include logging']) + facility_match = re.search(r'^logging facility (\S+)', data, re.M) + if facility_match: + facility = facility_match.group(1) + obj.append({ + 'dest': 'facility', + 'facility': facility + }) + else: + obj.append({ + 'dest': 'facility', + 'facility': 'user' + }) + for line in data.split('\n'): + match = re.search(r'^logging (\S+)', line.strip(), re.M) + if match: + + if match.group(1) in dest_group: + dest = match.group(1) + if dest == 'host': + obj.append({ + 'dest': dest, + 'name': parse_name(line.strip(), dest), + 'udp_port': parse_port(line, dest), + 'level': None, + 'addr6': parse_address(line, dest) + + }) + elif dest == 'buffered': + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + if dest == 'enable': + dest = 'rfc5424' + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + + ip_match = re.search(r'^no logging buffered (\S+)', line, re.M) + if ip_match: + dest = 'buffered' + buff_level.append(ip_match.group(1)) + if 'no logging on' not in data: + obj.append({ + 'dest': 'on', + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + levels = set() + for items in dest_level: + if items not in buff_level: + levels.add(items) + obj.append({ + 'dest': 'buffered', + 'level': levels, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + return obj + + +def count_terms(check, param=None): + count = 0 + for term in check: + if param[term] is not None: + count += 1 + return count + + +def check_required_if(module, spec, param): + for sp in spec: + missing = [] + max_missing_count = 0 + is_one_of = False + if len(sp) == 4: + key, val, requirements, is_one_of = sp + else: + key, val, requirements = sp + + if is_one_of: + max_missing_count = len(requirements) + term = 'any' + else: + term = 'all' + + if key in param and param[key] == val: + for check in requirements: + count = count_terms((check,), param) + if count == 0: + missing.append(check) + if len(missing) and len(missing) >= max_missing_count: + msg = "%s is %s but %s of the following are missing: %s" % (key, val, term, ', '.join(missing)) + module.fail_json(msg=msg) + + +def map_params_to_obj(module, required_if=None): + obj = [] + addr6 = False + aggregate = module.params.get('aggregate') + + if aggregate: + for item in aggregate: + if item['name'] is not None and validate_ip_v6_address(item['name']): + addr6 = True + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + check_required_if(module, required_if, item) + item.update({'addr6': addr6}) + + d = item.copy() + d['level'] = set(d['level']) if d['level'] is not None else None + if d['dest'] != 'host': + d['name'] = None + d['udp_port'] = None + + if d['dest'] != 'buffered': + d['level'] = None + del d['check_running_config'] + obj.append(d) + + else: + if module.params['name'] is not None and validate_ip_v6_address(module.params['name']): + addr6 = True + if module.params['dest'] != 'host': + module.params['name'] = None + module.params['udp_port'] = None + + if module.params['dest'] != 'buffered': + module.params['level'] = None + + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'udp_port': module.params['udp_port'], + 'level': set(module.params['level']) if module.params['level'] else None, + 'facility': module.params['facility'], + 'state': module.params['state'], + 'addr6': addr6 + }) + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + dest=dict( + type='str', + choices=[ + 'on', + 'host', + 'console', + 'buffered', + 'persistence', + 'rfc5424']), + name=dict( + type='str'), + udp_port=dict(), + level=dict( + type='list', + choices=[ + 'alerts', + 'critical', + 'debugging', + 'emergencies', + 'errors', + 'informational', + 'notifications', + 'warnings']), + facility=dict( + type='str', + choices=[ + 'auth', + 'cron', + 'daemon', + 'kern', + 'local0', + 'local1', + 'local2', + 'local3', + 'local4', + 'local5', + 'local6', + 'local7', + 'user', + 'lpr', + 'mail', + 'news', + 'syslog', + 'sys9', + 'sys10', + 'sys11', + 'sys12', + 'sys13', + 'sys14', + 'user', + 'uucp']), + state=dict( + default='present', + choices=[ + 'present', + 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))) + + aggregate_spec = deepcopy(element_spec) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + required_if = [('dest', 'host', ['name']), + ('dest', 'buffered', ['level'])] + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + warnings = list() + + exec_command(module, 'skip') + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_if=required_if) + have = map_config_to_obj(module) + result['want'] = want + result['have'] = have + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_ping.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_ping.py new file mode 100644 index 00000000..8ea2544e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_ping.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_ping +author: "Ruckus Wireless (@Commscope)" +short_description: Tests reachability using ping from Ruckus ICX 7000 series switches +description: + - Tests reachability using ping from switch to a remote destination. +notes: + - Tested against ICX 10.1 +options: + count: + description: + - Number of packets to send. Default is 1. + type: int + dest: + description: + - ip-addr | host-name | vrf vrf-name | ipv6 [ ipv6-addr | host-name | vrf vrf-name] (resolvable by switch) of the remote node. + required: true + type: str + timeout: + description: + - Specifies the time, in milliseconds for which the device waits for a reply from the pinged device. + The value can range from 1 to 4294967296. The default is 5000 (5 seconds). + type: int + ttl: + description: + - Specifies the time to live as a maximum number of hops. The value can range from 1 to 255. The default is 64. + type: int + size: + description: + - Specifies the size of the ICMP data portion of the packet, in bytes. This is the payload and does not include the header. + The value can range from 0 to 10000. The default is 16.. + type: int + source: + description: + - IP address to be used as the origin of the ping packets. + type: str + vrf: + description: + - Specifies the Virtual Routing and Forwarding (VRF) instance of the device to be pinged. + type: str + state: + description: + - Determines if the expected result is success or fail. + type: str + choices: [ absent, present ] + default: present +''' + +EXAMPLES = r''' +- name: Test reachability to 10.10.10.10 + community.network.icx_ping: + dest: 10.10.10.10 + +- name: Test reachability to ipv6 address from source with timeout + community.network.icx_ping: + dest: ipv6 2001:cdba:0000:0000:0000:0000:3257:9652 + source: 10.1.1.1 + timeout: 100000 + +- name: Test reachability to 10.1.1.1 through vrf using 5 packets + community.network.icx_ping: + dest: 10.1.1.1 + vrf: x.x.x.x + count: 5 + +- name: Test unreachability to 10.30.30.30 + community.network.icx_ping: + dest: 10.40.40.40 + state: absent + +- name: Test reachability to ipv4 with ttl and packet size + community.network.icx_ping: + dest: 10.10.10.10 + ttl: 20 + size: 500 +''' + +RETURN = ''' +commands: + description: Show the command sent. + returned: always + type: list + sample: ["ping 10.40.40.40 count 20 source loopback0", "ping 10.40.40.40"] +packet_loss: + description: Percentage of packets lost. + returned: always + type: str + sample: "0%" +packets_rx: + description: Packets successfully received. + returned: always + type: int + sample: 20 +packets_tx: + description: Packets successfully transmitted. + returned: always + type: int + sample: 20 +rtt: + description: Show RTT stats. + returned: always + type: dict + sample: {"avg": 2, "max": 8, "min": 1} +''' + +from ansible.module_utils._text import to_text +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection, ConnectionError +import re + + +def build_ping(dest, count=None, source=None, timeout=None, ttl=None, size=None, vrf=None): + """ + Function to build the command to send to the terminal for the switch + to execute. All args come from the module's unique params. + """ + + if vrf is not None: + cmd = "ping vrf {0} {1}".format(vrf, dest) + else: + cmd = "ping {0}".format(dest) + + if count is not None: + cmd += " count {0}".format(str(count)) + + if timeout is not None: + cmd += " timeout {0}".format(str(timeout)) + + if ttl is not None: + cmd += " ttl {0}".format(str(ttl)) + + if size is not None: + cmd += " size {0}".format(str(size)) + + if source is not None: + cmd += " source {0}".format(source) + + return cmd + + +def parse_ping(ping_stats): + """ + Function used to parse the statistical information from the ping response. + Example: "Success rate is 100 percent (5/5), round-trip min/avg/max=40/51/55 ms." + Returns the percent of packet loss, received packets, transmitted packets, and RTT dict. + """ + if ping_stats.startswith('Success'): + rate_re = re.compile(r"^\w+\s+\w+\s+\w+\s+(?P\d+)\s+\w+\s+\((?P\d+)/(?P\d+)\)") + rtt_re = re.compile(r".*,\s+\S+\s+\S+=(?P\d+)/(?P\d+)/(?P\d+)\s+\w+\.+\s*$|.*\s*$") + + rate = rate_re.match(ping_stats) + rtt = rtt_re.match(ping_stats) + return rate.group("pct"), rate.group("rx"), rate.group("tx"), rtt.groupdict() + else: + rate_re = re.compile(r"^Sending+\s+(?P\d+),") + rate = rate_re.match(ping_stats) + rtt = {'avg': 0, 'max': 0, 'min': 0} + return 0, 0, rate.group('tx'), rtt + + +def validate_results(module, loss, results): + """ + This function is used to validate whether the ping results were unexpected per "state" param. + """ + state = module.params["state"] + if state == "present" and loss == 100: + module.fail_json(msg="Ping failed unexpectedly", **results) + elif state == "absent" and loss < 100: + module.fail_json(msg="Ping succeeded unexpectedly", **results) + + +def validate_fail(module, responses): + if ("Success" in responses or "No reply" in responses) is False: + module.fail_json(msg=responses) + + +def validate_parameters(module, timeout, count): + if timeout and not 1 <= int(timeout) <= 4294967294: + module.fail_json(msg="bad value for timeout - valid range (1-4294967294)") + if count and not 1 <= int(count) <= 4294967294: + module.fail_json(msg="bad value for count - valid range (1-4294967294)") + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + count=dict(type="int"), + dest=dict(type="str", required=True), + timeout=dict(type="int"), + ttl=dict(type="int"), + size=dict(type="int"), + source=dict(type="str"), + state=dict(type="str", choices=["absent", "present"], default="present"), + vrf=dict(type="str") + ) + + module = AnsibleModule(argument_spec=argument_spec) + + count = module.params["count"] + dest = module.params["dest"] + source = module.params["source"] + timeout = module.params["timeout"] + ttl = module.params["ttl"] + size = module.params["size"] + vrf = module.params["vrf"] + results = {} + warnings = list() + + if warnings: + results["warnings"] = warnings + + response = '' + try: + validate_parameters(module, timeout, count) + results["commands"] = [build_ping(dest, count, source, timeout, ttl, size, vrf)] + ping_results = run_commands(module, commands=results["commands"]) + ping_results_list = ping_results[0].split("\n") + + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + validate_fail(module, ping_results[0]) + + stats = "" + statserror = '' + for line in ping_results_list: + if line.startswith('Sending'): + statserror = line + if line.startswith('Success'): + stats = line + elif line.startswith('No reply'): + stats = statserror + + success, rx, tx, rtt = parse_ping(stats) + loss = abs(100 - int(success)) + results["packet_loss"] = str(loss) + "%" + results["packets_rx"] = int(rx) + results["packets_tx"] = int(tx) + + # Convert rtt values to int + for k, v in rtt.items(): + if rtt[k] is not None: + rtt[k] = int(v) + + results["rtt"] = rtt + + validate_results(module, loss, results) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_static_route.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_static_route.py new file mode 100644 index 00000000..bf3fed12 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_static_route.py @@ -0,0 +1,310 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_static_route +author: "Ruckus Wireless (@Commscope)" +short_description: Manage static IP routes on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of static + IP routes on Ruckus ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + prefix: + description: + - Network prefix of the static route. + type: str + mask: + description: + - Network prefix mask of the static route. + type: str + next_hop: + description: + - Next hop IP of the static route. + type: str + admin_distance: + description: + - Admin distance of the static route. Range is 1 to 255. + type: int + aggregate: + description: List of static route definitions. + type: list + suboptions: + prefix: + description: + - Network prefix of the static route. + type: str + mask: + description: + - Network prefix mask of the static route. + type: str + next_hop: + description: + - Next hop IP of the static route. + type: str + admin_distance: + description: + - Admin distance of the static route. Range is 1 to 255. + type: int + state: + description: + - State of the static route configuration. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + purge: + description: + - Purge routes not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the static route configuration. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure static route + community.network.icx_static_route: + prefix: 192.168.2.0/24 + next_hop: 10.0.0.1 + +- name: Remove configuration + community.network.icx_static_route: + prefix: 192.168.2.0 + mask: 255.255.255.0 + next_hop: 10.0.0.1 + state: absent + +- name: Add static route aggregates + community.network.icx_static_route: + aggregate: + - { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + - { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + +- name: Remove static route aggregates + community.network.icx_static_route: + aggregate: + - { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + - { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - ip route 192.168.2.0 255.255.255.0 10.0.0.1 +""" + + +from copy import deepcopy +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + +try: + from ipaddress import ip_network + HAS_IPADDRESS = True +except ImportError: + HAS_IPADDRESS = False + + +def map_obj_to_commands(want, have, module): + commands = list() + purge = module.params['purge'] + for w in want: + for h in have: + for key in ['prefix', 'mask', 'next_hop']: + if w[key] != h[key]: + break + else: + break + else: + h = None + + prefix = w['prefix'] + mask = w['mask'] + next_hop = w['next_hop'] + admin_distance = w.get('admin_distance') + if not admin_distance and h: + w['admin_distance'] = admin_distance = h['admin_distance'] + state = w['state'] + del w['state'] + + if state == 'absent' and have == []: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + + if state == 'absent' and w in have: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + elif state == 'present' and w not in have: + if admin_distance: + commands.append('ip route %s %s %s distance %s' % (prefix, mask, next_hop, admin_distance)) + else: + commands.append('ip route %s %s %s' % (prefix, mask, next_hop)) + if purge: + commands = [] + for h in have: + if h not in want: + commands.append('no ip route %s %s %s' % (prefix, mask, next_hop)) + return commands + + +def map_config_to_obj(module): + obj = [] + compare = module.params['check_running_config'] + out = get_config(module, flags='| include ip route', compare=compare) + + for line in out.splitlines(): + splitted_line = line.split() + if len(splitted_line) not in (4, 5, 6): + continue + cidr = ip_network(to_text(splitted_line[2])) + prefix = str(cidr.network_address) + mask = str(cidr.netmask) + next_hop = splitted_line[3] + if len(splitted_line) == 6: + admin_distance = splitted_line[5] + else: + admin_distance = '1' + + obj.append({ + 'prefix': prefix, 'mask': mask, 'next_hop': next_hop, + 'admin_distance': admin_distance + }) + + return obj + + +def prefix_length_parser(prefix, mask, module): + if '/' in prefix and mask is not None: + module.fail_json(msg='Ambigous, specifed both length and mask') + if '/' in prefix: + cidr = ip_network(to_text(prefix)) + prefix = str(cidr.network_address) + mask = str(cidr.netmask) + return prefix, mask + + +def map_params_to_obj(module, required_together=None): + keys = ['prefix', 'mask', 'next_hop', 'admin_distance', 'state'] + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + route = item.copy() + for key in keys: + if route.get(key) is None: + route[key] = module.params.get(key) + + module._check_required_together(required_together, route) + + prefix, mask = prefix_length_parser(route['prefix'], route['mask'], module) + route.update({'prefix': prefix, 'mask': mask}) + + obj.append(route) + else: + module._check_required_together(required_together, module.params) + prefix, mask = prefix_length_parser(module.params['prefix'], module.params['mask'], module) + + obj.append({ + 'prefix': prefix, + 'mask': mask, + 'next_hop': module.params['next_hop'].strip(), + 'admin_distance': module.params.get('admin_distance'), + 'state': module.params['state'], + }) + + for route in obj: + if route['admin_distance']: + route['admin_distance'] = str(route['admin_distance']) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + prefix=dict(type='str'), + mask=dict(type='str'), + next_hop=dict(type='str'), + admin_distance=dict(type='int'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['prefix'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['aggregate', 'prefix']] + required_together = [['prefix', 'next_hop']] + mutually_exclusive = [['aggregate', 'prefix']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + if not HAS_IPADDRESS: + module.fail_json(msg="ipaddress python package is required") + + warnings = list() + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_together=required_together) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_system.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_system.py new file mode 100644 index 00000000..bc20c174 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_system.py @@ -0,0 +1,466 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_system +author: "Ruckus Wireless (@Commscope)" +short_description: Manage the system attributes on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of node system attributes + on Ruckus ICX 7000 series switches. It provides an option to configure host system + parameters or remove those parameters from the device active + configuration. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + hostname: + description: + - Configure the device hostname parameter. This option takes an ASCII string value. + type: str + domain_name: + description: + - Configure the IP domain name on the remote device to the provided value. + Value should be in the dotted name form and + will be appended to the hostname to create a fully-qualified domain name. + type: list + domain_search: + description: + - Provides the list of domain names to + append to the hostname for the purpose of doing name resolution. + This argument accepts a list of names and will be reconciled + with the current active configuration on the running node. + type: list + name_servers: + description: + - List of DNS name servers by IP address to use to perform name resolution + lookups. + type: list + aaa_servers: + description: + - Configures radius/tacacs server + type: list + suboptions: + type: + description: + - specify the type of the server + type: str + choices: ['radius','tacacs'] + hostname: + description: + - Configures the host name of the RADIUS server + type: str + auth_port_type: + description: + - specifies the type of the authentication port + type: str + choices: ['auth-port'] + auth_port_num: + description: + - Configures the authentication UDP port. The default value is 1812. + type: str + acct_port_num: + description: + - Configures the accounting UDP port. The default value is 1813. + type: str + acct_type: + description: + - Usage of the accounting port. + type: str + choices: ['accounting-only', 'authentication-only','authorization-only', default] + auth_key: + description: + - Configure the key for the server + type: str + auth_key_type: + description: + - List of authentication level specified in the choices + type: list + choices: ['dot1x','mac-auth','web-auth'] + state: + description: + - State of the configuration + values in the device's current active configuration. When set + to I(present), the values should be configured in the device active + configuration and when set to I(absent) the values should not be + in the device active configuration + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Configure hostname and domain name + community.network.icx_system: + hostname: icx + domain_search: + - ansible.com + - redhat.com + - ruckus.com + +- name: Configure radius server of type auth-port + community.network.icx_system: + aaa_servers: + - type: radius + hostname: radius-server + auth_port_type: auth-port + auth_port_num: 1821 + acct_port_num: 1321 + acct_type: accounting-only + auth_key: abc + auth_key_type: + - dot1x + - mac-auth + +- name: Configure tacacs server + community.network.icx_system: + aaa_servers: + - type: tacacs + hostname: tacacs-server + auth_port_type: auth-port + auth_port_num: 1821 + acct_port_num: 1321 + acct_type: accounting-only + auth_key: xyz + +- name: Configure name servers + community.network.icx_system: + name_servers: + - 8.8.8.8 + - 8.8.4.4 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - hostname icx + - ip domain name test.example.com + - radius-server host 172.16.10.12 auth-port 2083 acct-port 1850 default key abc dot1x mac-auth + - tacacs-server host 10.2.3.4 auth-port 4058 authorization-only key xyz + +""" + + +import re +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, validate_ip_v6_address +from ansible.module_utils.connection import Connection, ConnectionError, exec_command + + +def diff_list(want, have): + adds = [w for w in want if w not in have] + removes = [h for h in have if h not in want] + return (adds, removes) + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params['state'] + + def needs_update(x): + return want.get(x) is not None and (want.get(x) != have.get(x)) + + if state == 'absent': + if have['name_servers'] == [] and have['aaa_servers'] == [] and have['domain_search'] == [] and have['hostname'] is None: + if want['hostname']: + commands.append('no hostname') + + if want['domain_search']: + for item in want['domain_search']: + commands.append('no ip dns domain-list %s' % item) + + if want['name_servers']: + for item in want['name_servers']: + commands.append('no ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + if want_server: + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + for item in want_servers: + ipv6addr = validate_ip_v6_address(item['hostname']) + if ipv6addr: + commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname']) + else: + commands.append('no ' + item['type'] + '-server host ' + item['hostname']) + + if want['hostname']: + if have['hostname'] == want['hostname']: + commands.append('no hostname') + + if want['domain_search']: + for item in want['domain_search']: + if item in have['domain_search']: + commands.append('no ip dns domain-list %s' % item) + + if want['name_servers']: + for item in want['name_servers']: + if item in have['name_servers']: + commands.append('no ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + have_server = have['aaa_servers'] + if want_server: + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + for item in want_servers: + if item in have_server: + ipv6addr = validate_ip_v6_address(item['hostname']) + if ipv6addr: + commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname']) + else: + commands.append('no ' + item['type'] + '-server host ' + item['hostname']) + + elif state == 'present': + if needs_update('hostname'): + commands.append('hostname %s' % want['hostname']) + + if want['domain_search']: + adds, removes = diff_list(want['domain_search'], have['domain_search']) + for item in removes: + commands.append('no ip dns domain-list %s' % item) + for item in adds: + commands.append('ip dns domain-list %s' % item) + + if want['name_servers']: + adds, removes = diff_list(want['name_servers'], have['name_servers']) + for item in removes: + commands.append('no ip dns server-address %s' % item) + for item in adds: + commands.append('ip dns server-address %s' % item) + + if want['aaa_servers']: + want_servers = [] + want_server = want['aaa_servers'] + have_server = have['aaa_servers'] + want_list = deepcopy(want_server) + for items in want_list: + items['auth_key'] = None + want_servers.append(items) + + adds, removes = diff_list(want_servers, have_server) + + for item in removes: + ip6addr = validate_ip_v6_address(item['hostname']) + if ip6addr: + cmd = 'no ' + item['type'] + '-server host ipv6 ' + item['hostname'] + else: + cmd = 'no ' + item['type'] + '-server host ' + item['hostname'] + commands.append(cmd) + + for w_item in adds: + for item in want_server: + if item['hostname'] == w_item['hostname'] and item['type'] == w_item['type']: + auth_key = item['auth_key'] + + ip6addr = validate_ip_v6_address(w_item['hostname']) + if ip6addr: + cmd = w_item['type'] + '-server host ipv6 ' + w_item['hostname'] + else: + cmd = w_item['type'] + '-server host ' + w_item['hostname'] + if w_item['auth_port_type']: + cmd += ' ' + w_item['auth_port_type'] + ' ' + w_item['auth_port_num'] + if w_item['acct_port_num'] and w_item['type'] == 'radius': + cmd += ' acct-port ' + w_item['acct_port_num'] + if w_item['type'] == 'tacacs': + if any((w_item['acct_port_num'], w_item['auth_key_type'])): + module.fail_json(msg='acct_port and auth_key_type is not applicable for tacacs server') + if w_item['acct_type']: + cmd += ' ' + w_item['acct_type'] + if auth_key is not None: + cmd += ' key ' + auth_key + if w_item['auth_key_type'] and w_item['type'] == 'radius': + val = '' + for y in w_item['auth_key_type']: + val = val + ' ' + y + cmd += val + commands.append(cmd) + + return commands + + +def parse_hostname(config): + match = re.search(r'^hostname (\S+)', config, re.M) + if match: + return match.group(1) + + +def parse_domain_search(config): + match = re.findall(r'^ip dns domain[- ]list (\S+)', config, re.M) + matches = list() + for name in match: + matches.append(name) + return matches + + +def parse_name_servers(config): + matches = list() + values = list() + lines = config.split('\n') + for line in lines: + if 'ip dns server-address' in line: + values = line.split(' ') + for val in values: + match = re.search(r'([0-9.]+)', val) + if match: + matches.append(match.group()) + + return matches + + +def parse_aaa_servers(config): + configlines = config.split('\n') + obj = [] + for line in configlines: + auth_key_type = [] + if 'radius-server' in line or 'tacacs-server' in line: + aaa_type = 'radius' if 'radius-server' in line else 'tacacs' + match = re.search(r'(host ipv6 (\S+))|(host (\S+))', line) + if match: + hostname = match.group(2) if match.group(2) is not None else match.group(4) + match = re.search(r'auth-port ([0-9]+)', line) + if match: + auth_port_num = match.group(1) + else: + auth_port_num = None + match = re.search(r'acct-port ([0-9]+)', line) + if match: + acct_port_num = match.group(1) + else: + acct_port_num = None + match = re.search(r'acct-port [0-9]+ (\S+)', line) + if match: + acct_type = match.group(1) + else: + acct_type = None + if aaa_type == 'tacacs': + match = re.search(r'auth-port [0-9]+ (\S+)', line) + if match: + acct_type = match.group(1) + else: + acct_type = None + match = re.search(r'(dot1x)', line) + if match: + auth_key_type.append('dot1x') + match = re.search(r'(mac-auth)', line) + if match: + auth_key_type.append('mac-auth') + match = re.search(r'(web-auth)', line) + if match: + auth_key_type.append('web-auth') + + obj.append({ + 'type': aaa_type, + 'hostname': hostname, + 'auth_port_type': 'auth-port', + 'auth_port_num': auth_port_num, + 'acct_port_num': acct_port_num, + 'acct_type': acct_type, + 'auth_key': None, + 'auth_key_type': set(auth_key_type) if len(auth_key_type) > 0 else None + }) + + return obj + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + config = get_config(module, None, compare=compare) + return { + 'hostname': parse_hostname(config), + 'domain_search': parse_domain_search(config), + 'name_servers': parse_name_servers(config), + 'aaa_servers': parse_aaa_servers(config) + } + + +def map_params_to_obj(module): + if module.params['aaa_servers']: + for item in module.params['aaa_servers']: + if item['auth_key_type']: + item['auth_key_type'] = set(item['auth_key_type']) + obj = { + 'hostname': module.params['hostname'], + 'domain_name': module.params['domain_name'], + 'domain_search': module.params['domain_search'], + 'name_servers': module.params['name_servers'], + 'state': module.params['state'], + 'aaa_servers': module.params['aaa_servers'] + } + return obj + + +def main(): + """ Main entry point for Ansible module execution + """ + server_spec = dict( + type=dict(choices=['radius', 'tacacs']), + hostname=dict(), + auth_port_type=dict(choices=['auth-port']), + auth_port_num=dict(), + acct_port_num=dict(), + acct_type=dict(choices=['accounting-only', 'authentication-only', 'authorization-only', 'default']), + auth_key=dict(type='str', no_log=True), + auth_key_type=dict(type='list', choices=['dot1x', 'mac-auth', 'web-auth']) + ) + argument_spec = dict( + hostname=dict(), + + domain_name=dict(type='list'), + domain_search=dict(type='list'), + name_servers=dict(type='list'), + + aaa_servers=dict(type='list', elements='dict', options=server_spec), + state=dict(choices=['present', 'absent'], default='present'), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands(want, have, module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_user.py new file mode 100644 index 00000000..8cd5f6c0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_user.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_user +author: "Ruckus Wireless (@Commscope)" +short_description: Manage the user accounts on Ruckus ICX 7000 series switches. +description: + - This module creates or updates user account on network devices. It allows playbooks to manage + either individual usernames or the aggregate of usernames in the + current running config. It also supports purging usernames from the + configuration that are not explicitly defined. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + aggregate: + description: + - The set of username objects to be configured on the remote + ICX device. The list entries can either be the username + or a hash of username and properties. This argument is mutually + exclusive with the C(name) argument. + aliases: ['users', 'collection'] + type: list + suboptions: + name: + description: + - The username to be configured on the ICX device. + required: true + type: str + configured_password: + description: The password to be configured on the ICX device. + type: str + update_password: + description: + - This argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + choices: ['on_create', 'always'] + type: str + privilege: + description: + - The privilege level to be granted to the user + choices: ['0', '4', '5'] + type: str + nopassword: + description: + - Defines the username without assigning + a password. This will allow the user to login to the system + without being authenticated by a password. + type: bool + state: + description: + - Configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + choices: ['present', 'absent'] + type: str + access_time: + description: + - This parameter indicates the time the file's access time should be set to. + Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now. + Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + name: + description: + - The username to be configured on the ICX device. + required: true + type: str + configured_password: + description: The password to be configured on the ICX device. + type: str + update_password: + description: + - This argument will instruct the module when to change the password. When + set to C(always), the password will always be updated in the device + and when set to C(on_create) the password will be updated only if + the username is created. + default: always + choices: ['on_create', 'always'] + type: str + privilege: + description: + - The privilege level to be granted to the user + default: 0 + choices: ['0', '4', '5'] + type: str + nopassword: + description: + - Defines the username without assigning + a password. This will allow the user to login to the system + without being authenticated by a password. + type: bool + default: false + purge: + description: + - If set to true module will remove any previously + configured usernames on the device except the current defined set of users. + type: bool + default: false + state: + description: + - Configures the state of the username definition + as it relates to the device operational configuration. When set + to I(present), the username(s) should be configured in the device active + configuration and when set to I(absent) the username(s) should not be + in the device active configuration + default: present + choices: ['present', 'absent'] + type: str + access_time: + description: + - This parameter indicates the time the file's access time should be set to. + Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now. + Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Create a new user without password + community.network.icx_user: + name: user1 + nopassword: true + +- name: Create a new user with password + community.network.icx_user: + name: user1 + configured_password: 'newpassword' + +- name: Remove users + community.network.icx_user: + name: user1 + state: absent + +- name: Set user privilege level to 5 + community.network.icx_user: + name: user1 + privilege: 5 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - username ansible nopassword + - username ansible password-string alethea123 + - no username ansible + - username ansible privilege 5 + - username ansible enable +""" + +from copy import deepcopy + +import re +import base64 +import hashlib + +from functools import partial + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible.module_utils.connection import exec_command +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config + + +def get_param_value(key, item, module): + if not item.get(key): + value = module.params[key] + + else: + value_type = module.argument_spec[key].get('type', 'str') + type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] + type_checker(item[key]) + value = item[key] + + validator = globals().get('validate_%s' % key) + if all((value, validator)): + validator(value, module) + + return value + + +def map_params_to_obj(module): + users = module.params['aggregate'] + if not users: + if not module.params['name'] and module.params['purge']: + return list() + elif not module.params['name']: + module.fail_json(msg='username is required') + else: + aggregate = [{'name': module.params['name']}] + else: + aggregate = list() + for item in users: + if not isinstance(item, dict): + aggregate.append({'name': item}) + elif 'name' not in item: + module.fail_json(msg='name is required') + else: + aggregate.append(item) + + objects = list() + + for item in aggregate: + get_value = partial(get_param_value, item=item, module=module) + item['configured_password'] = get_value('configured_password') + item['nopassword'] = get_value('nopassword') + item['privilege'] = get_value('privilege') + item['state'] = get_value('state') + objects.append(item) + + return objects + + +def parse_privilege(data): + match = re.search(r'privilege (\S)', data, re.M) + if match: + return match.group(1) + + +def map_config_to_obj(module): + compare = module.params['check_running_config'] + data = get_config(module, flags=['| include username'], compare=compare) + + match = re.findall(r'(?:^(?:u|\s{2}u))sername (\S+)', data, re.M) + if not match: + return list() + + instances = list() + + for user in set(match): + regex = r'username %s .+$' % user + cfg = re.findall(regex, data, re.M) + cfg = '\n'.join(cfg) + obj = { + 'name': user, + 'state': 'present', + 'nopassword': 'nopassword' in cfg, + 'configured_password': None, + 'privilege': parse_privilege(cfg) + } + instances.append(obj) + + return instances + + +def map_obj_to_commands(updates, module): + commands = list() + state = module.params['state'] + update_password = module.params['update_password'] + + def needs_update(want, have, x): + return want.get(x) and (want.get(x) != have.get(x)) + + def add(command, want, x): + command.append('username %s %s' % (want['name'], x)) + for update in updates: + want, have = update + if want['state'] == 'absent': + commands.append(user_del_cmd(want['name'])) + + if needs_update(want, have, 'privilege'): + add(commands, want, 'privilege %s password %s' % (want['privilege'], want['configured_password'])) + else: + if needs_update(want, have, 'configured_password'): + if update_password == 'always' or not have: + add(commands, want, '%spassword %s' % ('privilege ' + str(have.get('privilege')) + + " " if have.get('privilege') is not None else '', want['configured_password'])) + + if needs_update(want, have, 'nopassword'): + if want['nopassword']: + add(commands, want, 'nopassword') + + if needs_update(want, have, 'access_time'): + add(commands, want, 'access-time %s' % want['access_time']) + + if needs_update(want, have, 'expiry_days'): + add(commands, want, 'expires %s' % want['expiry_days']) + + return commands + + +def update_objects(want, have): + updates = list() + for entry in want: + item = next((i for i in have if i['name'] == entry['name']), None) + + if all((item is None, entry['state'] == 'present')): + updates.append((entry, {})) + + elif all((have == [], entry['state'] == 'absent')): + for key, value in iteritems(entry): + if key not in ['update_password']: + updates.append((entry, item)) + break + elif item: + for key, value in iteritems(entry): + if key not in ['update_password']: + if value is not None and value != item.get(key): + updates.append((entry, item)) + break + return updates + + +def user_del_cmd(username): + return 'no username %s' % username + + +def main(): + """entry point for module execution + """ + element_spec = dict( + name=dict(), + + configured_password=dict(no_log=True), + nopassword=dict(type='bool', default=False), + update_password=dict(default='always', choices=['on_create', 'always']), + privilege=dict(type='str', choices=['0', '4', '5']), + access_time=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users', 'collection']), + purge=dict(type='bool', default=False) + ) + + argument_spec.update(element_spec) + + mutually_exclusive = [('name', 'aggregate')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + result = {'changed': False} + exec_command(module, 'skip') + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands(update_objects(want, have), module) + + if module.params['purge']: + want_users = [x['name'] for x in want] + have_users = [x['name'] for x in have] + for item in set(have_users).difference(want_users): + if item != 'admin': + commands.append(user_del_cmd(item)) + + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_vlan.py new file mode 100644 index 00000000..4c102705 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/icx/icx_vlan.py @@ -0,0 +1,779 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: icx_vlan +author: "Ruckus Wireless (@Commscope)" +short_description: Manage VLANs on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of VLANs + on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: int + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + delay: + description: + - Delay the play should wait to check for declarative intent params values. + default: 10 + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specify the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + aggregate: + description: + - List of VLANs definitions. + type: list + suboptions: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: str + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + delay: + description: + - Delay the play should wait to check for declarative intent params values. + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specify the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + state: + description: + - State of the VLAN configuration. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the VLAN configuration. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. + type: bool + default: yes +''' + +EXAMPLES = """ +- name: Add a single ethernet 1/1/48 as access(untagged) port to vlan 20 + community.network.icx_vlan: + name: test-vlan + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/48 + +- name: Add a single LAG 10 as access(untagged) port to vlan 20 + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - lag 10 + +- name: Add a range of ethernet ports as trunk(tagged) ports to vlan 20 by port + community.network.icx_vlan: + vlan_id: 20 + tagged: + name: + - ethernet 1/1/40 to 1/1/48 + +- name: Add discontinuous lags, ethernet ports as access(untagged) and trunk(tagged) port to vlan 20. + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 to 1/1/48 + - ethernet 2/1/1 + - lag 1 + - lag 3 to 5 + tagged: + name: + - ethernet 1/1/20 to 1/1/25 + - lag 1 to 3 + +- name: Remove an access and range of trunk ports from vlan + community.network.icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 + tagged: + name: + - ethernet 1/1/39 to 1/1/70 + +- name: Enable dhcp snooping, disable arp inspection in vlan + community.network.icx_vlan: + vlan_id: 20 + ip_dhcp_snooping: present + ip_arp_inspection: absent + +- name: Create vlan 20. Enable arp inspection in vlan. Purge all other vlans. + community.network.icx_vlan: + vlan_id: 20 + ip_arp_inspection: present + purge: present + +- name: Remove vlan 20. + community.network.icx_vlan: + vlan_id: 20 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +from time import sleep +import itertools +from copy import deepcopy +from time import sleep +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def search_obj_in_list(vlan_id, lst): + obj = list() + for o in lst: + if str(o['vlan_id']) == vlan_id: + return o + + +def parse_vlan_brief(module, vlan_id): + command = 'show run vlan %s' % vlan_id + rc, out, err = exec_command(module, command) + lines = out.split('\n') + untagged_ports = list() + untagged_lags = list() + tagged_ports = list() + tagged_lags = list() + + for line in lines: + if 'tagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + tagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + tagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + tagged_lags.append((int(l[0]) + i)) + else: + tagged_lags.append(int(lag)) + if 'untagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + untagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + untagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + untagged_lags.append((int(l[0]) + i)) + else: + untagged_lags.append(int(lag)) + + return untagged_ports, untagged_lags, tagged_ports, tagged_lags + + +def extract_list_from_interface(interface): + if 'ethernet' in interface: + if 'to' in interface: + s = re.search(r"\d+\/\d+/(?P\d+)\sto\s+\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + elif 'lag' in interface: + if 'to' in interface: + s = re.search(r"(?P\d+)\sto\s(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + + return low, high + + +def parse_vlan_id(module): + vlans = [] + command = 'show vlan brief' + rc, out, err = exec_command(module, command) + lines = out.split('\n') + for line in lines: + if 'VLANs Configured :' in line: + values = line.split(':')[1] + vlans = [s for s in values.split() if s.isdigit()] + s = re.findall(r"(?P\d+)\sto\s(?P\d+)", values) + for ranges in s: + low = int(ranges[0]) + 1 + high = int(ranges[1]) + while(high > low): + vlans.append(str(low)) + low = low + 1 + return vlans + + +def spanning_tree(module, stp): + stp_cmd = list() + if stp.get('enabled') is False: + if stp.get('type') == '802-1w': + stp_cmd.append('no spanning-tree' + ' ' + stp.get('type')) + stp_cmd.append('no spanning-tree') + + elif stp.get('type'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type')) + if stp.get('priority') and stp.get('type') == 'rstp': + module.fail_json(msg='spanning-tree 802-1w only can have priority') + elif stp.get('priority'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type') + ' ' + 'priority' + ' ' + stp.get('priority')) + + return stp_cmd + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + stp = item.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + item.update({'stp': stp_cmd}) + + d = item.copy() + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'vlan_id': module.params['vlan_id'], + 'interfaces': module.params['interfaces'], + 'tagged': module.params['tagged'], + 'associated_interfaces': module.params['associated_interfaces'], + 'associated_tagged': module.params['associated_tagged'], + 'delay': module.params['delay'], + 'ip_dhcp_snooping': module.params['ip_dhcp_snooping'], + 'ip_arp_inspection': module.params['ip_arp_inspection'], + 'state': module.params['state'], + } + + stp = module.params.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + params.update({'stp': stp_cmd}) + + obj.append(params) + + return obj + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + state = w['state'] + name = w['name'] + interfaces = w.get('interfaces') + tagged = w.get('tagged') + dhcp = w.get('ip_dhcp_snooping') + arp = w.get('ip_arp_inspection') + stp = w.get('stp') + obj_in_have = search_obj_in_list(str(vlan_id), have) + + if state == 'absent': + if have == []: + commands.append('no vlan {0}'.format(vlan_id)) + if obj_in_have: + commands.append('no vlan {0}'.format(vlan_id)) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + for item in interfaces['name']: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + for item in tagged['name']: + commands.append('tagged {0}'.format(item)) + + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + else: + commands.append('vlan {0}'.format(vlan_id)) + if name: + if name != obj_in_have['name']: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + have_interfaces = list() + for interface in interfaces['name']: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + have_interfaces.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in interface: + have_interfaces.append('lag {0}'.format(low)) + low = low + 1 + + if interfaces['purge'] is True: + remove_interfaces = list(set(obj_in_have['interfaces']) - set(have_interfaces)) + for item in remove_interfaces: + commands.append('no untagged {0}'.format(item)) + + if interfaces['name']: + add_interfaces = list(set(have_interfaces) - set(obj_in_have['interfaces'])) + for item in add_interfaces: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + have_tagged = list() + for tag in tagged['name']: + low, high = extract_list_from_interface(tag) + + while(high >= low): + if 'ethernet' in tag: + have_tagged.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in tag: + have_tagged.append('lag {0}'.format(low)) + low = low + 1 + if tagged['purge'] is True: + remove_tagged = list(set(obj_in_have['tagged']) - set(have_tagged)) + for item in remove_tagged: + commands.append('no tagged {0}'.format(item)) + + if tagged['name']: + add_tagged = list(set(have_tagged) - set(obj_in_have['tagged'])) + for item in add_tagged: + commands.append('tagged {0}'.format(item)) + + if dhcp != obj_in_have['ip_dhcp_snooping']: + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp != obj_in_have['ip_arp_inspection']: + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif arp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + if len(commands) == 1 and 'vlan ' + str(vlan_id) in commands: + commands = [] + + if purge: + commands = [] + vlans = parse_vlan_id(module) + for h in vlans: + obj_in_want = search_obj_in_list(h, want) + if not obj_in_want and h != '1': + commands.append('no vlan {0}'.format(h)) + + return commands + + +def parse_name_argument(module, item): + command = 'show vlan {0}'.format(item) + rc, out, err = exec_command(module, command) + match = re.search(r"Name (\S+),", out) + if match: + return match.group(1) + + +def parse_interfaces_argument(module, item, port_type): + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, item) + ports = list() + if port_type == "interfaces": + if untagged_ports: + for port in untagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if untagged_lags: + for port in untagged_lags: + ports.append('lag ' + str(port)) + + elif port_type == "tagged": + if tagged_ports: + for port in tagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if tagged_lags: + for port in tagged_lags: + ports.append('lag ' + str(port)) + + return ports + + +def parse_config_argument(config, arg): + match = re.search(arg, config, re.M) + if match: + return True + else: + return False + + +def map_config_to_obj(module): + config = get_config(module) + vlans = parse_vlan_id(module) + instance = list() + + for item in set(vlans): + obj = { + 'vlan_id': item, + 'name': parse_name_argument(module, item), + 'interfaces': parse_interfaces_argument(module, item, 'interfaces'), + 'tagged': parse_interfaces_argument(module, item, 'tagged'), + 'ip_dhcp_snooping': parse_config_argument(config, 'ip dhcp snooping vlan {0}'.format(item)), + 'ip_arp_inspection': parse_config_argument(config, 'ip arp inspection vlan {0}'.format(item)), + } + instance.append(obj) + return instance + + +def check_fail(module, output): + error = [ + re.compile(r"^error", re.I) + ] + for x in output: + for regex in error: + if regex.search(x): + module.fail_json(msg=x) + + +def check_declarative_intent_params(want, module, result): + def parse_ports(interfaces, ports, lags): + for interface in interfaces: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + if not (low in ports): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + if 'lag' in interface: + if not (low in lags): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + low = low + 1 + + is_delay = False + low = 0 + high = 0 + for w in want: + if w.get('associated_interfaces') is None and w.get('associated_tagged') is None: + continue + + if result['changed'] and not is_delay: + sleep(module.params['delay']) + is_delay = True + + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, w['vlan_id']) + + if w['associated_interfaces']: + parse_ports(w.get('associated_interfaces'), untagged_ports, untagged_lags) + + if w['associated_tagged']: + parse_ports(w.get('associated_tagged'), tagged_ports, tagged_lags) + + +def main(): + """ main entry point for module execution + """ + stp_spec = dict( + type=dict(default='802-1w', choices=['802-1w', 'rstp']), + priority=dict(), + enabled=dict(type='bool'), + ) + inter_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + tagged_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='dict', options=inter_spec), + tagged=dict(type='dict', options=tagged_spec), + ip_dhcp_snooping=dict(type='bool'), + ip_arp_inspection=dict(type='bool'), + associated_interfaces=dict(type='list'), + associated_tagged=dict(type='list'), + delay=dict(default=10, type='int'), + stp=dict(type='dict', options=stp_spec), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + argument_spec.update(element_spec) + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {} + result['changed'] = False + if warnings: + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + if module.params['check_running_config'] is False: + have = [] + else: + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + output = load_config(module, commands) + if output: + check_fail(module, output) + result['output'] = output + result['changed'] = True + + check_declarative_intent_params(want, module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_etherstub.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_etherstub.py new file mode 100644 index 00000000..c8c7591a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_etherstub.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_etherstub +short_description: Manage etherstubs on Solaris/illumos systems. +description: + - Create or delete etherstubs on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - Etherstub name. + required: true + temporary: + description: + - Specifies that the etherstub is temporary. Temporary etherstubs + do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create or delete Solaris/illumos etherstub. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create 'stub0' etherstub + community.network.dladm_etherstub: + name: stub0 + state: present + +- name: Remove 'stub0 etherstub + community.network.dladm_etherstub: + name: stub0 + state: absent +''' + +RETURN = ''' +name: + description: etherstub name + returned: always + type: str + sample: "switch0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: etherstub's persistence + returned: always + type: bool + sample: "True" +''' +from ansible.module_utils.basic import AnsibleModule + + +class Etherstub(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def etherstub_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-etherstub') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + etherstub = Etherstub(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = etherstub.name + result['state'] = etherstub.state + result['temporary'] = etherstub.temporary + + if etherstub.state == 'absent': + if etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.delete_etherstub() + if rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + elif etherstub.state == 'present': + if not etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.create_etherstub() + + if rc is not None and rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_iptun.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_iptun.py new file mode 100644 index 00000000..2b2d3175 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_iptun.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_iptun +short_description: Manage IP tunnel interfaces on Solaris/illumos systems. +description: + - Manage IP tunnel interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - IP tunnel interface name. + required: true + temporary: + description: + - Specifies that the IP tunnel interface is temporary. Temporary IP tunnel + interfaces do not persist across reboots. + required: false + default: false + type: bool + type: + description: + - Specifies the type of tunnel to be created. + required: false + default: "ipv4" + choices: [ "ipv4", "ipv6", "6to4" ] + aliases: ['tunnel_type'] + local_address: + description: + - Literal IP address or hostname corresponding to the tunnel source. + required: false + aliases: [ "local" ] + remote_address: + description: + - Literal IP address or hostname corresponding to the tunnel destination. + required: false + aliases: [ "remote" ] + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create IPv4 tunnel interface 'iptun0' + community.network.dladm_iptun: name=iptun0 local_address=192.0.2.23 remote_address=203.0.113.10 state=present + +- name: Change IPv4 tunnel remote address + community.network.dladm_iptun: name=iptun0 type=ipv4 local_address=192.0.2.23 remote_address=203.0.113.11 + +- name: Create IPv6 tunnel interface 'tun0' + community.network.dladm_iptun: name=tun0 type=ipv6 local_address=192.0.2.23 remote_address=203.0.113.42 + +- name: Remove 'iptun0' tunnel interface + community.network.dladm_iptun: name=iptun0 state=absent +''' + +RETURN = ''' +name: + description: tunnel interface name + returned: always + type: str + sample: iptun0 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +local_address: + description: local IP address + returned: always + type: str + sample: 1.1.1.1/32 +remote_address: + description: remote IP address + returned: always + type: str + sample: 2.2.2.2/32 +type: + description: tunnel type + returned: always + type: str + sample: ipv4 +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TYPES = ['ipv4', 'ipv6', '6to4'] + + +class IPTun(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.type = module.params['type'] + self.local_address = module.params['local_address'] + self.remote_address = module.params['remote_address'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self.dladm_bin = self.module.get_bin_path('dladm', True) + + def iptun_exists(self): + cmd = [self.dladm_bin] + + cmd.append('show-iptun') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('create-iptun') + + if self.temporary: + cmd.append('-t') + + cmd.append('-T') + cmd.append(self.type) + cmd.append('-a') + cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('delete-iptun') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def update_iptun(self): + cmd = [self.dladm_bin] + + cmd.append('modify-iptun') + + if self.temporary: + cmd.append('-t') + cmd.append('-a') + cmd.append('local=' + self.local_address + ',remote=' + self.remote_address) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def _query_iptun_props(self): + cmd = [self.dladm_bin] + + cmd.append('show-iptun') + cmd.append('-p') + cmd.append('-c') + cmd.append('link,type,flags,local,remote') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def iptun_needs_updating(self): + (rc, out, err) = self._query_iptun_props() + + NEEDS_UPDATING = False + + if rc == 0: + configured_local, configured_remote = out.split(':')[3:] + + if self.local_address != configured_local or self.remote_address != configured_remote: + NEEDS_UPDATING = True + + return NEEDS_UPDATING + else: + self.module.fail_json(msg='Failed to query tunnel interface %s properties' % self.name, + err=err, + rc=rc) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, type='str'), + type=dict(default='ipv4', type='str', aliases=['tunnel_type'], + choices=SUPPORTED_TYPES), + local_address=dict(type='str', aliases=['local']), + remote_address=dict(type='str', aliases=['remote']), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + required_if=[ + ['state', 'present', ['local_address', 'remote_address']], + ], + supports_check_mode=True + ) + + iptun = IPTun(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = iptun.name + result['type'] = iptun.type + result['local_address'] = iptun.local_address + result['remote_address'] = iptun.remote_address + result['state'] = iptun.state + result['temporary'] = iptun.temporary + + if iptun.state == 'absent': + if iptun.iptun_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = iptun.delete_iptun() + if rc != 0: + module.fail_json(name=iptun.name, msg=err, rc=rc) + elif iptun.state == 'present': + if not iptun.iptun_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = iptun.create_iptun() + + if rc is not None and rc != 0: + module.fail_json(name=iptun.name, msg=err, rc=rc) + else: + if iptun.iptun_needs_updating(): + (rc, out, err) = iptun.update_iptun() + if rc != 0: + module.fail_json(msg='Error while updating tunnel interface: "%s"' % err, + name=iptun.name, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_linkprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_linkprop.py new file mode 100644 index 00000000..25247a69 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_linkprop.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_linkprop +short_description: Manage link properties on Solaris/illumos systems. +description: + - Set / reset link properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + link: + description: + - Link interface name. + required: true + aliases: [ "nic", "interface" ] + property: + description: + - Specifies the name of the property we want to manage. + required: true + aliases: [ "name" ] + value: + description: + - Specifies the value we want to set for the link property. + required: false + temporary: + description: + - Specifies that lin property configuration is temporary. Temporary + link property configuration does not persist across reboots. + required: false + type: bool + default: false + state: + description: + - Set or reset the property value. + required: false + default: "present" + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Set 'maxbw' to 100M on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=maxbw value=100M state=present + +- name: Set 'mtu' to 9000 on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=mtu value=9000 + +- name: Reset 'mtu' property on e1000g1 + community.network.dladm_linkprop: name=e1000g1 property=mtu state=reset +''' + +RETURN = ''' +property: + description: property name + returned: always + type: str + sample: mtu +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +link: + description: link name + returned: always + type: str + sample: e100g0 +value: + description: property value + returned: always + type: str + sample: 9000 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class LinkProp(object): + + def __init__(self, module): + self.module = module + + self.link = module.params['link'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self.dladm_bin = self.module.get_bin_path('dladm', True) + + def property_exists(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" on link %s' % + (self.property, self.link), + property=self.property, + link=self.link) + + def property_is_modified(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('value,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_readonly(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('perm') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and out == 'r-': + return True + else: + return False + + def property_is_set(self): + cmd = [self.dladm_bin] + + cmd.append('show-linkprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('value') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.dladm_bin] + + cmd.append('set-linkprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + '=' + self.value) + cmd.append(self.link) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.dladm_bin] + + cmd.append('reset-linkprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.link) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + link=dict(required=True, default=None, type='str', aliases=['nic', 'interface']), + property=dict(required=True, type='str', aliases=['name']), + value=dict(required=False, type='str'), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + required_if=[ + ['state', 'present', ['value']], + ], + + supports_check_mode=True + ) + + linkprop = LinkProp(module) + + rc = None + out = '' + err = '' + result = {} + result['property'] = linkprop.property + result['link'] = linkprop.link + result['state'] = linkprop.state + if linkprop.value: + result['value'] = linkprop.value + + if linkprop.state == 'absent' or linkprop.state == 'reset': + if linkprop.property_exists(): + if not linkprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = linkprop.reset_property() + if rc != 0: + module.fail_json(property=linkprop.property, + link=linkprop.link, + msg=err, + rc=rc) + + elif linkprop.state == 'present': + if linkprop.property_exists(): + if not linkprop.property_is_readonly(): + if not linkprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = linkprop.set_property() + if rc != 0: + module.fail_json(property=linkprop.property, + link=linkprop.link, + msg=err, + rc=rc) + else: + module.fail_json(msg='Property "%s" is read-only!' % (linkprop.property), + property=linkprop.property, + link=linkprop.link) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vlan.py new file mode 100644 index 00000000..8ed48633 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vlan.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_vlan +short_description: Manage VLAN interfaces on Solaris/illumos systems. +description: + - Create or delete VLAN interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - VLAN interface name. + required: true + link: + description: + - VLAN underlying link name. + required: true + temporary: + description: + - Specifies that the VLAN interface is temporary. Temporary VLANs + do not persist across reboots. + required: false + default: false + type: bool + vlan_id: + description: + - VLAN ID value for VLAN interface. + required: false + default: false + aliases: [ "vid" ] + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +- name: Create 'vlan42' VLAN over 'bnx0' link + community.network.dladm_vlan: name=vlan42 link=bnx0 vlan_id=42 state=present + +- name: Remove 'vlan1337' VLAN interface + community.network.dladm_vlan: name=vlan1337 state=absent +''' + +RETURN = ''' +name: + description: VLAN name + returned: always + type: str + sample: vlan42 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +link: + description: VLAN's underlying link name + returned: always + type: str + sample: e100g0 +vlan_id: + description: VLAN ID + returned: always + type: str + sample: 42 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class VLAN(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.vlan_id = module.params['vlan_id'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def vlan_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-vlan') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_vlan(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-vlan') + + if self.temporary: + cmd.append('-t') + + cmd.append('-l') + cmd.append(self.link) + cmd.append('-v') + cmd.append(self.vlan_id) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_vlan(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-vlan') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def is_valid_vlan_id(self): + + return 0 <= int(self.vlan_id) <= 4095 + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, type='str'), + link=dict(default=None, type='str'), + vlan_id=dict(default=0, aliases=['vid']), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + required_if=[ + ['state', 'present', ['vlan_id', 'link', 'name']], + ], + supports_check_mode=True + ) + + vlan = VLAN(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = vlan.name + result['link'] = vlan.link + result['state'] = vlan.state + result['temporary'] = vlan.temporary + + if int(vlan.vlan_id) != 0: + if not vlan.is_valid_vlan_id(): + module.fail_json(msg='Invalid VLAN id value', + name=vlan.name, + state=vlan.state, + link=vlan.link, + vlan_id=vlan.vlan_id) + result['vlan_id'] = vlan.vlan_id + + if vlan.state == 'absent': + if vlan.vlan_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vlan.delete_vlan() + if rc != 0: + module.fail_json(name=vlan.name, msg=err, rc=rc) + elif vlan.state == 'present': + if not vlan.vlan_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vlan.create_vlan() + + if rc is not None and rc != 0: + module.fail_json(name=vlan.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vnic.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vnic.py new file mode 100644 index 00000000..c7e30c5d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/dladm_vnic.py @@ -0,0 +1,265 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: dladm_vnic +short_description: Manage VNICs on Solaris/illumos systems. +description: + - Create or delete VNICs on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - VNIC name. + required: true + type: str + link: + description: + - VNIC underlying link name. + required: true + type: str + temporary: + description: + - Specifies that the VNIC is temporary. Temporary VNICs + do not persist across reboots. + required: false + default: false + type: bool + mac: + description: + - Sets the VNIC's MAC address. Must be valid unicast MAC address. + required: false + default: false + aliases: [ "macaddr" ] + type: str + vlan: + description: + - Enable VLAN tagging for this VNIC. The VLAN tag will have id + I(vlan). + required: false + default: false + aliases: [ "vlan_id" ] + type: int + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] + type: str +''' + +EXAMPLES = ''' +- name: Create 'vnic0' VNIC over 'bnx0' link + community.network.dladm_vnic: + name: vnic0 + link: bnx0 + state: present + +- name: Create VNIC with specified MAC and VLAN tag over 'aggr0' + community.network.dladm_vnic: + name: vnic1 + link: aggr0 + mac: '00:00:5E:00:53:23' + vlan: 4 + +- name: Remove 'vnic0' VNIC + community.network.dladm_vnic: + name: vnic0 + link: bnx0 + state: absent +''' + +RETURN = ''' +name: + description: VNIC name + returned: always + type: str + sample: "vnic0" +link: + description: VNIC underlying link name + returned: always + type: str + sample: "igb0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: VNIC's persistence + returned: always + type: bool + sample: "True" +mac: + description: MAC address to use for VNIC + returned: if mac is specified + type: str + sample: "00:00:5E:00:53:42" +vlan: + description: VLAN to use for VNIC + returned: success + type: int + sample: 42 +''' + +import re + +from ansible.module_utils.basic import AnsibleModule + + +class VNIC(object): + + UNICAST_MAC_REGEX = r'^[a-f0-9][2-9a-f0]:([a-f0-9]{2}:){4}[a-f0-9]{2}$' + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.mac = module.params['mac'] + self.vlan = module.params['vlan'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def vnic_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-vnic') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-vnic') + + if self.temporary: + cmd.append('-t') + + if self.mac: + cmd.append('-m') + cmd.append(self.mac) + + if self.vlan: + cmd.append('-v') + cmd.append(self.vlan) + + cmd.append('-l') + cmd.append(self.link) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-vnic') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def is_valid_unicast_mac(self): + + mac_re = re.match(self.UNICAST_MAC_REGEX, self.mac) + + return mac_re is not None + + def is_valid_vlan_id(self): + + return 0 < self.vlan < 4095 + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + link=dict(type='str', required=True), + mac=dict(type='str', aliases=['macaddr']), + vlan=dict(type='int', aliases=['vlan_id']), + temporary=dict(type='bool', default=False), + state=dict(type='str', default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + vnic = VNIC(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = vnic.name + result['link'] = vnic.link + result['state'] = vnic.state + result['temporary'] = vnic.temporary + + if vnic.mac is not None: + if not vnic.is_valid_unicast_mac(): + module.fail_json(msg='Invalid unicast MAC address', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['mac'] = vnic.mac + + if vnic.vlan is not None: + if not vnic.is_valid_vlan_id(): + module.fail_json(msg='Invalid VLAN tag', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['vlan'] = vnic.vlan + + if vnic.state == 'absent': + if vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.delete_vnic() + if rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + elif vnic.state == 'present': + if not vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.create_vnic() + + if rc is not None and rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/flowadm.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/flowadm.py new file mode 100644 index 00000000..1dce95dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/flowadm.py @@ -0,0 +1,508 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: flowadm +short_description: Manage bandwidth resource control and priority for protocols, services and zones on Solaris/illumos systems +description: + - Create/modify/remove networking bandwidth and associated resources for a type of traffic on a particular link. +author: Adam Å tevko (@xen0l) +options: + name: + description: > + - A flow is defined as a set of attributes based on Layer 3 and Layer 4 + headers, which can be used to identify a protocol, service, or a zone. + required: true + aliases: [ 'flow' ] + link: + description: + - Specifiies a link to configure flow on. + required: false + local_ip: + description: + - Identifies a network flow by the local IP address. + required: false + remote_ip: + description: + - Identifies a network flow by the remote IP address. + required: false + transport: + description: > + - Specifies a Layer 4 protocol to be used. It is typically used in combination with I(local_port) to + identify the service that needs special attention. + required: false + local_port: + description: + - Identifies a service specified by the local port. + required: false + dsfield: + description: > + - Identifies the 8-bit differentiated services field (as defined in + RFC 2474). The optional dsfield_mask is used to state the bits of interest in + the differentiated services field when comparing with the dsfield + value. Both values must be in hexadecimal. + required: false + maxbw: + description: > + - Sets the full duplex bandwidth for the flow. The bandwidth is + specified as an integer with one of the scale suffixes(K, M, or G + for Kbps, Mbps, and Gbps). If no units are specified, the input + value will be read as Mbps. + required: false + priority: + description: + - Sets the relative priority for the flow. + required: false + default: 'medium' + choices: [ 'low', 'medium', 'high' ] + temporary: + description: + - Specifies that the configured flow is temporary. Temporary + flows do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create/delete/enable/disable an IP address on the network interface. + required: false + default: present + choices: [ 'absent', 'present', 'resetted' ] +''' + +EXAMPLES = ''' +- name: Limit SSH traffic to 100M via vnic0 interface + community.network.flowadm: + link: vnic0 + flow: ssh_out + transport: tcp + local_port: 22 + maxbw: 100M + state: present + +- name: Reset flow properties + community.network.flowadm: + name: dns + state: resetted + +- name: Configure policy for EF PHB (DSCP value of 101110 from RFC 2598) with a bandwidth of 500 Mbps and a high priority + community.network.flowadm: + link: bge0 + dsfield: '0x2e:0xfc' + maxbw: 500M + priority: high + flow: efphb-flow + state: present +''' + +RETURN = ''' +name: + description: flow name + returned: always + type: str + sample: "http_drop" +link: + description: flow's link + returned: if link is defined + type: str + sample: "vnic0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: flow's persistence + returned: always + type: bool + sample: "True" +priority: + description: flow's priority + returned: if priority is defined + type: str + sample: "low" +transport: + description: flow's transport + returned: if transport is defined + type: str + sample: "tcp" +maxbw: + description: flow's maximum bandwidth + returned: if maxbw is defined + type: str + sample: "100M" +local_Ip: + description: flow's local IP address + returned: if local_ip is defined + type: str + sample: "10.0.0.42" +local_port: + description: flow's local port + returned: if local_port is defined + type: int + sample: 1337 +remote_Ip: + description: flow's remote IP address + returned: if remote_ip is defined + type: str + sample: "10.0.0.42" +dsfield: + description: flow's differentiated services value + returned: if dsfield is defined + type: str + sample: "0x2e:0xfc" +''' + + +import socket + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TRANSPORTS = ['tcp', 'udp', 'sctp', 'icmp', 'icmpv6'] +SUPPORTED_PRIORITIES = ['low', 'medium', 'high'] + +SUPPORTED_ATTRIBUTES = ['local_ip', 'remote_ip', 'transport', 'local_port', 'dsfield'] +SUPPORTPED_PROPERTIES = ['maxbw', 'priority'] + + +class Flow(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.local_ip = module.params['local_ip'] + self.remote_ip = module.params['remote_ip'] + self.transport = module.params['transport'] + self.local_port = module.params['local_port'] + self.dsfield = module.params['dsfield'] + self.maxbw = module.params['maxbw'] + self.priority = module.params['priority'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self._needs_updating = { + 'maxbw': False, + 'priority': False, + } + + @classmethod + def is_valid_port(cls, port): + return 1 <= int(port) <= 65535 + + @classmethod + def is_valid_address(cls, ip): + + if ip.count('/') == 1: + ip_address, netmask = ip.split('/') + else: + ip_address = ip + + if len(ip_address.split('.')) == 4: + try: + socket.inet_pton(socket.AF_INET, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 32: + return False + else: + try: + socket.inet_pton(socket.AF_INET6, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 128: + return False + + return True + + @classmethod + def is_hex(cls, number): + try: + int(number, 16) + except ValueError: + return False + + return True + + @classmethod + def is_valid_dsfield(cls, dsfield): + + dsmask = None + + if dsfield.count(':') == 1: + dsval = dsfield.split(':')[0] + else: + dsval, dsmask = dsfield.split(':') + + if dsmask and not 0x01 <= int(dsmask, 16) <= 0xff and not 0x01 <= int(dsval, 16) <= 0xff: + return False + elif not 0x01 <= int(dsval, 16) <= 0xff: + return False + + return True + + def flow_exists(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flow') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def delete_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('remove-flow') + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def create_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('add-flow') + cmd.append('-l') + cmd.append(self.link) + + if self.local_ip: + cmd.append('-a') + cmd.append('local_ip=' + self.local_ip) + + if self.remote_ip: + cmd.append('-a') + cmd.append('remote_ip=' + self.remote_ip) + + if self.transport: + cmd.append('-a') + cmd.append('transport=' + self.transport) + + if self.local_port: + cmd.append('-a') + cmd.append('local_port=' + self.local_port) + + if self.dsfield: + cmd.append('-a') + cmd.append('dsfield=' + self.dsfield) + + if self.maxbw: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def _query_flow_props(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flowprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('property,possible') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def flow_needs_udpating(self): + (rc, out, err) = self._query_flow_props() + + NEEDS_UPDATING = False + + if rc == 0: + properties = (line.split(':') for line in out.rstrip().split('\n')) + for prop, value in properties: + if prop == 'maxbw' and self.maxbw != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + elif prop == 'priority' and self.priority != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + return NEEDS_UPDATING + else: + self.module.fail_json(msg='Error while checking flow properties: %s' % err, + stderr=err, + rc=rc) + + def update_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('set-flowprop') + + if self.maxbw and self._needs_updating['maxbw']: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority and self._needs_updating['priority']: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=['flow']), + link=dict(required=False), + local_ip=dict(required=False), + remote_ip=dict(required=False), + transport=dict(required=False, choices=SUPPORTED_TRANSPORTS), + local_port=dict(required=False), + dsfield=dict(required=False), + maxbw=dict(required=False), + priority=dict(required=False, + default='medium', + choices=SUPPORTED_PRIORITIES), + temporary=dict(default=False, type='bool'), + state=dict(required=False, + default='present', + choices=['absent', 'present', 'resetted']), + ), + mutually_exclusive=[ + ('local_ip', 'remote_ip'), + ('local_ip', 'transport'), + ('local_ip', 'local_port'), + ('local_ip', 'dsfield'), + ('remote_ip', 'transport'), + ('remote_ip', 'local_port'), + ('remote_ip', 'dsfield'), + ('transport', 'dsfield'), + ('local_port', 'dsfield'), + ], + supports_check_mode=True + ) + + flow = Flow(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = flow.name + result['state'] = flow.state + result['temporary'] = flow.temporary + + if flow.link: + result['link'] = flow.link + + if flow.maxbw: + result['maxbw'] = flow.maxbw + + if flow.priority: + result['priority'] = flow.priority + + if flow.local_ip: + if flow.is_valid_address(flow.local_ip): + result['local_ip'] = flow.local_ip + + if flow.remote_ip: + if flow.is_valid_address(flow.remote_ip): + result['remote_ip'] = flow.remote_ip + + if flow.transport: + result['transport'] = flow.transport + + if flow.local_port: + if flow.is_valid_port(flow.local_port): + result['local_port'] = flow.local_port + else: + module.fail_json(msg='Invalid port: %s' % flow.local_port, + rc=1) + + if flow.dsfield: + if flow.is_valid_dsfield(flow.dsfield): + result['dsfield'] = flow.dsfield + else: + module.fail_json(msg='Invalid dsfield: %s' % flow.dsfield, + rc=1) + + if flow.state == 'absent': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.delete_flow() + if rc != 0: + module.fail_json(msg='Error while deleting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'present': + if not flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.create_flow() + if rc != 0: + module.fail_json(msg='Error while creating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + else: + if flow.flow_needs_udpating(): + (rc, out, err) = flow.update_flow() + if rc != 0: + module.fail_json(msg='Error while updating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'resetted': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.reset_flow() + if rc != 0: + module.fail_json(msg='Error while resetting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addr.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addr.py new file mode 100644 index 00000000..8ce3fbf1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addr.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_addr +short_description: Manage IP addresses on an interface on Solaris/illumos systems +description: + - Create/delete static/dynamic IP addresses on network interfaces on Solaris/illumos systems. + - Up/down static/dynamic IP addresses on network interfaces on Solaris/illumos systems. + - Manage IPv6 link-local addresses on network interfaces on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + address: + description: + - Specifiies an IP address to configure in CIDR notation. + required: false + aliases: [ "addr" ] + addrtype: + description: + - Specifiies a type of IP address to configure. + required: false + default: static + choices: [ 'static', 'dhcp', 'addrconf' ] + addrobj: + description: + - Specifies an unique IP address on the system. + required: true + temporary: + description: + - Specifies that the configured IP address is temporary. Temporary + IP addresses do not persist across reboots. + required: false + default: false + type: bool + wait: + description: + - Specifies the time in seconds we wait for obtaining address via DHCP. + required: false + default: 60 + state: + description: + - Create/delete/enable/disable an IP address on the network interface. + required: false + default: present + choices: [ 'absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed' ] +''' + +EXAMPLES = ''' +- name: Configure IP address 10.0.0.1 on e1000g0 + community.network.ipadm_addr: addr=10.0.0.1/32 addrobj=e1000g0/v4 state=present + +- name: Delete addrobj + community.network.ipadm_addr: addrobj=e1000g0/v4 state=absent + +- name: Configure link-local IPv6 address + community.network.ipadm_addr: addtype=addrconf addrobj=vnic0/v6 + +- name: Configure address via DHCP and wait 180 seconds for address obtaining + community.network.ipadm_addr: addrobj=vnic0/dhcp addrtype=dhcp wait=180 +''' + +RETURN = ''' +addrobj: + description: address object name + returned: always + type: str + sample: bge0/v4 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +addrtype: + description: address type + returned: always + type: str + sample: static +address: + description: IP address + returned: only if addrtype is 'static' + type: str + sample: 1.3.3.7/32 +wait: + description: time we wait for DHCP + returned: only if addrtype is 'dhcp' + type: str + sample: 10 +''' + +import socket + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_TYPES = ['static', 'addrconf', 'dhcp'] + + +class Addr(object): + + def __init__(self, module): + self.module = module + + self.address = module.params['address'] + self.addrtype = module.params['addrtype'] + self.addrobj = module.params['addrobj'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + self.wait = module.params['wait'] + + def is_cidr_notation(self): + + return self.address.count('/') == 1 + + def is_valid_address(self): + + ip_address = self.address.split('/')[0] + + try: + if len(ip_address.split('.')) == 4: + socket.inet_pton(socket.AF_INET, ip_address) + else: + socket.inet_pton(socket.AF_INET6, ip_address) + except socket.error: + return False + + return True + + def is_dhcp(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addr') + cmd.append('-p') + cmd.append('-o') + cmd.append('type') + cmd.append(self.addrobj) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + if out.rstrip() != 'dhcp': + return False + + return True + else: + self.module.fail_json(msg='Wrong addrtype %s for addrobj "%s": %s' % (out, self.addrobj, err), + rc=rc, + stderr=err) + + def addrobj_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addr') + cmd.append(self.addrobj) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def delete_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('delete-addr') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def create_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('create-addr') + cmd.append('-T') + cmd.append(self.addrtype) + + if self.temporary: + cmd.append('-t') + + if self.addrtype == 'static': + cmd.append('-a') + cmd.append(self.address) + + if self.addrtype == 'dhcp' and self.wait: + cmd.append('-w') + cmd.append(self.wait) + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def up_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('up-addr') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def down_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('down-addr') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def enable_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('enable-addr') + cmd.append('-t') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def disable_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('disable-addr') + cmd.append('-t') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def refresh_addr(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('refresh-addr') + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + address=dict(aliases=['addr']), + addrtype=dict(default='static', choices=SUPPORTED_TYPES), + addrobj=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'up', 'down', 'enabled', 'disabled', 'refreshed']), + wait=dict(default=60, type='int'), + ), + mutually_exclusive=[ + ('address', 'wait'), + ], + supports_check_mode=True + ) + + addr = Addr(module) + + rc = None + out = '' + err = '' + result = {} + result['addrobj'] = addr.addrobj + result['state'] = addr.state + result['temporary'] = addr.temporary + result['addrtype'] = addr.addrtype + + if addr.addrtype == 'static' and addr.address: + if addr.is_cidr_notation() and addr.is_valid_address(): + result['address'] = addr.address + else: + module.fail_json(msg='Invalid IP address: %s' % addr.address) + + if addr.addrtype == 'dhcp' and addr.wait: + result['wait'] = addr.wait + + if addr.state == 'absent': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.delete_addr() + if rc != 0: + module.fail_json(msg='Error while deleting addrobj: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'present': + if not addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.create_addr() + if rc != 0: + module.fail_json(msg='Error while configuring IP address: "%s"' % err, + addrobj=addr.addrobj, + addr=addr.address, + stderr=err, + rc=rc) + + elif addr.state == 'up': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.up_addr() + if rc != 0: + module.fail_json(msg='Error while bringing IP address up: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'down': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.down_addr() + if rc != 0: + module.fail_json(msg='Error while bringing IP address down: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'refreshed': + if addr.addrobj_exists(): + if addr.is_dhcp(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.refresh_addr() + if rc != 0: + module.fail_json(msg='Error while refreshing IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + else: + module.fail_json(msg='state "refreshed" cannot be used with "%s" addrtype' % addr.addrtype, + addrobj=addr.addrobj, + stderr=err, + rc=1) + + elif addr.state == 'enabled': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.enable_addr() + if rc != 0: + module.fail_json(msg='Error while enabling IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + elif addr.state == 'disabled': + if addr.addrobj_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addr.disable_addr() + if rc != 0: + module.fail_json(msg='Error while disabling IP address: "%s"' % err, + addrobj=addr.addrobj, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addrprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addrprop.py new file mode 100644 index 00000000..94895c39 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_addrprop.py @@ -0,0 +1,254 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_addrprop +short_description: Manage IP address properties on Solaris/illumos systems. +description: + - Modify IP address properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + addrobj: + description: + - Specifies the address object we want to manage. + required: true + aliases: [nic, interface] + property: + description: + - Specifies the name of the address property we want to manage. + required: true + aliases: [name] + value: + description: + - Specifies the value we want to set for the address property. + required: false + temporary: + description: + - Specifies that the address property value is temporary. + Temporary values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Mark address on addrobj as deprecated + community.network.ipadm_addrprop: property=deprecated value=on addrobj=e1000g0/v6 + +- name: Set network prefix length for addrobj + community.network.ipadm_addrprop: addrobj=bge0/v4 name=prefixlen value=26 +''' + +RETURN = ''' +property: + description: property name + returned: always + type: str + sample: deprecated +addrobj: + description: address object name + returned: always + type: str + sample: bge0/v4 +state: + description: state of the target + returned: always + type: str + sample: present +temporary: + description: specifies if operation will persist across reboots + returned: always + type: bool + sample: True +value: + description: property value + returned: when value is provided + type: str + sample: 26 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class AddrProp(object): + + def __init__(self, module): + self.module = module + + self.addrobj = module.params['addrobj'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" on addrobj %s' % + (self.property, self.addrobj), + property=self.property, + addrobj=self.addrobj) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-addrprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-addrprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + '=' + self.value) + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-addrprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.addrobj) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + addrobj=dict(required=True, default=None, aliases=['nic', 'interface']), + property=dict(required=True, aliases=['name']), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + addrprop = AddrProp(module) + + rc = None + out = '' + err = '' + result = {} + result['property'] = addrprop.property + result['addrobj'] = addrprop.addrobj + result['state'] = addrprop.state + result['temporary'] = addrprop.temporary + if addrprop.value: + result['value'] = addrprop.value + + if addrprop.state == 'absent' or addrprop.state == 'reset': + if addrprop.property_exists(): + if not addrprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = addrprop.reset_property() + if rc != 0: + module.fail_json(property=addrprop.property, + addrobj=addrprop.addrobj, + msg=err, + rc=rc) + + elif addrprop.state == 'present': + if addrprop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if addrprop.property_exists(): + if not addrprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = addrprop.set_property() + if rc != 0: + module.fail_json(property=addrprop.property, + addrobj=addrprop.addrobj, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_if.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_if.py new file mode 100644 index 00000000..7d4ac2aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_if.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_if +short_description: Manage IP interfaces on Solaris/illumos systems. +description: + - Create, delete, enable or disable IP interfaces on Solaris/illumos + systems. +author: Adam Å tevko (@xen0l) +options: + name: + description: + - IP interface name. + required: true + temporary: + description: + - Specifies that the IP interface is temporary. Temporary IP + interfaces do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Create or delete Solaris/illumos IP interfaces. + required: false + default: "present" + choices: [ "present", "absent", "enabled", "disabled" ] +''' + +EXAMPLES = ''' +- name: Create vnic0 interface + community.network.ipadm_if: + name: vnic0 + state: enabled + +- name: Disable vnic0 interface + community.network.ipadm_if: + name: vnic0 + state: disabled +''' + +RETURN = ''' +name: + description: IP interface name + returned: always + type: str + sample: "vnic0" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: persistence of a IP interface + returned: always + type: bool + sample: "True" +''' +from ansible.module_utils.basic import AnsibleModule + + +class IPInterface(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def interface_exists(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + if rc == 0: + return True + else: + return False + + def interface_is_disabled(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append('-o') + cmd.append('state') + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + if rc != 0: + self.module.fail_json(name=self.name, rc=rc, msg=err) + + return 'disabled' in out + + def create_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('create-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('delete-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def enable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('enable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def disable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('disable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', + 'present', + 'enabled', + 'disabled']), + ), + supports_check_mode=True + ) + + interface = IPInterface(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = interface.name + result['state'] = interface.state + result['temporary'] = interface.temporary + + if interface.state == 'absent': + if interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.delete_interface() + if rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + elif interface.state == 'present': + if not interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.create_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'enabled': + if interface.interface_is_disabled(): + (rc, out, err) = interface.enable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'disabled': + if not interface.interface_is_disabled(): + (rc, out, err) = interface.disable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_ifprop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_ifprop.py new file mode 100644 index 00000000..8707516c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_ifprop.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_ifprop +short_description: Manage IP interface properties on Solaris/illumos systems. +description: + - Modify IP interface properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + interface: + description: + - Specifies the IP interface we want to manage. + required: true + aliases: [nic] + protocol: + description: + - Specifies the protocol for which we want to manage properties. + required: true + property: + description: + - Specifies the name of the property we want to manage. + required: true + aliases: [name] + value: + description: + - Specifies the value we want to set for the property. + required: false + temporary: + description: + - Specifies that the property value is temporary. Temporary + property values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Allow forwarding of IPv4 packets on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv4 property=forwarding value=on interface=e1000g0 + +- name: Temporarily reset IPv4 forwarding property on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv4 interface=e1000g0 temporary=true property=forwarding state=reset + +- name: Configure IPv6 metric on network interface e1000g0 + community.network.ipadm_ifprop: protocol=ipv6 nic=e1000g0 name=metric value=100 + +- name: Set IPv6 MTU on network interface bge0 + community.network.ipadm_ifprop: interface=bge0 name=mtu value=1280 protocol=ipv6 +''' + +RETURN = ''' +protocol: + description: property's protocol + returned: always + type: str + sample: ipv4 +property: + description: property's name + returned: always + type: str + sample: mtu +interface: + description: interface name we want to set property on + returned: always + type: str + sample: e1000g0 +state: + description: state of the target + returned: always + type: str + sample: present +value: + description: property's value + returned: when value is provided + type: str + sample: 1280 +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6'] + + +class IfProp(object): + + def __init__(self, module): + self.module = module + + self.interface = module.params['interface'] + self.protocol = module.params['protocol'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown %s property "%s" on IP interface %s' % + (self.protocol, self.property, self.interface), + protocol=self.protocol, + property=self.property, + interface=self.interface) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-ifprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-ifprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + "=" + self.value) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-ifprop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append('-m') + cmd.append(self.protocol) + cmd.append(self.interface) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), + property=dict(required=True, aliases=['name']), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + interface=dict(required=True, default=None, aliases=['nic']), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + ifprop = IfProp(module) + + rc = None + out = '' + err = '' + result = {} + result['protocol'] = ifprop.protocol + result['property'] = ifprop.property + result['interface'] = ifprop.interface + result['state'] = ifprop.state + if ifprop.value: + result['value'] = ifprop.value + + if ifprop.state == 'absent' or ifprop.state == 'reset': + if ifprop.property_exists(): + if not ifprop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = ifprop.reset_property() + if rc != 0: + module.fail_json(protocol=ifprop.protocol, + property=ifprop.property, + interface=ifprop.interface, + msg=err, + rc=rc) + + elif ifprop.state == 'present': + if ifprop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if ifprop.property_exists(): + if not ifprop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = ifprop.set_property() + if rc != 0: + module.fail_json(protocol=ifprop.protocol, + property=ifprop.property, + interface=ifprop.interface, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_prop.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_prop.py new file mode 100644 index 00000000..cfd01863 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/illumos/ipadm_prop.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Å tevko +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ipadm_prop +short_description: Manage protocol properties on Solaris/illumos systems. +description: + - Modify protocol properties on Solaris/illumos systems. +author: Adam Å tevko (@xen0l) +options: + protocol: + description: + - Specifies the protocol for which we want to manage properties. + required: true + property: + description: + - Specifies the name of property we want to manage. + required: true + value: + description: + - Specifies the value we want to set for the property. + required: false + temporary: + description: + - Specifies that the property value is temporary. Temporary + property values do not persist across reboots. + required: false + default: false + type: bool + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +- name: Set TCP receive buffer size + community.network.ipadm_prop: + protocol: tcp + property: recv_buf + value: 65536 + +- name: Reset UDP send buffer size to the default value + community.network.ipadm_prop: + protocol: udp + property: send_buf + state: reset +''' + +RETURN = ''' +protocol: + description: property's protocol + returned: always + type: str + sample: "TCP" +property: + description: name of the property + returned: always + type: str + sample: "recv_maxbuf" +state: + description: state of the target + returned: always + type: str + sample: "present" +temporary: + description: property's persistence + returned: always + type: bool + sample: "True" +value: + description: value of the property. May be int or string depending on property. + returned: always + type: int + sample: "'1024' or 'never'" +''' + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6', 'icmp', 'tcp', 'udp', 'sctp'] + + +class Prop(object): + + def __init__(self, module): + self.module = module + + self.protocol = module.params['protocol'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" for protocol %s' % + (self.property, self.protocol), + protocol=self.protocol, + property=self.property) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + "=" + self.value) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), + property=dict(required=True), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + prop = Prop(module) + + rc = None + out = '' + err = '' + result = {} + result['protocol'] = prop.protocol + result['property'] = prop.property + result['state'] = prop.state + result['temporary'] = prop.temporary + if prop.value: + result['value'] = prop.value + + if prop.state == 'absent' or prop.state == 'reset': + if prop.property_exists(): + if not prop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = prop.reset_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + elif prop.state == 'present': + if prop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if prop.property_exists(): + if not prop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = prop.set_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_config.py new file mode 100644 index 00000000..0abc5ec7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_config.py @@ -0,0 +1,564 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2018, Ingate Systems AB +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ig_config +short_description: Manage the configuration database on an Ingate SBC. +description: + - Manage the configuration database on an Ingate SBC. +extends_documentation_fragment: +- community.network.ingate + +options: + add: + description: + - Add a row to a table. + type: bool + delete: + description: + - Delete all rows in a table or a specific row. + type: bool + get: + description: + - Return all rows in a table or a specific row. + type: bool + modify: + description: + - Modify a row in a table. + type: bool + revert: + description: + - Reset the preliminary configuration. + type: bool + factory: + description: + - Reset the preliminary configuration to its factory defaults. + type: bool + store: + description: + - Store the preliminary configuration. + type: bool + no_response: + description: + - Expect no response when storing the preliminary configuration. + Refer to the C(store) option. + type: bool + default: false + return_rowid: + description: + - Get rowid(s) from a table where the columns match. + type: bool + download: + description: + - Download the configuration database from the unit. + type: bool + store_download: + description: + - If the downloaded configuration should be stored on disk. + Refer to the C(download) option. + type: bool + default: false + path: + description: + - Where in the filesystem to store the downloaded configuration. + Refer to the C(download) option. + filename: + description: + - The name of the file to store the downloaded configuration in. + Refer to the C(download) option. + table: + description: + - The name of the table. + rowid: + description: + - A row id. + type: int + columns: + description: + - A dict containing column names/values. +notes: + - If C(store_download) is set to True, and C(path) and C(filename) is omitted, + the file will be stored in the current directory with an automatic filename. +author: + - Ingate Systems AB (@ingatesystems) +''' + +EXAMPLES = ''' +- name: Add/remove DNS servers + hosts: 192.168.1.1 + connection: local + vars: + client_rw: + version: v1 + address: "{{ inventory_hostname }}" + scheme: http + username: alice + password: foobar + tasks: + + - name: Load factory defaults + community.network.ig_config: + client: "{{ client_rw }}" + factory: true + register: result + - ansible.builtin.debug: + var: result + + - name: Revert to last known applied configuration + community.network.ig_config: + client: "{{ client_rw }}" + revert: true + register: result + - ansible.builtin.debug: + var: result + + - name: Change the unit name + community.network.ig_config: + client: "{{ client_rw }}" + modify: true + table: misc.unitname + columns: + unitname: "Test Ansible" + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.21 + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.22 + register: result + - ansible.builtin.debug: + var: result + + - name: Add a DNS server + community.network.ig_config: + client: "{{ client_rw }}" + add: true + table: misc.dns_servers + columns: + server: 192.168.1.23 + register: last_dns + - ansible.builtin.debug: + var: last_dns + + - name: Modify the last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + modify: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + columns: + server: 192.168.1.24 + register: result + - ansible.builtin.debug: + var: result + + - name: Return the last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + get: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + register: result + - ansible.builtin.debug: + var: result + + - name: Remove last added DNS server + community.network.ig_config: + client: "{{ client_rw }}" + delete: true + table: misc.dns_servers + rowid: "{{ last_dns['add'][0]['id'] }}" + register: result + - ansible.builtin.debug: + var: result + + - name: Return the all rows from table misc.dns_servers + community.network.ig_config: + client: "{{ client_rw }}" + get: true + table: misc.dns_servers + register: result + - ansible.builtin.debug: + var: result + + - name: Remove remaining DNS servers + community.network.ig_config: + client: "{{ client_rw }}" + delete: true + table: misc.dns_servers + register: result + - ansible.builtin.debug: + var: result + + - name: Get rowid for interface eth0 + community.network.ig_config: + client: "{{ client_rw }}" + return_rowid: true + table: network.local_nets + columns: + interface: eth0 + register: result + - ansible.builtin.debug: + var: result + + - name: Store the preliminary configuration + community.network.ig_config: + client: "{{ client_rw }}" + store: true + register: result + - ansible.builtin.debug: + var: result + + - name: Do backup of the configuration database + community.network.ig_config: + client: "{{ client_rw }}" + download: true + store_download: true + register: result + - ansible.builtin.debug: + var: result +''' + +RETURN = ''' +add: + description: A list containing information about the added row + returned: when C(add) is yes and success + type: complex + contains: + href: + description: The REST API URL to the added row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/2 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 22 +delete: + description: A list containing information about the deleted row(s) + returned: when C(delete) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: misc.dns_servers + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 22 +get: + description: A list containing information about the row(s) + returned: when C(get) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: Testname + href: + description: The REST API URL to the row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/1 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 1 +modify: + description: A list containing information about the modified row + returned: when C(modify) is yes and success + type: complex + contains: + table: + description: The name of the table + returned: success + type: str + sample: Testname + href: + description: The REST API URL to the modified row + returned: success + type: str + sample: http://192.168.1.1/api/v1/misc/dns_servers/1 + data: + description: Column names/values + returned: success + type: complex + sample: {'number': '2', 'server': '10.48.254.33'} + id: + description: The row id + returned: success + type: int + sample: 10 +revert: + description: A command status message + returned: when C(revert) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: reverted the configuration to the last applied configuration. +factory: + description: A command status message + returned: when C(factory) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: reverted the configuration to the factory configuration. +store: + description: A command status message + returned: when C(store) is yes and success + type: complex + contains: + msg: + description: The command status message + returned: success + type: str + sample: Successfully applied and saved the configuration. +return_rowid: + description: The matched row id(s). + returned: when C(return_rowid) is yes and success + type: list + sample: [1, 3] +download: + description: Configuration database and meta data + returned: when C(download) is yes and success + type: complex + contains: + config: + description: The configuration database + returned: success + type: str + filename: + description: A suggested name for the configuration + returned: success + type: str + sample: testname_2018-10-01T214040.cfg + mimetype: + description: The mimetype + returned: success + type: str + sample: application/x-config-database +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ingate.common import (ingate_argument_spec, + ingate_create_client) + +try: + from ingate import ingatesdk + HAS_INGATESDK = True +except ImportError: + HAS_INGATESDK = False + + +def make_request(module): + # Create client and authenticate. + api_client = ingate_create_client(**module.params) + + if module.params.get('add'): + # Add a row to a table. + table = module.params['table'] + columns = module.params['columns'] + response = api_client.add_row(table, **columns) + return True, 'add', response + elif module.params.get('delete'): + # Delete a row/table. + changed = False + table = module.params['table'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.delete_row(table, rowid=rowid) + else: + response = api_client.delete_table(table) + if response: + changed = True + return changed, 'delete', response + elif module.params.get('get'): + # Get the contents of a table/row. + table = module.params['table'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.dump_row(table, rowid=rowid) + else: + response = api_client.dump_table(table) + if response: + changed = True + return changed, 'get', response + elif module.params.get('modify'): + # Modify a table row. + table = module.params['table'] + columns = module.params['columns'] + rowid = module.params.get('rowid') + if rowid: + response = api_client.modify_row(table, rowid=rowid, **columns) + else: + response = api_client.modify_single_row(table, **columns) + if response: + changed = True + return changed, 'modify', response + elif module.params.get('revert'): + # Revert edits. + response = api_client.revert_edits() + if response: + response = response[0]['revert-edits'] + return True, 'revert', response + elif module.params.get('factory'): + # Load factory defaults. + response = api_client.load_factory() + if response: + response = response[0]['load-factory'] + return True, 'factory', response + elif module.params.get('store'): + # Store edit. + no_response = module.params.get('no_response') + response = api_client.store_edit(no_response=no_response) + if response: + response = response[0]['store-edit'] + return True, 'store', response + elif module.params.get('return_rowid'): + # Find matching rowid(s) in a table. + table = module.params['table'] + columns = module.params['columns'] + response = api_client.dump_table(table) + rowids = [] + for row in response: + match = False + for (name, value) in columns.items(): + if name not in row['data']: + continue + if not row['data'][name] == value: + match = False + break + else: + match = True + if match: + rowids.append(row['id']) + return False, 'return_rowid', rowids + elif module.params.get('download'): + # Download the configuration database. + store = module.params.get('store_download') + path = module.params.get('path') + filename = module.params.get('filename') + response = api_client.download_config(store=store, path=path, + filename=filename) + if response: + response = response[0]['download-config'] + return False, 'download', response + return False, '', {} + + +def main(): + argument_spec = ingate_argument_spec( + add=dict(type='bool'), + delete=dict(type='bool'), + get=dict(type='bool'), + modify=dict(type='bool'), + revert=dict(type='bool'), + factory=dict(type='bool'), + store=dict(type='bool'), + no_response=dict(type='bool', default=False), + return_rowid=dict(type='bool'), + download=dict(type='bool'), + store_download=dict(type='bool', default=False), + path=dict(), + filename=dict(), + table=dict(), + rowid=dict(type='int'), + columns=dict(type='dict'), + ) + + mutually_exclusive = [('add', 'delete', 'get', 'modify', 'revert', + 'factory', 'store', 'return_rowid', 'download')] + required_one_of = [['add', 'delete', 'get', 'modify', 'revert', 'factory', + 'store', 'return_rowid', 'download']] + required_if = [('add', True, ['table', 'columns']), + ('delete', True, ['table']), + ('get', True, ['table']), + ('modify', True, ['table', 'columns']), + ('return_rowid', True, ['table', 'columns'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + required_one_of=required_one_of, + supports_check_mode=False) + if not HAS_INGATESDK: + module.fail_json(msg='The Ingate Python SDK module is required') + + result = dict(changed=False) + try: + changed, command, response = make_request(module) + if response and command: + result[command] = response + result['changed'] = changed + except ingatesdk.SdkError as e: + module.fail_json(msg=str(e)) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_unit_information.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_unit_information.py new file mode 100644 index 00000000..5fb2bbfd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ingate/ig_unit_information.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ingate Systems AB +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ig_unit_information +short_description: Get unit information from an Ingate SBC. +description: + - Get unit information from an Ingate SBC. +extends_documentation_fragment: +- community.network.ingate + +author: + - Ingate Systems AB (@ingatesystems) +''' + +EXAMPLES = ''' +- name: Get unit information + community.network.ig_unit_information: + client: + version: v1 + scheme: http + address: 192.168.1.1 + username: alice + password: foobar +''' + +RETURN = ''' +unit-information: + description: Information about the unit + returned: success + type: complex + contains: + installid: + description: The installation identifier + returned: success + type: str + sample: any + interfaces: + description: List of interface names + returned: success + type: str + sample: eth0 eth1 eth2 eth3 eth4 eth5 + lang: + description: The unit's language + returned: success + type: str + sample: en + lic_email: + description: License email information + returned: success + type: str + sample: example@example.com + lic_mac: + description: License MAC information + returned: success + type: str + sample: any + lic_name: + description: License name information + returned: success + type: str + sample: Example Inc + macaddr: + description: The MAC address of the first interface + returned: success + type: str + sample: 52:54:00:4c:e2:07 + mode: + description: Operational mode of the unit + returned: success + type: str + sample: Siparator + modules: + description: Installed module licenses + returned: success + type: str + sample: failover vpn sip qturn ems qos rsc voipsm + patches: + description: Installed patches on the unit + returned: success + type: list + sample: [] + product: + description: The product name + returned: success + type: str + sample: Software SIParator/Firewall + serial: + description: The serial number of the unit + returned: success + type: str + sample: IG-200-839-2008-0 + systemid: + description: The system identifier of the unit + returned: success + type: str + sample: IG-200-839-2008-0 + unitname: + description: The name of the unit + returned: success + type: str + sample: Testname + version: + description: Firmware version + returned: success + type: str + sample: 6.2.0-beta2 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.network.plugins.module_utils.network.ingate.common import (ingate_argument_spec, + ingate_create_client, + is_ingatesdk_installed) + +try: + from ingate import ingatesdk +except ImportError: + pass + + +def make_request(module): + # Create client and authenticate. + api_client = ingate_create_client(**module.params) + + # Get unit information. + response = api_client.unit_information() + return response + + +def main(): + argument_spec = ingate_argument_spec() + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + + is_ingatesdk_installed(module) + + result = dict(changed=False) + try: + response = make_request(module) + result.update(response[0]) + except ingatesdk.SdkError as e: + module.fail_json(msg=to_native(e)) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_command.py new file mode 100644 index 00000000..7c3587bc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_command.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_command +author: "Paul Baker (@paulquack)" +short_description: Run arbitrary commands on Extreme IronWare devices +description: + - Sends arbitrary commands to a Extreme Ironware node and returns the + results read from the device. This module includes a I(wait_for) + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +extends_documentation_fragment: +- community.network.ironware + +options: + commands: + description: + - List of commands to send to the remote device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. If the value + is set to C(all) then all conditionals in the I(wait_for) must be + satisfied. If the value is set to C(any) then only one of the + values must be satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +- name: Run a command + community.network.ironware_command: + commands: + - show version + +- name: Run several commands + community.network.ironware_command: + commands: + - show interfaces brief wide + - show mpls vll +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + spec = dict( + # { command: , prompt: , response: } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + spec.update(ironware_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + check_args(module) + + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params['commands'] + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_config.py new file mode 100644 index 00000000..ec3a7fb1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_config.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_config +author: "Paul Baker (@paulquack)" +short_description: Manage configuration sections on Extreme Ironware devices +description: + - Extreme Ironware configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with Ironware configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.ironware + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct + default: line + choices: ['line', 'block'] + update: + description: + - The I(update) argument controls how the configuration statements + are processed on the remote device. Valid choices for the I(update) + argument are I(merge) and I(check). When the argument is set to + I(merge), the configuration changes are merged with the current + device running configuration. When the argument is set to I(check) + the configuration updates are determined but not actually configured + on the remote device. + default: merge + choices: ['merge', 'check'] + commit: + description: + - This argument specifies the update method to use when applying the + configuration changes to the remote node. If the value is set to + I(merge) the configuration updates are merged with the running- + config. If the value is set to I(check), no changes are made to + the remote host. + default: merge + choices: ['merge', 'check'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config + default: never + choices: ['always', 'never', 'modified'] + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Run commands that should be configured in the section + community.network.ironware_config: + lines: + - port-name test + - enable + - load-interval 30 + - rate-limit input broadcast unknown-unicast multicast 521216 64000 + parents: ['interface ethernet 1/2'] +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ironware_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import get_config, load_config, run_commands +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + configobjs = None + + candidate = get_candidate(module) + if match != 'none': + contents = module.params['config'] + if not contents: + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + configobjs = candidate.difference(config, path=path, match=match, + replace=replace) + + else: + configobjs = candidate.items + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + if result['changed'] or module.params['save_when'] == 'always': + result['changed'] = True + if not module.check_mode: + cmd = {'command': 'write memory'} + run_commands(module, [cmd]) + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save_when=dict(choices=['always', 'never', 'modified'], default='never') + + ) + + argument_spec.update(ironware_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + check_args(module) + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_facts.py new file mode 100644 index 00000000..11467bf4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ironware/ironware_facts.py @@ -0,0 +1,647 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ironware_facts +author: "Paul Baker (@paulquack)" +short_description: Collect facts from devices running Extreme Ironware +description: + - Collects a base set of device facts from a remote device that + is running Ironware. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +extends_documentation_fragment: +- community.network.ironware + +notes: + - Tested against Ironware 5.8e +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, mpls and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: ['!config','!mpls'] +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.ironware_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.ironware_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.ironware_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# mpls +ansible_net_mpls_lsps: + description: All MPLS LSPs configured on the device + returned: When LSP is configured + type: dict +ansible_net_mpls_vll: + description: All VLL instances configured on the device + returned: When MPLS VLL is configured + type: dict +ansible_net_mpls_vll_local: + description: All VLL-LOCAL instances configured on the device + returned: When MPLS VLL-LOCAL is configured + type: dict +ansible_net_mpls_vpls: + description: All VPLS instances configured on the device + returned: When MPLS VPLS is configured + type: dict + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import run_commands +from ansible_collections.community.network.plugins.module_utils.network.ironware.ironware import ironware_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show chassis' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + + def parse_version(self, data): + match = re.search(r'IronWare : Version (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'^\*\*\* (.+) \*\*\*$', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial #: (\S+),', data) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'dir | include Directory', + 'show memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + + data = self.responses[1] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024 / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024 / 1024, 0)) + + def parse_filesystems(self, data): + return re.findall(r'^Directory of (\S+)', data, re.M) + + def parse_memtotal(self, data): + match = re.search(r'Total SDRAM\D*(\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'(Total Free Memory|Available Memory)\D*(\d+)\s', data, re.M) + if match: + return match.group(2) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class MPLS(FactsBase): + + COMMANDS = [ + 'show mpls lsp detail', + 'show mpls vll-local detail', + 'show mpls vll detail', + 'show mpls vpls detail' + ] + + def populate(self): + super(MPLS, self).populate() + data = self.responses[0] + if data: + data = self.parse_mpls(data) + self.facts['mpls_lsps'] = self.populate_lsps(data) + + data = self.responses[1] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vll_local'] = self.populate_vll_local(data) + + data = self.responses[2] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vll'] = self.populate_vll(data) + + data = self.responses[3] + if data: + data = self.parse_mpls(data) + self.facts['mpls_vpls'] = self.populate_vpls(data) + + def parse_mpls(self, data): + parsed = dict() + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(LSP|VLL|VPLS) ([^\s,]+)', line) + if match: + key = match.group(2) + parsed[key] = line + return parsed + + def populate_vpls(self, vpls): + facts = dict() + for key, value in iteritems(vpls): + vpls = dict() + vpls['endpoints'] = self.parse_vpls_endpoints(value) + vpls['vc-id'] = self.parse_vpls_vcid(value) + facts[key] = vpls + return facts + + def populate_vll_local(self, vll_locals): + facts = dict() + for key, value in iteritems(vll_locals): + vll = dict() + vll['endpoints'] = self.parse_vll_endpoints(value) + facts[key] = vll + return facts + + def populate_vll(self, vlls): + facts = dict() + for key, value in iteritems(vlls): + vll = dict() + vll['endpoints'] = self.parse_vll_endpoints(value) + vll['vc-id'] = self.parse_vll_vcid(value) + vll['cos'] = self.parse_vll_cos(value) + facts[key] = vll + return facts + + def parse_vll_vcid(self, data): + match = re.search(r'VC-ID (\d+),', data, re.M) + if match: + return match.group(1) + + def parse_vll_cos(self, data): + match = re.search(r'COS +: +(\d+)', data, re.M) + if match: + return match.group(1) + + def parse_vll_endpoints(self, data): + facts = list() + regex = r'End-point[0-9 ]*: +(?Ptagged|untagged) +(vlan +(?P[0-9]+) +)?(inner- vlan +(?P[0-9]+) +)?(?Pe [0-9/]+|--)' + matches = re.finditer(regex, data, re.IGNORECASE | re.DOTALL) + for match in matches: + f = match.groupdict() + f['type'] = 'local' + facts.append(f) + + regex = r'Vll-Peer +: +(?P[0-9\.]+).*Tunnel LSP +: +(?P\S+)' + matches = re.finditer(regex, data, re.IGNORECASE | re.DOTALL) + for match in matches: + f = match.groupdict() + f['type'] = 'remote' + facts.append(f) + + return facts + + def parse_vpls_vcid(self, data): + match = re.search(r'Id (\d+),', data, re.M) + if match: + return match.group(1) + + def parse_vpls_endpoints(self, data): + facts = list() + regex = r'Vlan (?P[0-9]+)\s(?: +(?:L2.*)\s| +Tagged: (?P.+)+\s| +Untagged: (?P.+)\s)*' + matches = re.finditer(regex, data, re.IGNORECASE) + for match in matches: + f = match.groupdict() + f['type'] = 'local' + facts.append(f) + + regex = r'Peer address: (?P[0-9\.]+)' + matches = re.finditer(regex, data, re.IGNORECASE) + for match in matches: + f = match.groupdict() + f['type'] = 'remote' + facts.append(f) + + return facts + + def populate_lsps(self, lsps): + facts = dict() + for key, value in iteritems(lsps): + lsp = dict() + lsp['to'] = self.parse_lsp_to(value) + lsp['from'] = self.parse_lsp_from(value) + lsp['adminstatus'] = self.parse_lsp_adminstatus(value) + lsp['operstatus'] = self.parse_lsp_operstatus(value) + lsp['pri_path'] = self.parse_lsp_pripath(value) + lsp['sec_path'] = self.parse_lsp_secpath(value) + lsp['frr'] = self.parse_lsp_frr(value) + + facts[key] = lsp + + return facts + + def parse_lsp_to(self, data): + match = re.search(r'^LSP .* to (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_lsp_from(self, data): + match = re.search(r'From: ([^\s,]+),', data, re.M) + if match: + return match.group(1) + + def parse_lsp_adminstatus(self, data): + match = re.search(r'admin: (\w+),', data, re.M) + if match: + return match.group(1) + + def parse_lsp_operstatus(self, data): + match = re.search(r'From: .* status: (\w+)', data, re.M) + if match: + return match.group(1) + + def parse_lsp_pripath(self, data): + match = re.search(r'Pri\. path: ([^\s,]+), up: (\w+), active: (\w+)', data, re.M) + if match: + path = dict() + path['name'] = match.group(1) if match.group(1) != 'NONE' else None + path['up'] = True if match.group(2) == 'yes' else False + path['active'] = True if match.group(3) == 'yes' else False + return path + + def parse_lsp_secpath(self, data): + match = re.search(r'Sec\. path: ([^\s,]+), active: (\w+).*\n.* status: (\w+)', data, re.M) + if match: + path = dict() + path['name'] = match.group(1) if match.group(1) != 'NONE' else None + path['up'] = True if match.group(3) == 'up' else False + path['active'] = True if match.group(2) == 'yes' else False + return path + + def parse_lsp_frr(self, data): + match = re.search(r'Backup LSP: (\w+)', data, re.M) + if match: + path = dict() + path['up'] = True if match.group(1) == 'UP' else False + path['name'] = None + if path['up']: + match = re.search(r'bypass_lsp: (\S)', data, re.M) + path['name'] = match.group(1) if match else None + return path + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces', + 'show ipv6 interface', + 'show lldp neighbors' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data and 'LLDP is not running' not in data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s([0-9a-f]+:+[0-9a-f:]+\/\d+)\s', value, re.M) + for addr in addresses: + address, masklen = addr.split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + for line in neighbors.split('\n'): + if line == '': + continue + match = re.search(r'([\d\/]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(5) + fact['port'] = match.group(3) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+Ethernet|eth )(\S+)', line) + if match: + key = match.group(2) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Port name is (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'BW is (\d+)', data) + if match: + return int(match.group(1)) + + def parse_duplex(self, data): + match = re.search(r'configured duplex \S+ actual (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_mediatype(self, data): + match = re.search(r'Type\s*:\s*(.+)$', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + mpls=MPLS, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config", "!mpls"], type='list') + ) + + argument_spec.update(ironware_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + check_args(module) + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_start_workflow.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_start_workflow.py new file mode 100644 index 00000000..c8c2ada8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_start_workflow.py @@ -0,0 +1,179 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Itential +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +This module provides the ability to start a workflow from Itential Automation Platform +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: iap_start_workflow +author: "Itential (@cma0) " +short_description: Start a workflow in the Itential Automation Platform +description: + - This will start a specified workflow in the Itential Automation Platform with given arguments. +options: + iap_port: + description: + - Provide the port number for the Itential Automation Platform + required: true + type: str + default: null + + iap_fqdn: + description: + - Provide the fqdn for the Itential Automation Platform + required: true + type: str + default: null + + token_key: + description: + - Token key generated by iap_token module for the Itential Automation Platform + required: true + type: str + default: null + + workflow_name: + description: + - Provide the workflow name + required: true + type: str + default: null + + description: + description: + - Provide the description for the workflow + required: true + type: str + default: null + + variables: + description: + - Provide the values to the job variables + required: true + type: dict + default: null + + https: + description: + - Use HTTPS to connect + - By default using http + type: bool + default: False + + validate_certs: + description: + - If C(no), SSL certificates for the target url will not be validated. This should only be used + on personally controlled sites using self-signed certificates. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Start a workflow in the Itential Automation Platform + community.network.iap_start_workflow: + iap_port: 3000 + iap_fqdn: localhost + token_key: "DFSFSFHFGFGF[DSFSFAADAFASD%3D" + workflow_name: "RouterUpgradeWorkflow" + description: "OS-Router-Upgrade" + variables: {"deviceName":"ASR9K"} + register: result + +- ansible.builtin.debug: var=result +''' + +RETURN = ''' +response: + description: The result contains the response from the call + type: dict + returned: always +msg: + description: The msg will contain the error code or status of the workflow + type: str + returned: always +''' + +# Ansible imports +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url + +# Standard library imports +import json + + +def start_workflow(module): + """ + :param module: + :return: response and msg + """ + # By default this will be http. + # By default when using https, self signed certificate is used + # If https needs to pass certificate then use validate_certs as true + if module.params['https']: + transport_protocol = 'https' + else: + transport_protocol = 'http' + + application_token = str(module.params['token_key']) + url = str(transport_protocol) + "://" + str(module.params['iap_fqdn']) + ":" + str(module.params[ + 'iap_port']) + "/workflow_engine/startJobWithOptions/" \ + + str(module.params['workflow_name']) + "?token=" + str(application_token) + options = { + "variables": module.params['variables'], + "description": str(module.params['description']) + } + + payload = { + "workflow": module.params['workflow_name'], + "options": options + } + + json_body = module.jsonify(payload) + headers = dict() + headers['Content-Type'] = 'application/json' + + # Using fetch url instead of requests + response, info = fetch_url(module, url, data=json_body, headers=headers) + response_code = str(info['status']) + if info['status'] not in [200, 201]: + module.fail_json(msg="Failed to connect to Itential Automation Platform. Response code is " + response_code) + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + jsonResponse = json.loads(response.read().decode('utf-8')) + module.exit_json(changed=True, msg={"workflow_name": module.params['workflow_name'], "status": "started"}, + response=jsonResponse) + + +def main(): + """ + :return: response and msg + """ + # define the available arguments/parameters that a user can pass to + # the module + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=dict( + iap_port=dict(type='str', required=True), + iap_fqdn=dict(type='str', required=True), + token_key=dict(type='str', required=True, no_log=True), + workflow_name=dict(type='str', required=True), + description=dict(type='str', required=True), + variables=dict(type='dict', required=False), + https=(dict(type='bool', default=False)), + validate_certs=dict(type='bool', default=False) + ) + ) + start_workflow(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_token.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_token.py new file mode 100644 index 00000000..72d69df6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/itential/iap_token.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Itential +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +This module provides the token for Itential Automation Platform +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: iap_token +author: "Itential (@cma0) " +short_description: Get token for the Itential Automation Platform +description: + - Checks the connection to IAP and retrieves a login token. +options: + iap_port: + description: + - Provide the port number for the Itential Automation Platform + required: true + default: null + + iap_fqdn: + description: + - Provide the fqdn or ip-address for the Itential Automation Platform + required: true + default: null + + username: + description: + - Provide the username for the Itential Automation Platform + required: true + default: null + + password: + description: + - Provide the password for the Itential Automation Platform + required: true + default: null + + https: + description: + - Use HTTPS to connect + - By default using http + type: bool + default: False + + validate_certs: + description: + - If C(no), SSL certificates for the target url will not be validated. This should only be used + on personally controlled sites using self-signed certificates. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Get token for the Itential Automation Platform + community.network.iap_token: + iap_port: 3000 + iap_fqdn: localhost + username: myusername + password: mypass + register: result + +- ansible.builtin.debug: var=result.token +''' + +RETURN = ''' +token: + description: The token acquired from the Itential Automation Platform + type: str + returned: always +''' +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url + + +def get_token(module): + """ + :param module: + :return: token + """ + # defaulting the value for transport_protocol to be : http + transport_protocol = 'http' + if module.params['https'] or module.params['validate_certs'] is True: + transport_protocol = 'https' + + url = transport_protocol + "://" + module.params['iap_fqdn'] + ":" + module.params['iap_port'] + "/login" + username = module.params['username'] + password = module.params['password'] + + login = { + "user": { + "username": username, + "password": password + } + } + json_body = module.jsonify(login) + headers = {} + headers['Content-Type'] = 'application/json' + + # Using fetch url instead of requests + response, info = fetch_url(module, url, data=json_body, headers=headers) + response_code = str(info['status']) + if info['status'] not in [200, 201]: + module.fail_json(msg="Failed to connect to Itential Automation Platform" + response_code) + response = response.read() + module.exit_json(changed=True, token=response) + + +def main(): + """ + :return: token + """ + # define the available arguments/parameters that a user can pass to + # the module + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=dict( + iap_port=dict(type='int', required=True), + iap_fqdn=dict(type='str', required=True), + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + https=(dict(type='bool', default=False)), + validate_certs=dict(type='bool', default=False) + ) + ) + get_token(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netact/netact_cm_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netact/netact_cm_command.py new file mode 100644 index 00000000..aba6f859 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netact/netact_cm_command.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# Copyright: Nokia +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# pylint: disable=invalid-name +# pylint: disable=wrong-import-position +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=too-many-statements + +""" +NetAct CM ansible command module +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: netact_cm_command + +short_description: Manage network configuration data in Nokia Core and Radio networks + + +description: + netact_cm_command can be used to run various configuration management operations. + This module requires that the target hosts have Nokia NetAct network management system installed. + Module will access the Configurator command line interface in NetAct to upload network configuration to NetAct, + run configuration export, plan import and configuration provision operations + To set the scope of the operation, define Distinguished Name (DN) or Working Set (WS) or + Maintenance Region (MR) as input +options: + operation: + description: + Supported operations allow user to upload actual configuration from the network, to import and + provision prepared plans, or export reference or actual configuration for planning purposes. + Provision_Mass_Modification enables provisioning the same parameters to multiple network elements. + This operation supports modifications only to one object class at a time. With this option + NetAct Configurator creates and provisions a plan to the network with the given scope and options. + required: true + choices: + - upload + - provision + - import + - export + - Provision_Mass_Modification + aliases: + - op + opsName: + description: + - user specified operation name + required: false + DN: + description: + Sets the exact scope of the operation in form of a list of managed object + Distinguished Names (DN) in the network. + A single DN or a list of DNs can be given (comma separated list without spaces). + Alternatively, if DN or a list of DNs is not given, working set (WS) or Maintenance Region (MR) + must be provided as parameter to set the scope of operation. + required: false + + WS: + description: + Sets the scope of the operation to use one or more pre-defined working sets (WS) in NetAct. + A working set contains network elements selected by user according to defined criteria. + A single WS name, or multiple WSs can be provided (comma-separated list without spaces). + Alternatively, if a WS name or a list of WSs is not given, Distinguished Name (DN) or + Maintenance Region(MR) must be provided as parameter to set the scope of operation. + required: false + MR: + description: + Sets the scope of the operation to network elements assigned to a Maintenance Region (MR) + Value can be set as MR IDs including the Maintenance Region Collection (MRC) + information (for example MRC-FIN1/MR-Hel). + Multiple MRs can be given (comma-separated list without spaces) + The value of this parameter is searched through MR IDs under given MRC. If there is no match, + then it is searched from all MR names. + Alternatively, if MR ID or a list or MR IDs is not given, Distinguished Name (DN) or Working Set (WS) + must be provided as parameter to set the scope of operation. + required: false + planName: + description: + - Specifies a plan name. + required: false + typeOption: + description: + Specifies the type of the export operation. + required: false + choices: + - plan + - actual + - reference + - template + - siteTemplate + aliases: + - type + fileFormat: + description: + Indicates file format. + required: false + choices: + - RAML2 + - CSV + - XLSX + fileName: + description: + - Specifies a file name. Valid for Import and Export operations. + required: false + inputFile: + description: + Specifies full path to plan file location for the import operation. + This parameter (inputFile) or the fileName parameter must be filled. If both are present then + the inputFile is used. + required: false + createBackupPlan: + description: + - Specifies if backup plan generation is enabled. + required: false + type: bool + backupPlanName: + description: + - Specifies a backup plan name + required: false + verbose: + description: + NetAct Configurator will print more info + required: false + extra_opts: + description: + Extra options to be set for operations. Check Configuration Management > Configuration Management + Operating Procedures > Command Line Operations in Nokia NetAct user documentation for further + information for extra options. + required: false +notes: + - Check mode is not currently supported +author: + - Harri Tuominen (@hatuomin) +''' + +EXAMPLES = ''' +# Pass in a message +- name: Upload + community.network.netact_cm_command: + operation: "Upload" + opsname: 'Uploading_test' + dn: "PLMN-PLMN/MRBTS-746" + extra_opts: '-btsContentInUse true' + +- name: Provision + community.network.netact_cm_command: + operation: "Provision" + opsname: 'Provision_test' + dn: "PLMN-PLMN/MRBTS-746" + planName: 'mySiteTemplate' + type: 'actual' + createBackupPlan: true + backupPlanName: 'myBackupPlanName' + +- name: Export and fetching data from target + community.network.netact_cm_command: + operation: "Export" + opsname: 'Export_test' + planName: 'mySiteTemplate' + type: 'actual' + fileName: 'exportTest.xml' +- ansible.builtin.fetch: + src: /var/opt/nokia/oss/global/racops/export/exportTest.xml + dest: fetched + +- name: Import + community.network.netact_cm_command: + operation: "Import" + opsname: 'Import_test' + fileFormat: 'CSV' + type: 'plan' + fileName: 'myCSVFile' + planName: 'myPlanName' + extra_ops: 'enablePolicyPlans true' + +# fail the module +- name: Test failure of the module + community.network.netact_cm_command: + name: fail me +''' + +RETURN = ''' +original_message: + description: The original name param that was passed in + returned: Command line + type: str + sample: '/opt/oss/bin/racclimx.sh -op Upload -opsName Uploading_testi -DN PLMN-PLMN/MRBTS-746' +message: + description: The output message that the netact_cm_command module generates + returned: Command output message + type: str +changed: + description: data changed + returned: true if data is changed + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule + +racclimx = '/opt/oss/bin/racclimx.sh' + + +def main(): + """ + Main module where option are handled and command is executed + :return: + """ + # define the available arguments/parameters that a user can pass to + # the module + module_args = dict( + operation=dict(type='str', required=True, + aliases=['op'], + choices=['Upload', 'Provision', 'Import', + 'Export', 'Provision_Mass_Modification']), + opsName=dict(type='str', required=False), + DN=dict(type='str', required=False), + WS=dict(type='str', required=False), + MR=dict(type='str', required=False), + + planName=dict(type='str', required=False), + typeOption=dict(type='str', required=False, aliases=['type'], + choices=['plan', 'actual', 'reference', 'template', 'siteTemplate']), + fileFormat=dict(type='str', required=False, choices=['CSV', 'RAML2', 'XLSX']), + fileName=dict(type='str', required=False), + createBackupPlan=dict(type='bool', required=False), + backupPlanName=dict(type='str', required=False), + inputFile=dict(type='str', required=False), + + verbose=dict(type='str', required=False), + extra_opts=dict(type='str', required=False) + ) + + # seed the result dict in the object + # we primarily care about changed and state + # change is if this module effectively modified the target + # state will include any data that you want your module to pass back + # for consumption, for example, in a subsequent task + result = dict( + changed=False, + original_message='', + cmd='', + message='' + ) + + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True + ) + + # if the user is working with this module in only check mode we do not + # want to make any changes to the environment, just return the current + # state with no modifications + if module.check_mode: + result['skipped'] = True + result['msg'] = 'check mode not (yet) supported for this module' + module.exit_json(**result) + + # manipulate or modify the state as needed (this is going to be the + # part where your module will do what it needs to do) + + operation = module.params.get('operation') + if not operation: + module.fail_json(msg='Operation not defined', **result) + + opsname = module.params.get('opsName') + dn = module.params.get('DN') + ws = module.params.get('WS') + mr = module.params.get('MR') + + planname = module.params.get('planName') + typeoption = module.params.get('typeOption') + fileformat = module.params.get('fileFormat') + filename = module.params.get('fileName') + + createBackupPlan = module.params.get('createBackupPlan') + backupPlanName = module.params.get('backupPlanName') + inputfile = module.params.get('inputFile') + + extra_opts = module.params.get('extra_opts') + verbose = module.params.get('verbose') + + # parameter checks + + command = [racclimx, '-op', operation] + + if opsname: + command.append('-opsName') + command.append(opsname) + + if dn: + command.append('-DN') + command.append(dn) + + if ws: + command.append('-WS') + command.append(ws) + + if mr: + command.append('-MR') + command.append(mr) + + if planname: + command.append('-planName') + command.append(planname) + + if typeoption: + command.append('-type') + command.append(typeoption) + + if fileformat: + command.append('-fileFormat') + command.append(fileformat) + + if filename: + command.append('-fileName') + command.append(filename) + + if createBackupPlan: + command.append('-createBackupPlan') + command.append('true') + + if backupPlanName: + command.append('-backupPlanName') + command.append(backupPlanName) + + if inputfile: + command.append('-inputFile') + command.append(inputfile) + + if extra_opts: + command = command + extra_opts.split(" ") + + if verbose: + if verbose == 'True': + command.append("-v") + + rc, out, err = module.run_command(command, check_rc=True) + if rc != 0: + result['changed'] = False + module.fail_json(msg=err) + else: + result['changed'] = True + result['original_message'] = out + result['cmd'] = command + result['message'] = out + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_action.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_action.py new file mode 100644 index 00000000..8fc7511c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_action.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_action +short_description: Manage content switching actions +description: + - Manage content switching actions + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the content switching action. Must begin with an ASCII alphanumeric or underscore C(_) + character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon + C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the content + switching action is created. + + targetlbvserver: + description: + - "Name of the load balancing virtual server to which the content is switched." + + targetvserver: + description: + - "Name of the VPN virtual server to which the content is switched." + + targetvserverexpr: + description: + - "Information about this content switching action." + + comment: + description: + - "Comments associated with this cs action." + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# lb_vserver_1 must have been already created with the netscaler_lb_vserver module + +- name: Configure netscaler content switching action + delegate_to: localhost + community.network.netscaler_cs_action: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + name: action-1 + targetlbvserver: lb_vserver_1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +import json + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csaction import csaction + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) + + +def action_exists(client, module): + if csaction.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def action_identical(client, module, csaction_proxy): + if len(diff_list(client, module, csaction_proxy)) == 0: + return True + else: + return False + + +def diff_list(client, module, csaction_proxy): + action_list = csaction.get_filtered(client, 'name:%s' % module.params['name']) + diff_list = csaction_proxy.diff_object(action_list[0]) + if False and 'targetvserverexpr' in diff_list: + json_value = json.loads(action_list[0].targetvserverexpr) + if json_value == module.params['targetvserverexpr']: + del diff_list['targetvserverexpr'] + return diff_list + + +def main(): + + module_specific_arguments = dict( + + name=dict(type='str'), + targetlbvserver=dict(type='str'), + targetvserverexpr=dict(type='str'), + comment=dict(type='str'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'targetlbvserver', + 'targetvserverexpr', + 'comment', + ] + readonly_attrs = [ + 'hits', + 'referencecount', + 'undefhits', + 'builtin', + ] + + immutable_attrs = [ + 'name', + 'targetvserverexpr', + ] + + transforms = { + } + + # Instantiate config proxy + csaction_proxy = ConfigProxy( + actual=csaction(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + ensure_feature_is_enabled(client, 'CS') + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not action_exists(client, module): + if not module.check_mode: + csaction_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not action_identical(client, module, csaction_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(csaction_proxy, diff_list(client, module, csaction_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, csaction_proxy), + **module_result + ) + + if not module.check_mode: + csaction_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + log('Sanity checks for state present') + if not module.check_mode: + if not action_exists(client, module): + module.fail_json(msg='Content switching action does not exist', **module_result) + if not action_identical(client, module, csaction_proxy): + module.fail_json( + msg='Content switching action differs from configured', + diff=diff_list(client, module, csaction_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if action_exists(client, module): + if not module.check_mode: + csaction_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if action_exists(client, module): + module.fail_json(msg='Content switching action still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_policy.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_policy.py new file mode 100644 index 00000000..47459608 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_policy.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_policy +short_description: Manage content switching policy +description: + - Manage content switching policy. + - "This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance." + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + policyname: + description: + - >- + Name for the content switching policy. Must begin with an ASCII alphanumeric or underscore C(_) + character, and must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon + C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Cannot be changed after a policy is + created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, my policy or my policy). + - "Minimum length = 1" + + url: + description: + - >- + URL string that is matched with the URL of a request. Can contain a wildcard character. Specify the + string value in the following format: C([[prefix] [*]] [.suffix]). + - "Minimum length = 1" + - "Maximum length = 208" + + rule: + description: + - >- + Expression, or name of a named expression, against which traffic is evaluated. Written in the classic + or default syntax. + - "Note:" + - >- + Maximum length of a string literal in the expression is 255 characters. A longer string can be split + into smaller strings of up to 255 characters each, and the smaller strings concatenated with the + + operator. For example, you can create a 500-character string as follows: '"" + ""' + + domain: + description: + - "The domain name. The string value can range to 63 characters." + - "Minimum length = 1" + + action: + description: + - >- + Content switching action that names the target load balancing virtual server to which the traffic is + switched. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Create url cs policy + delegate_to: localhost + community.network.netscaler_cs_policy: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + policyname: policy_1 + url: /example/ +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Could not load nitro python sdk" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'url': 'difference. ours: (str) example1 other: (str) /example1' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, ensure_feature_is_enabled) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy import cspolicy + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def policy_exists(client, module): + log('Checking if policy exists') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) > 0: + return True + else: + return False + + +def policy_identical(client, module, cspolicy_proxy): + log('Checking if defined policy is identical to configured') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) == 0: + return False + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + diff_dict = cspolicy_proxy.diff_object(policy_list[0]) + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, cspolicy_proxy): + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + return cspolicy_proxy.diff_object(policy_list[0]) + + +def main(): + + module_specific_arguments = dict( + policyname=dict(type='str'), + url=dict(type='str'), + rule=dict(type='str'), + domain=dict(type='str'), + action=dict(type='str'), + ) + + hand_inserted_arguments = dict( + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'policyname', + 'url', + 'rule', + 'domain', + 'action', + ] + readonly_attrs = [ + 'vstype', + 'hits', + 'bindhits', + 'labelname', + 'labeltype', + 'priority', + 'activepolicy', + 'cspolicytype', + ] + + transforms = { + } + + # Instantiate config proxy + cspolicy_proxy = ConfigProxy( + actual=cspolicy(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Sanity checks for state present') + if not policy_exists(client, module): + if not module.check_mode: + cspolicy_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not policy_identical(client, module, cspolicy_proxy): + if not module.check_mode: + cspolicy_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not policy_exists(client, module): + module.fail_json(msg='Policy does not exist', **module_result) + if not policy_identical(client, module, cspolicy_proxy): + module.fail_json(msg='Policy differs from configured', diff=diff_list(client, module, cspolicy_proxy), **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if policy_exists(client, module): + if not module.check_mode: + cspolicy_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if policy_exists(client, module): + module.fail_json(msg='Policy still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_vserver.py new file mode 100644 index 00000000..96113cdc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_cs_vserver.py @@ -0,0 +1,1302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_vserver +short_description: Manage content switching vserver +description: + - Manage content switching vserver + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the content switching virtual server. Must begin with an ASCII alphanumeric or underscore + C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, + colon C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. + - "Cannot be changed after the CS virtual server is created." + - "Minimum length = 1" + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = 0" + - "Maximum value = 4094" + + servicetype: + choices: + - 'HTTP' + - 'SSL' + - 'TCP' + - 'FTP' + - 'RTSP' + - 'SSL_TCP' + - 'UDP' + - 'DNS' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'ANY' + - 'RADIUS' + - 'RDP' + - 'MYSQL' + - 'MSSQL' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'DNS_TCP' + - 'ORACLE' + - 'SMPP' + description: + - "Protocol used by the virtual server." + + ipv46: + description: + - "IP address of the content switching virtual server." + - "Minimum length = 1" + + targettype: + choices: + - 'GSLB' + description: + - "Virtual server target type." + + ippattern: + description: + - >- + IP address pattern, in dotted decimal notation, for identifying packets to be accepted by the virtual + server. The IP Mask parameter specifies which part of the destination IP address is matched against + the pattern. Mutually exclusive with the IP Address parameter. + - >- + For example, if the IP pattern assigned to the virtual server is C(198.51.100.0) and the IP mask is + C(255.255.240.0) (a forward mask), the first 20 bits in the destination IP addresses are matched with + the first 20 bits in the pattern. The virtual server accepts requests with IP addresses that range + from 198.51.96.1 to 198.51.111.254. You can also use a pattern such as C(0.0.2.2) and a mask such as + C(0.0.255.255) (a reverse mask). + - >- + If a destination IP address matches more than one IP pattern, the pattern with the longest match is + selected, and the associated virtual server processes the request. For example, if the virtual + servers, C(vs1) and C(vs2), have the same IP pattern, C(0.0.100.128), but different IP masks of C(0.0.255.255) + and C(0.0.224.255), a destination IP address of 198.51.100.128 has the longest match with the IP pattern + of C(vs1). If a destination IP address matches two or more virtual servers to the same extent, the + request is processed by the virtual server whose port number matches the port number in the request. + + ipmask: + description: + - >- + IP mask, in dotted decimal notation, for the IP Pattern parameter. Can have leading or trailing + non-zero octets (for example, C(255.255.240.0) or C(0.0.255.255)). Accordingly, the mask specifies whether + the first n bits or the last n bits of the destination IP address in a client request are to be + matched with the corresponding bits in the IP pattern. The former is called a forward mask. The + latter is called a reverse mask. + + range: + description: + - >- + Number of consecutive IP addresses, starting with the address specified by the IP Address parameter, + to include in a range of addresses assigned to this virtual server. + - "Minimum value = C(1)" + - "Maximum value = C(254)" + + port: + description: + - "Port number for content switching virtual server." + - "Minimum value = 1" + - "Range C(1) - C(65535)" + - "* in CLI is represented as 65535 in NITRO API" + + stateupdate: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Enable state updates for a specific content switching virtual server. By default, the Content + Switching virtual server is always UP, regardless of the state of the Load Balancing virtual servers + bound to it. This parameter interacts with the global setting as follows: + - "Global Level | Vserver Level | Result" + - "enabled enabled enabled" + - "enabled disabled enabled" + - "disabled enabled enabled" + - "disabled disabled disabled" + - >- + If you want to enable state updates for only some content switching virtual servers, be sure to + disable the state update parameter. + + cacheable: + description: + - >- + Use this option to specify whether a virtual server, used for load balancing or content switching, + routes requests to the cache redirection virtual server before sending it to the configured servers. + type: bool + + redirecturl: + description: + - >- + URL to which traffic is redirected if the virtual server becomes unavailable. The service type of the + virtual server should be either C(HTTP) or C(SSL). + - >- + Caution: Make sure that the domain in the URL does not match the domain specified for a content + switching policy. If it does, requests are continuously redirected to the unavailable virtual server. + - "Minimum length = 1" + + clttimeout: + description: + - "Idle time, in seconds, after which the client connection is terminated. The default values are:" + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + precedence: + choices: + - 'RULE' + - 'URL' + description: + - >- + Type of precedence to use for both RULE-based and URL-based policies on the content switching virtual + server. With the default C(RULE) setting, incoming requests are evaluated against the rule-based + content switching policies. If none of the rules match, the URL in the request is evaluated against + the URL-based content switching policies. + + casesensitive: + description: + - >- + Consider case in URLs (for policies that use URLs instead of RULES). For example, with the C(on) + setting, the URLs /a/1.html and /A/1.HTML are treated differently and can have different targets (set + by content switching policies). With the C(off) setting, /a/1.html and /A/1.HTML are switched to the + same target. + type: bool + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - >- + Type of spillover used to divert traffic to the backup virtual server when the primary virtual server + reaches the spillover threshold. Connection spillover is based on the number of connections. + Bandwidth spillover is based on the total Kbps of incoming and outgoing traffic. + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - "Maintain source-IP based persistence on primary and backup virtual servers." + + sopersistencetimeout: + description: + - "Time-out value, in minutes, for spillover persistence." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + sothreshold: + description: + - >- + Depending on the spillover method, the maximum number of connections or the maximum total bandwidth + (Kbps) that a virtual server can handle before spillover occurs. + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + redirectportrewrite: + choices: + - 'enabled' + - 'disabled' + description: + - "State of port rewrite while performing HTTP redirect." + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a virtual server whose state transitions from UP to + DOWN. Do not enable this option for applications that must complete their transactions. + + backupvserver: + description: + - >- + Name of the backup virtual server that you are configuring. Must begin with an ASCII alphanumeric or + underscore C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), + space C( ), colon C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the + backup virtual server is created. You can assign a different backup virtual server or rename the + existing virtual server. + - "Minimum length = 1" + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Continue forwarding the traffic to backup virtual server even after the primary server comes UP from + the DOWN state. + + insertvserveripport: + choices: + - 'OFF' + - 'VIPADDR' + - 'V6TOV4MAPPING' + description: + - >- + Insert the virtual server's VIP address and port number in the request header. Available values + function as follows: + - "C(VIPADDR) - Header contains the vserver's IP address and port number without any translation." + - "C(OFF) - The virtual IP and port header insertion option is disabled." + - >- + C(V6TOV4MAPPING) - Header contains the mapped IPv4 address corresponding to the IPv6 address of the + vserver and the port number. An IPv6 address can be mapped to a user-specified IPv4 address using the + set ns ip6 command. + + vipheader: + description: + - "Name of virtual server IP and port header, for use with the VServer IP Port Insertion parameter." + - "Minimum length = 1" + + rtspnat: + description: + - "Enable network address translation (NAT) for real-time streaming protocol (RTSP) connections." + type: bool + + authenticationhost: + description: + - >- + FQDN of the authentication virtual server. The service type of the virtual server should be either + C(HTTP) or C(SSL). + - "Minimum length = 3" + - "Maximum length = 252" + + authentication: + description: + - "Authenticate users who request a connection to the content switching virtual server." + type: bool + + listenpolicy: + description: + - >- + String specifying the listen policy for the content switching virtual server. Can be either the name + of an existing expression or an in-line expression. + + authn401: + description: + - "Enable HTTP 401-response based authentication." + type: bool + + authnvsname: + description: + - >- + Name of authentication virtual server that authenticates the incoming user requests to this content + switching virtual server. . + - "Minimum length = 1" + - "Maximum length = 252" + + push: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Process traffic with the push virtual server that is bound to this content switching virtual server + (specified by the Push VServer parameter). The service type of the push virtual server should be + either C(HTTP) or C(SSL). + + pushvserver: + description: + - >- + Name of the load balancing virtual server, of type C(PUSH) or C(SSL_PUSH), to which the server pushes + updates received on the client-facing load balancing virtual server. + - "Minimum length = 1" + + pushlabel: + description: + - >- + Expression for extracting the label from the response received from server. This string can be either + an existing rule name or an inline expression. The service type of the virtual server should be + either C(HTTP) or C(SSL). + + pushmulticlients: + description: + - >- + Allow multiple Web 2.0 connections from the same client to connect to the virtual server and expect + updates. + type: bool + + tcpprofilename: + description: + - "Name of the TCP profile containing TCP configuration settings for the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - >- + Name of the HTTP profile containing HTTP configuration settings for the virtual server. The service + type of the virtual server should be either C(HTTP) or C(SSL). + - "Minimum length = 1" + - "Maximum length = 127" + + dbprofilename: + description: + - "Name of the DB profile." + - "Minimum length = 1" + - "Maximum length = 127" + + oracleserverversion: + choices: + - '10G' + - '11G' + description: + - "Oracle server version." + + comment: + description: + - "Information about this virtual server." + + mssqlserverversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - "The version of the MSSQL server." + + l2conn: + description: + - "Use L2 Parameters to identify a connection." + type: bool + + mysqlprotocolversion: + description: + - "The protocol version returned by the mysql vserver." + + mysqlserverversion: + description: + - "The server version string returned by the mysql vserver." + - "Minimum length = 1" + - "Maximum length = 31" + + mysqlcharacterset: + description: + - "The character set returned by the mysql vserver." + + mysqlservercapabilities: + description: + - "The server capabilities returned by the mysql vserver." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + netprofile: + description: + - "The name of the network profile." + - "Minimum length = 1" + - "Maximum length = 127" + + icmpvsrresponse: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "Can be active or passive." + + rhistate: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "A host route is injected according to the setting on the virtual servers" + - >- + * If set to C(PASSIVE) on all the virtual servers that share the IP address, the appliance always + injects the hostroute. + - >- + * If set to C(ACTIVE) on all the virtual servers that share the IP address, the appliance injects even + if one virtual server is UP. + - >- + * If set to C(ACTIVE) on some virtual servers and C(PASSIVE) on the others, the appliance, injects even if + one virtual server set to C(ACTIVE) is UP. + + authnprofile: + description: + - "Name of the authentication profile to be used when authentication is turned on." + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the VServer. DNS profile properties will applied to the + transactions processed by a VServer. This parameter is valid only for DNS and DNS-TCP VServers. + - "Minimum length = 1" + - "Maximum length = 127" + + domainname: + description: + - "Domain name for which to change the time to live (TTL) and/or backup service IP address." + - "Minimum length = 1" + + ttl: + description: + - "." + - "Minimum value = C(1)" + + backupip: + description: + - "." + - "Minimum length = 1" + + cookiedomain: + description: + - "." + - "Minimum length = 1" + + cookietimeout: + description: + - "." + - "Minimum value = C(0)" + - "Maximum value = C(1440)" + + sitedomainttl: + description: + - "." + - "Minimum value = C(1)" + + lbvserver: + description: + - The default Load Balancing virtual server. + + ssl_certkey: + description: + - The name of the ssl certificate that is bound to this service. + - The ssl certificate must already exist. + - Creating the certificate can be done with the M(community.network.netscaler_ssl_certkey) module. + - This option is only applicable only when C(servicetype) is C(SSL). + + disabled: + description: + - When set to C(yes) the cs vserver will be disabled. + - When set to C(no) the cs vserver will be enabled. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: 'no' + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# policy_1 must have been already created with the netscaler_cs_policy module +# lbvserver_1 must have been already created with the netscaler_lb_vserver module + +- name: Setup content switching vserver + delegate_to: localhost + community.network.netscaler_cs_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: cs_vserver_1 + ipv46: 192.168.1.1 + port: 80 + servicetype: HTTP + + policybindings: + - policyname: policy_1 + targetlbvserver: lbvserver_1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 100.0 other: (float) 60.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver import csvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_lbvserver_binding import csvserver_lbvserver_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_cspolicy_binding import csvserver_cspolicy_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding import sslvserver_sslcertkey_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def cs_vserver_exists(client, module): + if csvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def cs_vserver_identical(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = csvserver_proxy.diff_object(csvserver_list[0]) + if len(diff_dict) == 0: + return True + else: + return False + + +def get_configured_policybindings(client, module): + log('Getting configured policy bindigs') + bindings = {} + if module.params['policybindings'] is None: + return bindings + + for binding in module.params['policybindings']: + binding['name'] = module.params['name'] + key = binding['policyname'] + binding_proxy = ConfigProxy( + actual=csvserver_cspolicy_binding(), + client=client, + readwrite_attrs=[ + 'priority', + 'bindpoint', + 'policyname', + 'labelname', + 'gotopriorityexpression', + 'targetlbvserver', + 'name', + 'invoke', + 'labeltype', + ], + readonly_attrs=[], + attribute_values_dict=binding + ) + bindings[key] = binding_proxy + return bindings + + +def get_default_lb_vserver(client, module): + try: + default_lb_vserver = csvserver_lbvserver_binding.get(client, module.params['name']) + return default_lb_vserver[0] + except nitro_exception as e: + if e.errorcode == 258: + return csvserver_lbvserver_binding() + else: + raise + + +def default_lb_vserver_identical(client, module): + d = get_default_lb_vserver(client, module) + configured = ConfigProxy( + actual=csvserver_lbvserver_binding(), + client=client, + readwrite_attrs=[ + 'name', + 'lbvserver', + ], + attribute_values_dict={ + 'name': module.params['name'], + 'lbvserver': module.params['lbvserver'], + } + ) + log('default lb vserver %s' % ((d.name, d.lbvserver),)) + if d.name is None and module.params['lbvserver'] is None: + log('Default lb vserver identical missing') + return True + elif d.name is not None and module.params['lbvserver'] is None: + log('Default lb vserver needs removing') + return False + elif configured.has_equal_attributes(d): + log('Default lb vserver identical') + return True + else: + log('Default lb vserver not identical') + return False + + +def sync_default_lb_vserver(client, module): + d = get_default_lb_vserver(client, module) + + if module.params['lbvserver'] is not None: + configured = ConfigProxy( + actual=csvserver_lbvserver_binding(), + client=client, + readwrite_attrs=[ + 'name', + 'lbvserver', + ], + attribute_values_dict={ + 'name': module.params['name'], + 'lbvserver': module.params['lbvserver'], + } + ) + + if not configured.has_equal_attributes(d): + if d.name is not None: + log('Deleting default lb vserver %s' % d.lbvserver) + csvserver_lbvserver_binding.delete(client, d) + log('Adding default lb vserver %s' % configured.lbvserver) + configured.add() + else: + if d.name is not None: + log('Deleting default lb vserver %s' % d.lbvserver) + csvserver_lbvserver_binding.delete(client, d) + + +def get_actual_policybindings(client, module): + log('Getting actual policy bindigs') + bindings = {} + try: + count = csvserver_cspolicy_binding.count(client, name=module.params['name']) + if count == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + for binding in csvserver_cspolicy_binding.get(client, name=module.params['name']): + key = binding.policyname + bindings[key] = binding + + return bindings + + +def cs_policybindings_identical(client, module): + log('Checking policy bindings identical') + actual_bindings = get_actual_policybindings(client, module) + configured_bindings = get_configured_policybindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_bindings.keys()) + if len(actual_keyset ^ configured_keyset) > 0: + return False + + # Compare item to item + for key in actual_bindings.keys(): + configured_binding_proxy = configured_bindings[key] + actual_binding_object = actual_bindings[key] + if not configured_binding_proxy.has_equal_attributes(actual_binding_object): + return False + + # Fallthrough to success + return True + + +def sync_cs_policybindings(client, module): + log('Syncing cs policybindings') + actual_bindings = get_actual_policybindings(client, module) + configured_bindings = get_configured_policybindings(client, module) + + # Delete actual bindings not in configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindings.keys())) + for key in delete_keys: + log('Deleting binding for policy %s' % key) + csvserver_cspolicy_binding.delete(client, actual_bindings[key]) + + # Add configured bindings not in actual + add_keys = list(set(configured_bindings.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding binding for policy %s' % key) + configured_bindings[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindings.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindings[key].has_equal_attributes(actual_bindings[key]): + log('Updating binding for policy %s' % key) + csvserver_cspolicy_binding.delete(client, actual_bindings[key]) + configured_bindings[key].add() + + +def ssl_certkey_bindings_identical(client, module): + log('Checking if ssl cert key bindings are identical') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + if module.params['ssl_certkey'] is None: + if len(bindings) == 0: + return True + else: + return False + else: + certificate_list = [item.certkeyname for item in bindings] + if certificate_list == [module.params['ssl_certkey']]: + return True + else: + return False + + +def ssl_certkey_bindings_sync(client, module): + log('Syncing certkey bindings') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + # Delete existing bindings + for binding in bindings: + log('Deleting existing binding for certkey %s' % binding.certkeyname) + sslvserver_sslcertkey_binding.delete(client, binding) + + # Add binding if appropriate + if module.params['ssl_certkey'] is not None: + log('Adding binding for certkey %s' % module.params['ssl_certkey']) + binding = sslvserver_sslcertkey_binding() + binding.vservername = module.params['name'] + binding.certkeyname = module.params['ssl_certkey'] + sslvserver_sslcertkey_binding.add(client, binding) + + +def diff_list(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + return csvserver_proxy.diff_object(csvserver_list[0]) + + +def do_state_change(client, module, csvserver_proxy): + if module.params['disabled']: + log('Disabling cs vserver') + result = csvserver.disable(client, csvserver_proxy.actual) + else: + log('Enabling cs vserver') + result = csvserver.enable(client, csvserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + + name=dict(type='str'), + td=dict(type='float'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'SSL', + 'TCP', + 'FTP', + 'RTSP', + 'SSL_TCP', + 'UDP', + 'DNS', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'ANY', + 'RADIUS', + 'RDP', + 'MYSQL', + 'MSSQL', + 'DIAMETER', + 'SSL_DIAMETER', + 'DNS_TCP', + 'ORACLE', + 'SMPP' + ] + ), + + ipv46=dict(type='str'), + dnsrecordtype=dict( + type='str', + choices=[ + 'A', + 'AAAA', + 'CNAME', + 'NAPTR', + ] + ), + ippattern=dict(type='str'), + ipmask=dict(type='str'), + range=dict(type='float'), + port=dict(type='int'), + stateupdate=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cacheable=dict(type='bool'), + redirecturl=dict(type='str'), + clttimeout=dict(type='float'), + precedence=dict( + type='str', + choices=[ + 'RULE', + 'URL', + ] + ), + casesensitive=dict(type='bool'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + redirectportrewrite=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + insertvserveripport=dict( + type='str', + choices=[ + 'OFF', + 'VIPADDR', + 'V6TOV4MAPPING', + ] + ), + vipheader=dict(type='str'), + rtspnat=dict(type='bool'), + authenticationhost=dict(type='str'), + authentication=dict(type='bool'), + listenpolicy=dict(type='str'), + authn401=dict(type='bool'), + authnvsname=dict(type='str'), + push=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + pushvserver=dict(type='str'), + pushlabel=dict(type='str'), + pushmulticlients=dict(type='bool'), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + dbprofilename=dict(type='str'), + oracleserverversion=dict( + type='str', + choices=[ + '10G', + '11G', + ] + ), + comment=dict(type='str'), + mssqlserverversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + l2conn=dict(type='bool'), + mysqlprotocolversion=dict(type='float'), + mysqlserverversion=dict(type='str'), + mysqlcharacterset=dict(type='float'), + mysqlservercapabilities=dict(type='float'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + icmpvsrresponse=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + rhistate=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + authnprofile=dict(type='str'), + dnsprofilename=dict(type='str'), + ) + + hand_inserted_arguments = dict( + policybindings=dict(type='list'), + ssl_certkey=dict(type='str'), + disabled=dict( + type='bool', + default=False + ), + lbvserver=dict(type='str'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'td', + 'servicetype', + 'ipv46', + 'dnsrecordtype', + 'ippattern', + 'ipmask', + 'range', + 'port', + 'stateupdate', + 'cacheable', + 'redirecturl', + 'clttimeout', + 'precedence', + 'casesensitive', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'sothreshold', + 'sobackupaction', + 'redirectportrewrite', + 'downstateflush', + 'disableprimaryondown', + 'insertvserveripport', + 'vipheader', + 'rtspnat', + 'authenticationhost', + 'authentication', + 'listenpolicy', + 'authn401', + 'authnvsname', + 'push', + 'pushvserver', + 'pushlabel', + 'pushmulticlients', + 'tcpprofilename', + 'httpprofilename', + 'dbprofilename', + 'oracleserverversion', + 'comment', + 'mssqlserverversion', + 'l2conn', + 'mysqlprotocolversion', + 'mysqlserverversion', + 'mysqlcharacterset', + 'mysqlservercapabilities', + 'appflowlog', + 'netprofile', + 'icmpvsrresponse', + 'rhistate', + 'authnprofile', + 'dnsprofilename', + ] + + readonly_attrs = [ + 'ip', + 'value', + 'ngname', + 'type', + 'curstate', + 'sc', + 'status', + 'cachetype', + 'redirect', + 'homepage', + 'dnsvservername', + 'domain', + 'policyname', + 'servicename', + 'weight', + 'cachevserver', + 'targetvserver', + 'priority', + 'url', + 'gotopriorityexpression', + 'bindpoint', + 'invoke', + 'labeltype', + 'labelname', + 'gt2gb', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'ruletype', + 'lbvserver', + 'targetlbvserver', + ] + + immutable_attrs = [ + 'name', + 'td', + 'servicetype', + 'ipv46', + 'targettype', + 'range', + 'port', + 'state', + 'vipheader', + 'newname', + ] + + transforms = { + 'cacheable': ['bool_yes_no'], + 'rtspnat': ['bool_on_off'], + 'authn401': ['bool_on_off'], + 'casesensitive': ['bool_on_off'], + 'authentication': ['bool_on_off'], + 'l2conn': ['bool_on_off'], + 'pushmulticlients': ['bool_yes_no'], + 'stateupdate': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'redirectportrewrite': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'push': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + csvserver_proxy = ConfigProxy( + actual=csvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not cs_vserver_identical(client, module, csvserver_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(csvserver_proxy, diff_list(client, module, csvserver_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, csvserver_proxy), + **module_result + ) + + if not module.check_mode: + csvserver_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Check policybindings + if not cs_policybindings_identical(client, module): + if not module.check_mode: + sync_cs_policybindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if module.params['servicetype'] != 'SSL' and module.params['ssl_certkey'] is not None: + module.fail_json(msg='ssl_certkey is applicable only to SSL vservers', **module_result) + + # Check ssl certkey bindings + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + if not module.check_mode: + ssl_certkey_bindings_sync(client, module) + + module_result['changed'] = True + + # Check default lb vserver + if not default_lb_vserver_identical(client, module): + if not module.check_mode: + sync_default_lb_vserver(client, module) + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, csvserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not cs_vserver_exists(client, module): + module.fail_json(msg='CS vserver does not exist', **module_result) + if not cs_vserver_identical(client, module, csvserver_proxy): + module.fail_json(msg='CS vserver differs from configured', diff=diff_list(client, module, csvserver_proxy), **module_result) + if not cs_policybindings_identical(client, module): + module.fail_json(msg='Policy bindings differ') + + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + module.fail_json(msg='sll certkey bindings not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if cs_vserver_exists(client, module): + module.fail_json(msg='CS vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_service.py new file mode 100644 index 00000000..5151e67c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_service.py @@ -0,0 +1,691 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_service +short_description: Manage gslb service entities in Netscaler. +description: + - Manage gslb service entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + servicename: + description: + - >- + Name for the GSLB service. Must begin with an ASCII alphanumeric or underscore C(_) character, and + must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@), + equals C(=), and hyphen C(-) characters. Can be changed after the GSLB service is created. + - >- + - "Minimum length = 1" + + cnameentry: + description: + - "Canonical name of the GSLB service. Used in CNAME-based GSLB." + - "Minimum length = 1" + + + servername: + description: + - "Name of the server hosting the GSLB service." + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'NNTP' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'RADIUS' + - 'RDP' + - 'RTSP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + description: + - "Type of service to create." + + port: + description: + - "Port on which the load balancing entity represented by this GSLB service listens." + - "Minimum value = 1" + - "Range 1 - 65535" + - "* in CLI is represented as 65535 in NITRO API" + + publicip: + description: + - >- + The public IP address that a NAT device translates to the GSLB service's private IP address. + Optional. + + publicport: + description: + - >- + The public port associated with the GSLB service's public IP address. The port is mapped to the + service's private port number. Applicable to the local GSLB service. Optional. + + maxclient: + description: + - >- + The maximum number of open connections that the service can support at any given time. A GSLB service + whose connection count reaches the maximum is not considered when a GSLB decision is made, until the + connection count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(4294967294)" + + healthmonitor: + description: + - "Monitor the health of the GSLB service." + type: bool + + sitename: + description: + - "Name of the GSLB site to which the service belongs." + - "Minimum length = 1" + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - >- + In the request that is forwarded to the GSLB service, insert a header that stores the client's IP + address. Client IP header insertion is used in connection-proxy based site persistence. + + cipheader: + description: + - >- + Name for the HTTP header that stores the client's IP address. Used with the Client IP option. If + client IP header insertion is enabled on the service and a name is not specified for the header, the + NetScaler appliance uses the name specified by the cipHeader parameter in the set ns param command + or, in the GUI, the Client IP Header parameter in the Configure HTTP Parameters dialog box. + - "Minimum length = 1" + + sitepersistence: + choices: + - 'ConnectionProxy' + - 'HTTPRedirect' + - 'NONE' + description: + - "Use cookie-based site persistence. Applicable only to C(HTTP) and C(SSL) GSLB services." + + siteprefix: + description: + - >- + The site's prefix string. When the service is bound to a GSLB virtual server, a GSLB site domain is + generated internally for each bound service-domain pair by concatenating the site prefix of the + service and the name of the domain. If the special string NONE is specified, the site-prefix string + is unset. When implementing HTTP redirect site persistence, the NetScaler appliance redirects GSLB + requests to GSLB services by using their site domains. + + clttimeout: + description: + - >- + Idle time, in seconds, after which a client connection is terminated. Applicable if connection proxy + based site persistence is used. + - "Minimum value = 0" + - "Maximum value = 31536000" + + maxbandwidth: + description: + - >- + Integer specifying the maximum bandwidth allowed for the service. A GSLB service whose bandwidth + reaches the maximum is not considered when a GSLB decision is made, until its bandwidth consumption + drops below the maximum. + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with the GSLB service when its state transitions from UP to + DOWN. Do not enable this option for services that must complete their transactions. Applicable if + connection proxy based site persistence is used. + + maxaaausers: + description: + - >- + Maximum number of SSL VPN users that can be logged on concurrently to the VPN virtual server that is + represented by this GSLB service. A GSLB service whose user count reaches the maximum is not + considered when a GSLB decision is made, until the count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + monthreshold: + description: + - >- + Monitoring threshold value for the GSLB service. If the sum of the weights of the monitors that are + bound to this GSLB service and are in the UP state is not equal to or greater than this threshold + value, the service is marked as DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + hashid: + description: + - "Unique hash identifier for the GSLB service, used by hash based load balancing methods." + - "Minimum value = C(1)" + + comment: + description: + - "Any comments that you might want to associate with the GSLB service." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + ipaddress: + description: + - >- + IP address for the GSLB service. Should represent a load balancing, content switching, or VPN virtual + server on the NetScaler appliance, or the IP address of another load balancing device. + + monitor_bindings: + description: + - Bind monitors to this gslb service + suboptions: + + weight: + description: + - Weight to assign to the monitor-service binding. + - A larger number specifies a greater weight. + - Contributes to the monitoring threshold, which determines the state of the service. + - Minimum value = C(1) + - Maximum value = C(100) + + monitor_name: + description: + - Monitor name. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup gslb service 2 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + community.network.netscaler_gslb_service: + operation: present + + servicename: gslb-service-2 + cnameentry: example.com + sitename: gslb-site-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +import copy + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + monkey_patch_nitro_api, + get_immutables_intersection, +) + +try: + monkey_patch_nitro_api() + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice import gslbservice + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding import gslbservice_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def gslb_service_exists(client, module): + if gslbservice.count_filtered(client, 'servicename:%s' % module.params['servicename']) > 0: + return True + else: + return False + + +def gslb_service_identical(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_dict = gslb_service_proxy.diff_object(gslb_service_list[0]) + # Ignore ip attribute missing + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def get_actual_monitor_bindings(client, module): + log('get_actual_monitor_bindings') + # Get actual monitor bindings and index them by monitor_name + actual_monitor_bindings = {} + if gslbservice_lbmonitor_binding.count(client, servicename=module.params['servicename']) != 0: + # Get all monitor bindings associated with the named gslb vserver + fetched_bindings = gslbservice_lbmonitor_binding.get(client, servicename=module.params['servicename']) + # index by monitor name + for binding in fetched_bindings: + # complete_missing_attributes(binding, gslbservice_lbmonitor_binding_rw_attrs, fill_value=None) + actual_monitor_bindings[binding.monitor_name] = binding + return actual_monitor_bindings + + +def get_configured_monitor_bindings(client, module): + log('get_configured_monitor_bindings') + configured_monitor_proxys = {} + gslbservice_lbmonitor_binding_rw_attrs = [ + 'weight', + 'servicename', + 'monitor_name', + ] + # Get configured monitor bindings and index them by monitor_name + if module.params['monitor_bindings'] is not None: + for configured_monitor_bindings in module.params['monitor_bindings']: + binding_values = copy.deepcopy(configured_monitor_bindings) + binding_values['servicename'] = module.params['servicename'] + proxy = ConfigProxy( + actual=gslbservice_lbmonitor_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbservice_lbmonitor_binding_rw_attrs, + readonly_attrs=[], + ) + configured_monitor_proxys[configured_monitor_bindings['monitor_name']] = proxy + return configured_monitor_proxys + + +def monitor_bindings_identical(client, module): + log('monitor_bindings_identical') + actual_bindings = get_actual_monitor_bindings(client, module) + configured_proxys = get_configured_monitor_bindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + if len(symmetric_difference) != 0: + log('Symmetric difference %s' % symmetric_difference) + return False + + # Item for item equality test + for key, proxy in configured_proxys.items(): + if not proxy.has_equal_attributes(actual_bindings[key]): + log('monitor binding difference %s' % proxy.diff_object(actual_bindings[key])) + return False + + # Fallthrough to True result + return True + + +def sync_monitor_bindings(client, module): + log('sync_monitor_bindings') + + actual_monitor_bindings = get_actual_monitor_bindings(client, module) + configured_monitor_proxys = get_configured_monitor_bindings(client, module) + + # Delete actual bindings not in configured bindings + for monitor_name, actual_binding in actual_monitor_bindings.items(): + if monitor_name not in configured_monitor_proxys.keys(): + log('Deleting absent binding for monitor %s' % monitor_name) + log('dir is %s' % dir(actual_binding)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + + # Delete and re-add actual bindings that differ from configured + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key in actual_monitor_bindings: + actual_binding = actual_monitor_bindings[proxy_key] + if not binding_proxy.has_equal_attributes(actual_binding): + log('Deleting differing binding for monitor %s' % actual_binding.monitor_name) + log('dir %s' % dir(actual_binding)) + log('attribute monitor_name %s' % getattr(actual_binding, 'monitor_name')) + log('attribute monitorname %s' % getattr(actual_binding, 'monitorname', None)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + log('Adding anew binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + # Add configured monitors that are missing from actual + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key not in actual_monitor_bindings.keys(): + log('Adding monitor binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + +def diff_list(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_list = gslb_service_proxy.diff_object(gslb_service_list[0]) + if 'ip' in diff_list: + del diff_list['ip'] + return diff_list + + +def all_identical(client, module, gslb_service_proxy): + return gslb_service_identical(client, module, gslb_service_proxy) and monitor_bindings_identical(client, module) + + +def main(): + + module_specific_arguments = dict( + servicename=dict(type='str'), + cnameentry=dict(type='str'), + servername=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'NNTP', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'RADIUS', + 'RDP', + 'RTSP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + ] + ), + port=dict(type='int'), + publicip=dict(type='str'), + publicport=dict(type='int'), + maxclient=dict(type='float'), + healthmonitor=dict(type='bool'), + sitename=dict(type='str'), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + sitepersistence=dict( + type='str', + choices=[ + 'ConnectionProxy', + 'HTTPRedirect', + 'NONE', + ] + ), + siteprefix=dict(type='str'), + clttimeout=dict(type='float'), + maxbandwidth=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + maxaaausers=dict(type='float'), + monthreshold=dict(type='float'), + hashid=dict(type='float'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + ipaddress=dict(type='str'), + ) + + hand_inserted_arguments = dict( + monitor_bindings=dict(type='list'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'publicip', + 'publicport', + 'maxclient', + 'healthmonitor', + 'sitename', + 'cip', + 'cipheader', + 'sitepersistence', + 'siteprefix', + 'clttimeout', + 'maxbandwidth', + 'downstateflush', + 'maxaaausers', + 'monthreshold', + 'hashid', + 'comment', + 'appflowlog', + 'ipaddress', + ] + + readonly_attrs = [ + 'gslb', + 'svrstate', + 'svreffgslbstate', + 'gslbthreshold', + 'gslbsvcstats', + 'monstate', + 'preferredlocation', + 'monitor_state', + 'statechangetimesec', + 'tickssincelaststatechange', + 'threshold', + 'clmonowner', + 'clmonview', + '__count', + ] + + immutable_attrs = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'sitename', + 'state', + 'cipheader', + 'cookietimeout', + 'clttimeout', + 'svrtimeout', + 'viewip', + 'monitor_name_svc', + 'newname', + ] + + transforms = { + 'healthmonitor': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # params = copy.deepcopy(module.params) + module.params['ip'] = module.params['ipaddress'] + + # Instantiate config proxy + gslb_service_proxy = ConfigProxy( + actual=gslbservice(), + client=client, + attribute_values_dict=module.params, + transforms=transforms, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + # Apply appropriate state + if module.params['state'] == 'present': + if not gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_proxy.add() + sync_monitor_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, gslb_service_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(gslb_service_proxy, diff_list(client, module, gslb_service_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + # Update main configuration object + if not gslb_service_identical(client, module, gslb_service_proxy): + if not module.check_mode: + gslb_service_proxy.update() + + # Update monitor bindigns + if not monitor_bindings_identical(client, module): + if not module.check_mode: + sync_monitor_bindings(client, module) + + # Fallthrough to save and change status update + module_result['changed'] = True + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if not gslb_service_exists(client, module): + module.fail_json(msg='GSLB service does not exist', **module_result) + if not gslb_service_identical(client, module, gslb_service_proxy): + module.fail_json( + msg='GSLB service differs from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + if not monitor_bindings_identical(client, module): + module.fail_json( + msg='Monitor bindings differ from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + if gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if gslb_service_exists(client, module): + module.fail_json(msg='GSLB service still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_site.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_site.py new file mode 100644 index 00000000..96b9faeb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_site.py @@ -0,0 +1,419 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_site +short_description: Manage gslb site entities in Netscaler. +description: + - Manage gslb site entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + sitename: + description: + - >- + Name for the GSLB site. Must begin with an ASCII alphanumeric or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Cannot be changed after the virtual server is created. + - "Minimum length = 1" + + sitetype: + choices: + - 'REMOTE' + - 'LOCAL' + description: + - >- + Type of site to create. If the type is not specified, the appliance automatically detects and sets + the type on the basis of the IP address being assigned to the site. If the specified site IP address + is owned by the appliance (for example, a MIP address or SNIP address), the site is a local site. + Otherwise, it is a remote site. + + siteipaddress: + description: + - >- + IP address for the GSLB site. The GSLB site uses this IP address to communicate with other GSLB + sites. For a local site, use any IP address that is owned by the appliance (for example, a SNIP or + MIP address, or the IP address of the ADNS service). + - "Minimum length = 1" + + publicip: + description: + - >- + Public IP address for the local site. Required only if the appliance is deployed in a private address + space and the site has a public IP address hosted on an external firewall or a NAT device. + - "Minimum length = 1" + + metricexchange: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Exchange metrics with other sites. Metrics are exchanged by using Metric Exchange Protocol (MEP). The + appliances in the GSLB setup exchange health information once every second. + - >- + If you disable metrics exchange, you can use only static load balancing methods (such as round robin, + static proximity, or the hash-based methods), and if you disable metrics exchange when a dynamic load + balancing method (such as least connection) is in operation, the appliance falls back to round robin. + Also, if you disable metrics exchange, you must use a monitor to determine the state of GSLB + services. Otherwise, the service is marked as DOWN. + + nwmetricexchange: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Exchange, with other GSLB sites, network metrics such as round-trip time (RTT), learned from + communications with various local DNS (LDNS) servers used by clients. RTT information is used in the + dynamic RTT load balancing method, and is exchanged every 5 seconds. + + sessionexchange: + choices: + - 'enabled' + - 'disabled' + description: + - "Exchange persistent session entries with other GSLB sites every five seconds." + + triggermonitor: + choices: + - 'ALWAYS' + - 'MEPDOWN' + - 'MEPDOWN_SVCDOWN' + description: + - >- + Specify the conditions under which the GSLB service must be monitored by a monitor, if one is bound. + Available settings function as follows: + - "* C(ALWAYS) - Monitor the GSLB service at all times." + - >- + * C(MEPDOWN) - Monitor the GSLB service only when the exchange of metrics through the Metrics Exchange + Protocol (MEP) is disabled. + - "C(MEPDOWN_SVCDOWN) - Monitor the service in either of the following situations:" + - "* The exchange of metrics through MEP is disabled." + - >- + * The exchange of metrics through MEP is enabled but the status of the service, learned through + metrics exchange, is DOWN. + + parentsite: + description: + - "Parent site of the GSLB site, in a parent-child topology." + + clip: + description: + - >- + Cluster IP address. Specify this parameter to connect to the remote cluster site for GSLB auto-sync. + Note: The cluster IP address is defined when creating the cluster. + + publicclip: + description: + - >- + IP address to be used to globally access the remote cluster when it is deployed behind a NAT. It can + be same as the normal cluster IP address. + + naptrreplacementsuffix: + description: + - >- + The naptr replacement suffix configured here will be used to construct the naptr replacement field in + NAPTR record. + - "Minimum length = 1" + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup gslb site + delegate_to: localhost + community.network.netscaler_gslb_site: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + sitename: gslb-site-1 + siteipaddress: 192.168.1.1 + sitetype: LOCAL + publicip: 192.168.1.1 + metricexchange: enabled + nwmetricexchange: enabled + sessionexchange: enabled + triggermonitor: ALWAYS + +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbsite import gslbsite + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection, +) + + +def gslb_site_exists(client, module): + if gslbsite.count_filtered(client, 'sitename:%s' % module.params['sitename']) > 0: + return True + else: + return False + + +def gslb_site_identical(client, module, gslb_site_proxy): + gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename']) + diff_dict = gslb_site_proxy.diff_object(gslb_site_list[0]) + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, gslb_site_proxy): + gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename']) + return gslb_site_proxy.diff_object(gslb_site_list[0]) + + +def main(): + + module_specific_arguments = dict( + sitename=dict(type='str'), + sitetype=dict( + type='str', + choices=[ + 'REMOTE', + 'LOCAL', + ] + ), + siteipaddress=dict(type='str'), + publicip=dict(type='str'), + metricexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + nwmetricexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sessionexchange=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + triggermonitor=dict( + type='str', + choices=[ + 'ALWAYS', + 'MEPDOWN', + 'MEPDOWN_SVCDOWN', + ] + ), + parentsite=dict(type='str'), + clip=dict(type='str'), + publicclip=dict(type='str'), + naptrreplacementsuffix=dict(type='str'), + ) + + hand_inserted_arguments = dict( + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'sitename', + 'sitetype', + 'siteipaddress', + 'publicip', + 'metricexchange', + 'nwmetricexchange', + 'sessionexchange', + 'triggermonitor', + 'parentsite', + 'clip', + 'publicclip', + 'naptrreplacementsuffix', + ] + + readonly_attrs = [ + 'status', + 'persistencemepstatus', + 'version', + '__count', + ] + + immutable_attrs = [ + 'sitename', + 'sitetype', + 'siteipaddress', + 'publicip', + 'parentsite', + 'clip', + 'publicclip', + ] + + transforms = { + 'metricexchange': [lambda v: v.upper()], + 'nwmetricexchange': [lambda v: v.upper()], + 'sessionexchange': [lambda v: v.upper()], + } + + # Instantiate config proxy + gslb_site_proxy = ConfigProxy( + actual=gslbsite(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not gslb_site_exists(client, module): + if not module.check_mode: + gslb_site_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not gslb_site_identical(client, module, gslb_site_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(gslb_site_proxy, diff_list(client, module, gslb_site_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_site_proxy), + **module_result + ) + + if not module.check_mode: + gslb_site_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not gslb_site_exists(client, module): + module.fail_json(msg='GSLB site does not exist', **module_result) + if not gslb_site_identical(client, module, gslb_site_proxy): + module.fail_json(msg='GSLB site differs from configured', diff=diff_list(client, module, gslb_site_proxy), **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if gslb_site_exists(client, module): + if not module.check_mode: + gslb_site_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if gslb_site_exists(client, module): + module.fail_json(msg='GSLB site still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_vserver.py new file mode 100644 index 00000000..2f10fcf7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_gslb_vserver.py @@ -0,0 +1,951 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_vserver +short_description: Configure gslb vserver entities in Netscaler. +description: + - Configure gslb vserver entities in Netscaler. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the GSLB virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character, + and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@), + equals C(=), and hyphen C(-) characters. Can be changed after the virtual server is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'NNTP' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'RADIUS' + - 'RDP' + - 'RTSP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + description: + - "Protocol used by services bound to the virtual server." + - >- + + dnsrecordtype: + choices: + - 'A' + - 'AAAA' + - 'CNAME' + - 'NAPTR' + description: + - "DNS record type to associate with the GSLB virtual server's domain name." + - "Default value: A" + - "Possible values = A, AAAA, CNAME, NAPTR" + + lbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'STATICPROXIMITY' + - 'RTT' + - 'CUSTOMLOAD' + description: + - "Load balancing method for the GSLB virtual server." + - "Default value: LEASTCONNECTION" + - >- + Possible values = ROUNDROBIN, LEASTCONNECTION, LEASTRESPONSETIME, SOURCEIPHASH, LEASTBANDWIDTH, + LEASTPACKETS, STATICPROXIMITY, RTT, CUSTOMLOAD + + backuplbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'STATICPROXIMITY' + - 'RTT' + - 'CUSTOMLOAD' + description: + - >- + Backup load balancing method. Becomes operational if the primary load balancing method fails or + cannot be used. Valid only if the primary method is based on either round-trip time (RTT) or static + proximity. + + netmask: + description: + - "IPv4 network mask for use in the SOURCEIPHASH load balancing method." + - "Minimum length = 1" + + v6netmasklen: + description: + - >- + Number of bits to consider, in an IPv6 source IP address, for creating the hash that is required by + the C(SOURCEIPHASH) load balancing method. + - "Default value: C(128)" + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + tolerance: + description: + - >- + Site selection tolerance, in milliseconds, for implementing the RTT load balancing method. If a + site's RTT deviates from the lowest RTT by more than the specified tolerance, the site is not + considered when the NetScaler appliance makes a GSLB decision. The appliance implements the round + robin method of global server load balancing between sites whose RTT values are within the specified + tolerance. If the tolerance is 0 (zero), the appliance always sends clients the IP address of the + site with the lowest RTT. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + persistencetype: + choices: + - 'SOURCEIP' + - 'NONE' + description: + - "Use source IP address based persistence for the virtual server." + - >- + After the load balancing method selects a service for the first packet, the IP address received in + response to the DNS query is used for subsequent requests from the same client. + + persistenceid: + description: + - >- + The persistence ID for the GSLB virtual server. The ID is a positive integer that enables GSLB sites + to identify the GSLB virtual server, and is required if source IP address based or spill over based + persistence is enabled on the virtual server. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + persistmask: + description: + - >- + The optional IPv4 network mask applied to IPv4 addresses to establish source IP address based + persistence. + - "Minimum length = 1" + + v6persistmasklen: + description: + - >- + Number of bits to consider in an IPv6 source IP address when creating source IP address based + persistence sessions. + - "Default value: C(128)" + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + timeout: + description: + - "Idle time, in minutes, after which a persistence entry is cleared." + - "Default value: C(2)" + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + mir: + choices: + - 'enabled' + - 'disabled' + description: + - "Include multiple IP addresses in the DNS responses sent to clients." + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Continue to direct traffic to the backup chain even after the primary GSLB virtual server returns to + the UP state. Used when spillover is configured for the virtual server. + + dynamicweight: + choices: + - 'SERVICECOUNT' + - 'SERVICEWEIGHT' + - 'DISABLED' + description: + - >- + Specify if the appliance should consider the service count, service weights, or ignore both when + using weight-based load balancing methods. The state of the number of services bound to the virtual + server help the appliance to select the service. + + considereffectivestate: + choices: + - 'NONE' + - 'STATE_ONLY' + description: + - >- + If the primary state of all bound GSLB services is DOWN, consider the effective states of all the + GSLB services, obtained through the Metrics Exchange Protocol (MEP), when determining the state of + the GSLB virtual server. To consider the effective state, set the parameter to STATE_ONLY. To + disregard the effective state, set the parameter to NONE. + - >- + The effective state of a GSLB service is the ability of the corresponding virtual server to serve + traffic. The effective state of the load balancing virtual server, which is transferred to the GSLB + service, is UP even if only one virtual server in the backup chain of virtual servers is in the UP + state. + + comment: + description: + - "Any comments that you might want to associate with the GSLB virtual server." + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:" + - "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold." + - >- + * C(DYNAMICCONNECTION) - Spillover occurs when the number of client connections at the GSLB virtual + server exceeds the sum of the maximum client (Max Clients) settings for bound GSLB services. Do not + specify a spillover threshold for this setting, because the threshold is implied by the Max Clients + settings of the bound GSLB services. + - >- + * C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the GSLB virtual server's incoming and + outgoing traffic exceeds the threshold. + - >- + * C(HEALTH) - Spillover occurs when the percentage of weights of the GSLB services that are UP drops + below the threshold. For example, if services gslbSvc1, gslbSvc2, and gslbSvc3 are bound to a virtual + server, with weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if gslbSvc1 + and gslbSvc3 or gslbSvc2 and gslbSvc3 transition to DOWN. + - "* C(NONE) - Spillover does not occur." + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If spillover occurs, maintain source IP address based persistence for both primary and backup GSLB + virtual servers. + + sopersistencetimeout: + description: + - "Timeout for spillover persistence, in minutes." + - "Default value: C(2)" + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + sothreshold: + description: + - >- + Threshold at which spillover occurs. Specify an integer for the CONNECTION spillover method, a + bandwidth value in kilobits per second for the BANDWIDTH method (do not enter the units), or a + percentage for the HEALTH method (do not enter the percentage symbol). + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + domain_bindings: + description: + - >- + List of bindings for domains for this glsb vserver. + suboptions: + cookietimeout: + description: + - Timeout, in minutes, for the GSLB site cookie. + + domainname: + description: + - Domain name for which to change the time to live (TTL) and/or backup service IP address. + + ttl: + description: + - Time to live (TTL) for the domain. + + sitedomainttl: + description: + - >- + TTL, in seconds, for all internally created site domains (created when a site prefix is + configured on a GSLB service) that are associated with this virtual server. + - Minimum value = C(1) + + service_bindings: + description: + - List of bindings for gslb services bound to this gslb virtual server. + suboptions: + servicename: + description: + - Name of the GSLB service for which to change the weight. + weight: + description: + - Weight to assign to the GSLB service. + + disabled: + description: + - When set to C(yes) the GSLB Vserver state will be set to C(disabled). + - When set to C(no) the GSLB Vserver state will be set to C(enabled). + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + + + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# FIXME: Add examples +''' + +RETURN = ''' +''' + + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver import gslbvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_gslbservice_binding import gslbvserver_gslbservice_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_domain_binding import gslbvserver_domain_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection, + complete_missing_attributes +) + + +gslbvserver_domain_binding_rw_attrs = [ + 'name', + 'domainname', + 'backupipflag', + 'cookietimeout', + 'backupip', + 'ttl', + 'sitedomainttl', + 'cookie_domainflag', +] + +gslbvserver_gslbservice_binding_rw_attrs = [ + 'name', + 'servicename', + 'weight', +] + + +def get_actual_domain_bindings(client, module): + log('get_actual_domain_bindings') + # Get actual domain bindings and index them by domainname + actual_domain_bindings = {} + if gslbvserver_domain_binding.count(client, name=module.params['name']) != 0: + # Get all domain bindings associated with the named gslb vserver + fetched_domain_bindings = gslbvserver_domain_binding.get(client, name=module.params['name']) + # index by domainname + for binding in fetched_domain_bindings: + complete_missing_attributes(binding, gslbvserver_domain_binding_rw_attrs, fill_value=None) + actual_domain_bindings[binding.domainname] = binding + return actual_domain_bindings + + +def get_configured_domain_bindings_proxys(client, module): + log('get_configured_domain_bindings_proxys') + configured_domain_proxys = {} + # Get configured domain bindings and index them by domainname + if module.params['domain_bindings'] is not None: + for configured_domain_binding in module.params['domain_bindings']: + binding_values = copy.deepcopy(configured_domain_binding) + binding_values['name'] = module.params['name'] + gslbvserver_domain_binding_proxy = ConfigProxy( + actual=gslbvserver_domain_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbvserver_domain_binding_rw_attrs, + readonly_attrs=[], + ) + configured_domain_proxys[configured_domain_binding['domainname']] = gslbvserver_domain_binding_proxy + return configured_domain_proxys + + +def sync_domain_bindings(client, module): + log('sync_domain_bindings') + + actual_domain_bindings = get_actual_domain_bindings(client, module) + configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) + + # Delete actual bindings not in configured bindings + for domainname, actual_domain_binding in actual_domain_bindings.items(): + if domainname not in configured_domain_proxys.keys(): + log('Deleting absent binding for domain %s' % domainname) + gslbvserver_domain_binding.delete(client, actual_domain_binding) + + # Delete actual bindings that differ from configured + for proxy_key, binding_proxy in configured_domain_proxys.items(): + if proxy_key in actual_domain_bindings: + actual_binding = actual_domain_bindings[proxy_key] + if not binding_proxy.has_equal_attributes(actual_binding): + log('Deleting differing binding for domain %s' % binding_proxy.domainname) + gslbvserver_domain_binding.delete(client, actual_binding) + log('Adding anew binding for domain %s' % binding_proxy.domainname) + binding_proxy.add() + + # Add configured domains that are missing from actual + for proxy_key, binding_proxy in configured_domain_proxys.items(): + if proxy_key not in actual_domain_bindings.keys(): + log('Adding domain binding for domain %s' % binding_proxy.domainname) + binding_proxy.add() + + +def domain_bindings_identical(client, module): + log('domain_bindings_identical') + actual_domain_bindings = get_actual_domain_bindings(client, module) + configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) + + actual_keyset = set(actual_domain_bindings.keys()) + configured_keyset = set(configured_domain_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + + log('symmetric difference %s' % symmetric_difference) + if len(symmetric_difference) != 0: + return False + + # Item for item equality test + for key, proxy in configured_domain_proxys.items(): + diff = proxy.diff_object(actual_domain_bindings[key]) + if 'backupipflag' in diff: + del diff['backupipflag'] + if not len(diff) == 0: + return False + # Fallthrough to True result + return True + + +def get_actual_service_bindings(client, module): + log('get_actual_service_bindings') + # Get actual domain bindings and index them by domainname + actual_bindings = {} + if gslbvserver_gslbservice_binding.count(client, name=module.params['name']) != 0: + # Get all service bindings associated with the named gslb vserver + fetched_bindings = gslbvserver_gslbservice_binding.get(client, name=module.params['name']) + # index by servicename + for binding in fetched_bindings: + complete_missing_attributes(binding, gslbvserver_gslbservice_binding_rw_attrs, fill_value=None) + actual_bindings[binding.servicename] = binding + + return actual_bindings + + +def get_configured_service_bindings(client, module): + log('get_configured_service_bindings_proxys') + configured_proxys = {} + # Get configured domain bindings and index them by domainname + if module.params['service_bindings'] is not None: + for configured_binding in module.params['service_bindings']: + binding_values = copy.deepcopy(configured_binding) + binding_values['name'] = module.params['name'] + gslbvserver_service_binding_proxy = ConfigProxy( + actual=gslbvserver_gslbservice_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbvserver_gslbservice_binding_rw_attrs, + readonly_attrs=[], + ) + configured_proxys[configured_binding['servicename']] = gslbvserver_service_binding_proxy + return configured_proxys + + +def sync_service_bindings(client, module): + actual = get_actual_service_bindings(client, module) + configured = get_configured_service_bindings(client, module) + + # Delete extraneous + extraneous_service_bindings = list(set(actual.keys()) - set(configured.keys())) + for servicename in extraneous_service_bindings: + log('Deleting missing binding from service %s' % servicename) + binding = actual[servicename] + binding.name = module.params['name'] + gslbvserver_gslbservice_binding.delete(client, binding) + + # Recreate different + common_service_bindings = list(set(actual.keys()) & set(configured.keys())) + for servicename in common_service_bindings: + proxy = configured[servicename] + binding = actual[servicename] + if not proxy.has_equal_attributes(actual): + log('Recreating differing service binding %s' % servicename) + gslbvserver_gslbservice_binding.delete(client, binding) + proxy.add() + + # Add missing + missing_service_bindings = list(set(configured.keys()) - set(actual.keys())) + for servicename in missing_service_bindings: + proxy = configured[servicename] + log('Adding missing service binding %s' % servicename) + proxy.add() + + +def service_bindings_identical(client, module): + actual_bindings = get_actual_service_bindings(client, module) + configured_proxys = get_configured_service_bindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_proxys.keys()) + + symmetric_difference = actual_keyset ^ configured_keyset + if len(symmetric_difference) != 0: + return False + + # Item for item equality test + for key, proxy in configured_proxys.items(): + if key in actual_bindings.keys(): + if not proxy.has_equal_attributes(actual_bindings[key]): + return False + + # Fallthrough to True result + return True + + +def gslb_vserver_exists(client, module): + if gslbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def gslb_vserver_identical(client, module, gslb_vserver_proxy): + gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) + if len(diff_dict) != 0: + return False + else: + return True + + +def all_identical(client, module, gslb_vserver_proxy): + return ( + gslb_vserver_identical(client, module, gslb_vserver_proxy) and + domain_bindings_identical(client, module) and + service_bindings_identical(client, module) + ) + + +def diff_list(client, module, gslb_vserver_proxy): + gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) + return gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) + + +def do_state_change(client, module, gslb_vserver_proxy): + if module.params['disabled']: + log('Disabling glsb_vserver') + result = gslbvserver.disable(client, gslb_vserver_proxy.actual) + else: + log('Enabling gslbvserver') + result = gslbvserver.enable(client, gslb_vserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'NNTP', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'RADIUS', + 'RDP', + 'RTSP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + ] + ), + dnsrecordtype=dict( + type='str', + choices=[ + 'A', + 'AAAA', + 'CNAME', + 'NAPTR', + ] + ), + lbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'STATICPROXIMITY', + 'RTT', + 'CUSTOMLOAD', + ] + ), + backuplbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'STATICPROXIMITY', + 'RTT', + 'CUSTOMLOAD', + ] + ), + netmask=dict(type='str'), + v6netmasklen=dict(type='float'), + tolerance=dict(type='float'), + persistencetype=dict( + type='str', + choices=[ + 'SOURCEIP', + 'NONE', + ] + ), + persistenceid=dict(type='float'), + persistmask=dict(type='str'), + v6persistmasklen=dict(type='float'), + timeout=dict(type='float'), + mir=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dynamicweight=dict( + type='str', + choices=[ + 'SERVICECOUNT', + 'SERVICEWEIGHT', + 'DISABLED', + ] + ), + considereffectivestate=dict( + type='str', + choices=[ + 'NONE', + 'STATE_ONLY', + ] + ), + comment=dict(type='str'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + domainname=dict(type='str'), + cookie_domain=dict(type='str'), + ) + + hand_inserted_arguments = dict( + domain_bindings=dict(type='list'), + service_bindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'servicetype', + 'dnsrecordtype', + 'lbmethod', + 'backuplbmethod', + 'netmask', + 'v6netmasklen', + 'tolerance', + 'persistencetype', + 'persistenceid', + 'persistmask', + 'v6persistmasklen', + 'timeout', + 'mir', + 'disableprimaryondown', + 'dynamicweight', + 'considereffectivestate', + 'comment', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'sothreshold', + 'sobackupaction', + 'appflowlog', + 'cookie_domain', + ] + + readonly_attrs = [ + 'curstate', + 'status', + 'lbrrreason', + 'iscname', + 'sitepersistence', + 'totalservices', + 'activeservices', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'health', + 'policyname', + 'priority', + 'gotopriorityexpression', + 'type', + 'vsvrbindsvcip', + 'vsvrbindsvcport', + '__count', + ] + + immutable_attrs = [ + 'name', + 'servicetype', + ] + + transforms = { + 'mir': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + gslb_vserver_proxy = ConfigProxy( + actual=gslbvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying state present') + if not gslb_vserver_exists(client, module): + log('Creating object') + if not module.check_mode: + gslb_vserver_proxy.add() + sync_domain_bindings(client, module) + sync_service_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, gslb_vserver_proxy): + log('Entering update actions') + + # Check if we try to change value of immutable attributes + if not gslb_vserver_identical(client, module, gslb_vserver_proxy): + log('Updating gslb vserver') + immutables_changed = get_immutables_intersection(gslb_vserver_proxy, diff_list(client, module, gslb_vserver_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_vserver_proxy), + **module_result + ) + if not module.check_mode: + gslb_vserver_proxy.update() + + # Update domain bindings + if not domain_bindings_identical(client, module): + if not module.check_mode: + sync_domain_bindings(client, module) + + # Update service bindings + if not service_bindings_identical(client, module): + if not module.check_mode: + sync_service_bindings(client, module) + + module_result['changed'] = True + if not module.check_mode: + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, gslb_vserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + if not gslb_vserver_exists(client, module): + module.fail_json(msg='GSLB Vserver does not exist', **module_result) + if not gslb_vserver_identical(client, module, gslb_vserver_proxy): + module.fail_json(msg='GSLB Vserver differs from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + if not domain_bindings_identical(client, module): + module.fail_json(msg='Domain bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + if not service_bindings_identical(client, module): + module.fail_json(msg='Service bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) + + elif module.params['state'] == 'absent': + + if gslb_vserver_exists(client, module): + if not module.check_mode: + gslb_vserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if gslb_vserver_exists(client, module): + module.fail_json(msg='GSLB Vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_monitor.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_monitor.py new file mode 100644 index 00000000..9221afb4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_monitor.py @@ -0,0 +1,1376 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +module: netscaler_lb_monitor +short_description: Manage load balancing monitors +description: + - Manage load balancing monitors. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + monitorname: + description: + - >- + Name for the monitor. Must begin with an ASCII alphanumeric or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. + - "Minimum length = 1" + + type: + choices: + - 'PING' + - 'TCP' + - 'HTTP' + - 'TCP-ECV' + - 'HTTP-ECV' + - 'UDP-ECV' + - 'DNS' + - 'FTP' + - 'LDNS-PING' + - 'LDNS-TCP' + - 'LDNS-DNS' + - 'RADIUS' + - 'USER' + - 'HTTP-INLINE' + - 'SIP-UDP' + - 'SIP-TCP' + - 'LOAD' + - 'FTP-EXTENDED' + - 'SMTP' + - 'SNMP' + - 'NNTP' + - 'MYSQL' + - 'MYSQL-ECV' + - 'MSSQL-ECV' + - 'ORACLE-ECV' + - 'LDAP' + - 'POP3' + - 'CITRIX-XML-SERVICE' + - 'CITRIX-WEB-INTERFACE' + - 'DNS-TCP' + - 'RTSP' + - 'ARP' + - 'CITRIX-AG' + - 'CITRIX-AAC-LOGINPAGE' + - 'CITRIX-AAC-LAS' + - 'CITRIX-XD-DDC' + - 'ND6' + - 'CITRIX-WI-EXTENDED' + - 'DIAMETER' + - 'RADIUS_ACCOUNTING' + - 'STOREFRONT' + - 'APPC' + - 'SMPP' + - 'CITRIX-XNC-ECV' + - 'CITRIX-XDM' + - 'CITRIX-STA-SERVICE' + - 'CITRIX-STA-SERVICE-NHOP' + description: + - "Type of monitor that you want to create." + + action: + choices: + - 'NONE' + - 'LOG' + - 'DOWN' + description: + - >- + Action to perform when the response to an inline monitor (a monitor of type C(HTTP-INLINE)) indicates + that the service is down. A service monitored by an inline monitor is considered C(DOWN) if the response + code is not one of the codes that have been specified for the Response Code parameter. + - "Available settings function as follows:" + - >- + * C(NONE) - Do not take any action. However, the show service command and the show lb monitor command + indicate the total number of responses that were checked and the number of consecutive error + responses received after the last successful probe. + - "* C(LOG) - Log the event in NSLOG or SYSLOG." + - >- + * C(DOWN) - Mark the service as being down, and then do not direct any traffic to the service until the + configured down time has expired. Persistent connections to the service are terminated as soon as the + service is marked as C(DOWN). Also, log the event in NSLOG or SYSLOG. + + respcode: + description: + - >- + Response codes for which to mark the service as UP. For any other response code, the action performed + depends on the monitor type. C(HTTP) monitors and C(RADIUS) monitors mark the service as C(DOWN), while + C(HTTP-INLINE) monitors perform the action indicated by the Action parameter. + + httprequest: + description: + - 'HTTP request to send to the server (for example, C("HEAD /file.html")).' + + rtsprequest: + description: + - 'RTSP request to send to the server (for example, C("OPTIONS *")).' + + customheaders: + description: + - "Custom header string to include in the monitoring probes." + + maxforwards: + description: + - >- + Maximum number of hops that the SIP request used for monitoring can traverse to reach the server. + Applicable only to monitors of type C(SIP-UDP). + - "Minimum value = C(0)" + - "Maximum value = C(255)" + + sipmethod: + choices: + - 'OPTIONS' + - 'INVITE' + - 'REGISTER' + description: + - "SIP method to use for the query. Applicable only to monitors of type C(SIP-UDP)." + + sipuri: + description: + - >- + SIP URI string to send to the service (for example, C(sip:sip.test)). Applicable only to monitors of + type C(SIP-UDP). + - "Minimum length = 1" + + sipreguri: + description: + - >- + SIP user to be registered. Applicable only if the monitor is of type C(SIP-UDP) and the SIP Method + parameter is set to C(REGISTER). + - "Minimum length = 1" + + send: + description: + - "String to send to the service. Applicable to C(TCP-ECV), C(HTTP-ECV), and C(UDP-ECV) monitors." + + recv: + description: + - >- + String expected from the server for the service to be marked as UP. Applicable to C(TCP-ECV), C(HTTP-ECV), + and C(UDP-ECV) monitors. + + query: + description: + - "Domain name to resolve as part of monitoring the DNS service (for example, C(example.com))." + + querytype: + choices: + - 'Address' + - 'Zone' + - 'AAAA' + description: + - >- + Type of DNS record for which to send monitoring queries. Set to C(Address) for querying A records, C(AAAA) + for querying AAAA records, and C(Zone) for querying the SOA record. + + scriptname: + description: + - >- + Path and name of the script to execute. The script must be available on the NetScaler appliance, in + the /nsconfig/monitors/ directory. + - "Minimum length = 1" + + scriptargs: + description: + - "String of arguments for the script. The string is copied verbatim into the request." + + dispatcherip: + description: + - "IP address of the dispatcher to which to send the probe." + + dispatcherport: + description: + - "Port number on which the dispatcher listens for the monitoring probe." + + username: + description: + - >- + User name with which to probe the C(RADIUS), C(NNTP), C(FTP), C(FTP-EXTENDED), C(MYSQL), C(MSSQL), C(POP3), C(CITRIX-AG), + C(CITRIX-XD-DDC), C(CITRIX-WI-EXTENDED), C(CITRIX-XNC) or C(CITRIX-XDM) server. + - "Minimum length = 1" + + password: + description: + - >- + Password that is required for logging on to the C(RADIUS), C(NNTP), C(FTP), C(FTP-EXTENDED), C(MYSQL), C(MSSQL), C(POP3), + C(CITRIX-AG), C(CITRIX-XD-DDC), C(CITRIX-WI-EXTENDED), C(CITRIX-XNC-ECV) or C(CITRIX-XDM) server. Used in + conjunction with the user name specified for the C(username) parameter. + - "Minimum length = 1" + + secondarypassword: + description: + - >- + Secondary password that users might have to provide to log on to the Access Gateway server. + Applicable to C(CITRIX-AG) monitors. + + logonpointname: + description: + - >- + Name of the logon point that is configured for the Citrix Access Gateway Advanced Access Control + software. Required if you want to monitor the associated login page or Logon Agent. Applicable to + C(CITRIX-AAC-LAS) and C(CITRIX-AAC-LOGINPAGE) monitors. + + lasversion: + description: + - >- + Version number of the Citrix Advanced Access Control Logon Agent. Required by the C(CITRIX-AAC-LAS) + monitor. + + radkey: + description: + - >- + Authentication key (shared secret text string) for RADIUS clients and servers to exchange. Applicable + to monitors of type C(RADIUS) and C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radnasid: + description: + - "NAS-Identifier to send in the Access-Request packet. Applicable to monitors of type C(RADIUS)." + - "Minimum length = 1" + + radnasip: + description: + - >- + Network Access Server (NAS) IP address to use as the source IP address when monitoring a RADIUS + server. Applicable to monitors of type C(RADIUS) and C(RADIUS_ACCOUNTING). + + radaccounttype: + description: + - "Account Type to be used in Account Request Packet. Applicable to monitors of type C(RADIUS_ACCOUNTING)." + - "Minimum value = 0" + - "Maximum value = 15" + + radframedip: + description: + - "Source ip with which the packet will go out . Applicable to monitors of type C(RADIUS_ACCOUNTING)." + + radapn: + description: + - >- + Called Station Id to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radmsisdn: + description: + - >- + Calling Stations Id to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + radaccountsession: + description: + - >- + Account Session ID to be used in Account Request Packet. Applicable to monitors of type + C(RADIUS_ACCOUNTING). + - "Minimum length = 1" + + lrtm: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Calculate the least response times for bound services. If this parameter is not enabled, the + appliance does not learn the response times of the bound services. Also used for LRTM load balancing. + + deviation: + description: + - >- + Time value added to the learned average response time in dynamic response time monitoring (DRTM). + When a deviation is specified, the appliance learns the average response time of bound services and + adds the deviation to the average. The final value is then continually adjusted to accommodate + response time variations over time. Specified in milliseconds, seconds, or minutes. + - "Minimum value = C(0)" + - "Maximum value = C(20939)" + + units1: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "Unit of measurement for the Deviation parameter. Cannot be changed after the monitor is created." + + interval: + description: + - "Time interval between two successive probes. Must be greater than the value of Response Time-out." + - "Minimum value = C(1)" + - "Maximum value = C(20940)" + + units3: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "monitor interval units." + + resptimeout: + description: + - >- + Amount of time for which the appliance must wait before it marks a probe as FAILED. Must be less than + the value specified for the Interval parameter. + - >- + Note: For C(UDP-ECV) monitors for which a receive string is not configured, response timeout does not + apply. For C(UDP-ECV) monitors with no receive string, probe failure is indicated by an ICMP port + unreachable error received from the service. + - "Minimum value = C(1)" + - "Maximum value = C(20939)" + + units4: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "monitor response timeout units." + + resptimeoutthresh: + description: + - >- + Response time threshold, specified as a percentage of the Response Time-out parameter. If the + response to a monitor probe has not arrived when the threshold is reached, the appliance generates an + SNMP trap called monRespTimeoutAboveThresh. After the response time returns to a value below the + threshold, the appliance generates a monRespTimeoutBelowThresh SNMP trap. For the traps to be + generated, the "MONITOR-RTO-THRESHOLD" alarm must also be enabled. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + retries: + description: + - >- + Maximum number of probes to send to establish the state of a service for which a monitoring probe + failed. + - "Minimum value = C(1)" + - "Maximum value = C(127)" + + failureretries: + description: + - >- + Number of retries that must fail, out of the number specified for the Retries parameter, for a + service to be marked as DOWN. For example, if the Retries parameter is set to 10 and the Failure + Retries parameter is set to 6, out of the ten probes sent, at least six probes must fail if the + service is to be marked as DOWN. The default value of 0 means that all the retries must fail if the + service is to be marked as DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(32)" + + alertretries: + description: + - >- + Number of consecutive probe failures after which the appliance generates an SNMP trap called + monProbeFailed. + - "Minimum value = C(0)" + - "Maximum value = C(32)" + + successretries: + description: + - "Number of consecutive successful probes required to transition a service's state from DOWN to UP." + - "Minimum value = C(1)" + - "Maximum value = C(32)" + + downtime: + description: + - >- + Time duration for which to wait before probing a service that has been marked as DOWN. Expressed in + milliseconds, seconds, or minutes. + - "Minimum value = C(1)" + - "Maximum value = C(20939)" + + units2: + choices: + - 'SEC' + - 'MSEC' + - 'MIN' + description: + - "Unit of measurement for the Down Time parameter. Cannot be changed after the monitor is created." + + destip: + description: + - >- + IP address of the service to which to send probes. If the parameter is set to 0, the IP address of + the server to which the monitor is bound is considered the destination IP address. + + destport: + description: + - >- + TCP or UDP port to which to send the probe. If the parameter is set to 0, the port number of the + service to which the monitor is bound is considered the destination port. For a monitor of type C(USER), + however, the destination port is the port number that is included in the HTTP request sent to the + dispatcher. Does not apply to monitors of type C(PING). + + state: + choices: + - 'enabled' + - 'disabled' + description: + - >- + State of the monitor. The C(disabled) setting disables not only the monitor being configured, but all + monitors of the same type, until the parameter is set to C(enabled). If the monitor is bound to a + service, the state of the monitor is not taken into account when the state of the service is + determined. + + reverse: + description: + - >- + Mark a service as DOWN, instead of UP, when probe criteria are satisfied, and as UP instead of DOWN + when probe criteria are not satisfied. + type: bool + + transparent: + description: + - >- + The monitor is bound to a transparent device such as a firewall or router. The state of a transparent + device depends on the responsiveness of the services behind it. If a transparent device is being + monitored, a destination IP address must be specified. The probe is sent to the specified IP address + by using the MAC address of the transparent device. + type: bool + + iptunnel: + description: + - >- + Send the monitoring probe to the service through an IP tunnel. A destination IP address must be + specified. + type: bool + + tos: + description: + - "Probe the service by encoding the destination IP address in the IP TOS (6) bits." + type: bool + + tosid: + description: + - "The TOS ID of the specified destination IP. Applicable only when the TOS parameter is set." + - "Minimum value = C(1)" + - "Maximum value = C(63)" + + secure: + description: + - >- + Use a secure SSL connection when monitoring a service. Applicable only to TCP based monitors. The + secure option cannot be used with a C(CITRIX-AG) monitor, because a CITRIX-AG monitor uses a secure + connection by default. + type: bool + + validatecred: + description: + - >- + Validate the credentials of the Xen Desktop DDC server user. Applicable to monitors of type + C(CITRIX-XD-DDC). + type: bool + + domain: + description: + - >- + Domain in which the XenDesktop Desktop Delivery Controller (DDC) servers or Web Interface servers are + present. Required by C(CITRIX-XD-DDC) and C(CITRIX-WI-EXTENDED) monitors for logging on to the DDC servers + and Web Interface servers, respectively. + + ipaddress: + description: + - >- + Set of IP addresses expected in the monitoring response from the DNS server, if the record type is A + or AAAA. Applicable to C(DNS) monitors. + - "Minimum length = 1" + + group: + description: + - >- + Name of a newsgroup available on the NNTP service that is to be monitored. The appliance periodically + generates an NNTP query for the name of the newsgroup and evaluates the response. If the newsgroup is + found on the server, the service is marked as UP. If the newsgroup does not exist or if the search + fails, the service is marked as DOWN. Applicable to NNTP monitors. + - "Minimum length = 1" + + filename: + description: + - >- + Name of a file on the FTP server. The appliance monitors the FTP service by periodically checking the + existence of the file on the server. Applicable to C(FTP-EXTENDED) monitors. + - "Minimum length = 1" + + basedn: + description: + - >- + The base distinguished name of the LDAP service, from where the LDAP server can begin the search for + the attributes in the monitoring query. Required for C(LDAP) service monitoring. + - "Minimum length = 1" + + binddn: + description: + - >- + The distinguished name with which an LDAP monitor can perform the Bind operation on the LDAP server. + Optional. Applicable to C(LDAP) monitors. + - "Minimum length = 1" + + filter: + description: + - "Filter criteria for the LDAP query. Optional." + - "Minimum length = 1" + + attribute: + description: + - >- + Attribute to evaluate when the LDAP server responds to the query. Success or failure of the + monitoring probe depends on whether the attribute exists in the response. Optional. + - "Minimum length = 1" + + database: + description: + - "Name of the database to connect to during authentication." + - "Minimum length = 1" + + oraclesid: + description: + - "Name of the service identifier that is used to connect to the Oracle database during authentication." + - "Minimum length = 1" + + sqlquery: + description: + - >- + SQL query for a C(MYSQL-ECV) or C(MSSQL-ECV) monitor. Sent to the database server after the server + authenticates the connection. + - "Minimum length = 1" + + evalrule: + description: + - >- + Default syntax expression that evaluates the database server's response to a MYSQL-ECV or MSSQL-ECV + monitoring query. Must produce a Boolean result. The result determines the state of the server. If + the expression returns TRUE, the probe succeeds. + - >- + For example, if you want the appliance to evaluate the error message to determine the state of the + server, use the rule C(MYSQL.RES.ROW(10) .TEXT_ELEM(2).EQ("MySQL")). + + mssqlprotocolversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - "Version of MSSQL server that is to be monitored." + + Snmpoid: + description: + - "SNMP OID for C(SNMP) monitors." + - "Minimum length = 1" + + snmpcommunity: + description: + - "Community name for C(SNMP) monitors." + - "Minimum length = 1" + + snmpthreshold: + description: + - "Threshold for C(SNMP) monitors." + - "Minimum length = 1" + + snmpversion: + choices: + - 'V1' + - 'V2' + description: + - "SNMP version to be used for C(SNMP) monitors." + + metrictable: + description: + - "Metric table to which to bind metrics." + - "Minimum length = 1" + - "Maximum length = 99" + + application: + description: + - >- + Name of the application used to determine the state of the service. Applicable to monitors of type + C(CITRIX-XML-SERVICE). + - "Minimum length = 1" + + sitepath: + description: + - >- + URL of the logon page. For monitors of type C(CITRIX-WEB-INTERFACE), to monitor a dynamic page under the + site path, terminate the site path with a slash C(/). Applicable to C(CITRIX-WEB-INTERFACE), + C(CITRIX-WI-EXTENDED) and C(CITRIX-XDM) monitors. + - "Minimum length = 1" + + storename: + description: + - >- + Store Name. For monitors of type C(STOREFRONT), C(storename) is an optional argument defining storefront + service store name. Applicable to C(STOREFRONT) monitors. + - "Minimum length = 1" + + storefrontacctservice: + description: + - >- + Enable/Disable probing for Account Service. Applicable only to Store Front monitors. For + multi-tenancy configuration users my skip account service. + type: bool + + hostname: + description: + - "Hostname in the FQDN format (Example: C(porche.cars.org)). Applicable to C(STOREFRONT) monitors." + - "Minimum length = 1" + + netprofile: + description: + - "Name of the network profile." + - "Minimum length = 1" + - "Maximum length = 127" + + originhost: + description: + - >- + Origin-Host value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + originrealm: + description: + - >- + Origin-Realm value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + hostipaddress: + description: + - >- + Host-IP-Address value for the Capabilities-Exchange-Request (CER) message to use for monitoring + Diameter servers. If Host-IP-Address is not specified, the appliance inserts the mapped IP (MIP) + address or subnet IP (SNIP) address from which the CER request (the monitoring probe) is sent. + - "Minimum length = 1" + + vendorid: + description: + - >- + Vendor-Id value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + + productname: + description: + - >- + Product-Name value for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + - "Minimum length = 1" + + firmwarerevision: + description: + - >- + Firmware-Revision value for the Capabilities-Exchange-Request (CER) message to use for monitoring + Diameter servers. + + authapplicationid: + description: + - >- + List of Auth-Application-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum of eight of these AVPs are supported in a + monitoring CER message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + acctapplicationid: + description: + - >- + List of Acct-Application-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum of eight of these AVPs are supported in a + monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + inbandsecurityid: + choices: + - 'NO_INBAND_SECURITY' + - 'TLS' + description: + - >- + Inband-Security-Id for the Capabilities-Exchange-Request (CER) message to use for monitoring Diameter + servers. + + supportedvendorids: + description: + - >- + List of Supported-Vendor-Id attribute value pairs (AVPs) for the Capabilities-Exchange-Request (CER) + message to use for monitoring Diameter servers. A maximum eight of these AVPs are supported in a + monitoring message. + - "Minimum value = C(1)" + - "Maximum value = C(4294967295)" + + vendorspecificvendorid: + description: + - >- + Vendor-Id to use in the Vendor-Specific-Application-Id grouped attribute-value pair (AVP) in the + monitoring CER message. To specify Auth-Application-Id or Acct-Application-Id in + Vendor-Specific-Application-Id, use vendorSpecificAuthApplicationIds or + vendorSpecificAcctApplicationIds, respectively. Only one Vendor-Id is supported for all the + Vendor-Specific-Application-Id AVPs in a CER monitoring message. + - "Minimum value = 1" + + vendorspecificauthapplicationids: + description: + - >- + List of Vendor-Specific-Auth-Application-Id attribute value pairs (AVPs) for the + Capabilities-Exchange-Request (CER) message to use for monitoring Diameter servers. A maximum of + eight of these AVPs are supported in a monitoring message. The specified value is combined with the + value of vendorSpecificVendorId to obtain the Vendor-Specific-Application-Id AVP in the CER + monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + vendorspecificacctapplicationids: + description: + - >- + List of Vendor-Specific-Acct-Application-Id attribute value pairs (AVPs) to use for monitoring + Diameter servers. A maximum of eight of these AVPs are supported in a monitoring message. The + specified value is combined with the value of vendorSpecificVendorId to obtain the + Vendor-Specific-Application-Id AVP in the CER monitoring message. + - "Minimum value = C(0)" + - "Maximum value = C(4294967295)" + + kcdaccount: + description: + - "KCD Account used by C(MSSQL) monitor." + - "Minimum length = 1" + - "Maximum length = 32" + + storedb: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Store the database list populated with the responses to monitor probes. Used in database specific + load balancing if C(MSSQL-ECV)/C(MYSQL-ECV) monitor is configured. + + storefrontcheckbackendservices: + description: + - >- + This option will enable monitoring of services running on storefront server. Storefront services are + monitored by probing to a Windows service that runs on the Storefront server and exposes details of + which storefront services are running. + type: bool + + trofscode: + description: + - "Code expected when the server is under maintenance." + + trofsstring: + description: + - >- + String expected from the server for the service to be marked as trofs. Applicable to HTTP-ECV/TCP-ECV + monitors. + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Set lb monitor + local_action: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + + module: netscaler_lb_monitor + state: present + + monitorname: monitor_1 + type: HTTP-INLINE + action: DOWN + respcode: ['400'] +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' } +''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + get_immutables_intersection +) + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor import lbmonitor + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def lbmonitor_exists(client, module): + log('Checking if monitor exists') + if lbmonitor.count_filtered(client, 'monitorname:%s' % module.params['monitorname']) > 0: + return True + else: + return False + + +def lbmonitor_identical(client, module, lbmonitor_proxy): + log('Checking if monitor is identical') + + count = lbmonitor.count_filtered(client, 'monitorname:%s' % module.params['monitorname']) + if count == 0: + return False + + lbmonitor_list = lbmonitor.get_filtered(client, 'monitorname:%s' % module.params['monitorname']) + diff_dict = lbmonitor_proxy.diff_object(lbmonitor_list[0]) + + # Skipping hashed fields since the cannot be compared directly + hashed_fields = [ + 'password', + 'secondarypassword', + 'radkey', + ] + for key in hashed_fields: + if key in diff_dict: + del diff_dict[key] + + if diff_dict == {}: + return True + else: + return False + + +def diff_list(client, module, lbmonitor_proxy): + monitor_list = lbmonitor.get_filtered(client, 'monitorname:%s' % module.params['monitorname']) + return lbmonitor_proxy.diff_object(monitor_list[0]) + + +def main(): + + module_specific_arguments = dict( + + monitorname=dict(type='str'), + + type=dict( + type='str', + choices=[ + 'PING', + 'TCP', + 'HTTP', + 'TCP-ECV', + 'HTTP-ECV', + 'UDP-ECV', + 'DNS', + 'FTP', + 'LDNS-PING', + 'LDNS-TCP', + 'LDNS-DNS', + 'RADIUS', + 'USER', + 'HTTP-INLINE', + 'SIP-UDP', + 'SIP-TCP', + 'LOAD', + 'FTP-EXTENDED', + 'SMTP', + 'SNMP', + 'NNTP', + 'MYSQL', + 'MYSQL-ECV', + 'MSSQL-ECV', + 'ORACLE-ECV', + 'LDAP', + 'POP3', + 'CITRIX-XML-SERVICE', + 'CITRIX-WEB-INTERFACE', + 'DNS-TCP', + 'RTSP', + 'ARP', + 'CITRIX-AG', + 'CITRIX-AAC-LOGINPAGE', + 'CITRIX-AAC-LAS', + 'CITRIX-XD-DDC', + 'ND6', + 'CITRIX-WI-EXTENDED', + 'DIAMETER', + 'RADIUS_ACCOUNTING', + 'STOREFRONT', + 'APPC', + 'SMPP', + 'CITRIX-XNC-ECV', + 'CITRIX-XDM', + 'CITRIX-STA-SERVICE', + 'CITRIX-STA-SERVICE-NHOP', + ] + ), + + action=dict( + type='str', + choices=[ + 'NONE', + 'LOG', + 'DOWN', + ] + ), + respcode=dict(type='list'), + httprequest=dict(type='str'), + rtsprequest=dict(type='str'), + customheaders=dict(type='str'), + maxforwards=dict(type='float'), + sipmethod=dict( + type='str', + choices=[ + 'OPTIONS', + 'INVITE', + 'REGISTER', + ] + ), + sipuri=dict(type='str'), + sipreguri=dict(type='str'), + send=dict(type='str'), + recv=dict(type='str'), + query=dict(type='str'), + querytype=dict( + type='str', + choices=[ + 'Address', + 'Zone', + 'AAAA', + ] + ), + scriptname=dict(type='str'), + scriptargs=dict(type='str'), + dispatcherip=dict(type='str'), + dispatcherport=dict(type='int'), + username=dict(type='str'), + password=dict(type='str', no_log=True), + secondarypassword=dict(type='str', no_log=True), + logonpointname=dict(type='str'), + lasversion=dict(type='str'), + radkey=dict(type='str', no_log=True), + radnasid=dict(type='str'), + radnasip=dict(type='str'), + radaccounttype=dict(type='float'), + radframedip=dict(type='str'), + radapn=dict(type='str'), + radmsisdn=dict(type='str'), + radaccountsession=dict(type='str'), + lrtm=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + deviation=dict(type='float'), + units1=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + interval=dict(type='int'), + units3=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + resptimeout=dict(type='int'), + units4=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + resptimeoutthresh=dict(type='float'), + retries=dict(type='int'), + failureretries=dict(type='int'), + alertretries=dict(type='int'), + successretries=dict(type='int'), + downtime=dict(type='int'), + units2=dict( + type='str', + choices=[ + 'SEC', + 'MSEC', + 'MIN', + ] + ), + destip=dict(type='str'), + destport=dict(type='int'), + reverse=dict(type='bool'), + transparent=dict(type='bool'), + iptunnel=dict(type='bool'), + tos=dict(type='bool'), + tosid=dict(type='float'), + secure=dict(type='bool'), + validatecred=dict(type='bool'), + domain=dict(type='str'), + ipaddress=dict(type='list'), + group=dict(type='str'), + filename=dict(type='str'), + basedn=dict(type='str'), + binddn=dict(type='str'), + filter=dict(type='str'), + attribute=dict(type='str'), + database=dict(type='str'), + oraclesid=dict(type='str'), + sqlquery=dict(type='str'), + evalrule=dict(type='str'), + mssqlprotocolversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + Snmpoid=dict(type='str'), + snmpcommunity=dict(type='str'), + snmpthreshold=dict(type='str'), + snmpversion=dict( + type='str', + choices=[ + 'V1', + 'V2', + ] + ), + application=dict(type='str'), + sitepath=dict(type='str'), + storename=dict(type='str'), + storefrontacctservice=dict(type='bool'), + hostname=dict(type='str'), + netprofile=dict(type='str'), + originhost=dict(type='str'), + originrealm=dict(type='str'), + hostipaddress=dict(type='str'), + vendorid=dict(type='float'), + productname=dict(type='str'), + firmwarerevision=dict(type='float'), + authapplicationid=dict(type='list'), + acctapplicationid=dict(type='list'), + inbandsecurityid=dict( + type='str', + choices=[ + 'NO_INBAND_SECURITY', + 'TLS', + ] + ), + supportedvendorids=dict(type='list'), + vendorspecificvendorid=dict(type='float'), + vendorspecificauthapplicationids=dict(type='list'), + vendorspecificacctapplicationids=dict(type='list'), + storedb=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + storefrontcheckbackendservices=dict(type='bool'), + trofscode=dict(type='float'), + trofsstring=dict(type='str'), + ) + + hand_inserted_arguments = dict() + + argument_spec = dict() + argument_spec.update(module_specific_arguments) + argument_spec.update(netscaler_common_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk', **module_result) + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate lb monitor object + readwrite_attrs = [ + 'monitorname', + 'type', + 'action', + 'respcode', + 'httprequest', + 'rtsprequest', + 'customheaders', + 'maxforwards', + 'sipmethod', + 'sipuri', + 'sipreguri', + 'send', + 'recv', + 'query', + 'querytype', + 'scriptname', + 'scriptargs', + 'dispatcherip', + 'dispatcherport', + 'username', + 'password', + 'secondarypassword', + 'logonpointname', + 'lasversion', + 'radkey', + 'radnasid', + 'radnasip', + 'radaccounttype', + 'radframedip', + 'radapn', + 'radmsisdn', + 'radaccountsession', + 'lrtm', + 'deviation', + 'units1', + 'interval', + 'units3', + 'resptimeout', + 'units4', + 'resptimeoutthresh', + 'retries', + 'failureretries', + 'alertretries', + 'successretries', + 'downtime', + 'units2', + 'destip', + 'destport', + 'reverse', + 'transparent', + 'iptunnel', + 'tos', + 'tosid', + 'secure', + 'validatecred', + 'domain', + 'ipaddress', + 'group', + 'filename', + 'basedn', + 'binddn', + 'filter', + 'attribute', + 'database', + 'oraclesid', + 'sqlquery', + 'evalrule', + 'mssqlprotocolversion', + 'Snmpoid', + 'snmpcommunity', + 'snmpthreshold', + 'snmpversion', + 'application', + 'sitepath', + 'storename', + 'storefrontacctservice', + 'netprofile', + 'originhost', + 'originrealm', + 'hostipaddress', + 'vendorid', + 'productname', + 'firmwarerevision', + 'authapplicationid', + 'acctapplicationid', + 'inbandsecurityid', + 'supportedvendorids', + 'vendorspecificvendorid', + 'vendorspecificauthapplicationids', + 'vendorspecificacctapplicationids', + 'storedb', + 'storefrontcheckbackendservices', + 'trofscode', + 'trofsstring', + ] + + readonly_attrs = [ + 'lrtmconf', + 'lrtmconfstr', + 'dynamicresponsetimeout', + 'dynamicinterval', + 'multimetrictable', + 'dup_state', + 'dup_weight', + 'weight', + ] + + immutable_attrs = [ + 'monitorname', + 'type', + 'units1', + 'units3', + 'units4', + 'units2', + 'Snmpoid', + 'hostname', + 'servicename', + 'servicegroupname', + ] + + transforms = { + 'storefrontcheckbackendservices': ['bool_yes_no'], + 'secure': ['bool_yes_no'], + 'tos': ['bool_yes_no'], + 'validatecred': ['bool_yes_no'], + 'storefrontacctservice': ['bool_yes_no'], + 'iptunnel': ['bool_yes_no'], + 'transparent': ['bool_yes_no'], + 'reverse': ['bool_yes_no'], + 'lrtm': [lambda v: v.upper()], + 'storedb': [lambda v: v.upper()], + } + + lbmonitor_proxy = ConfigProxy( + actual=lbmonitor(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'LB') + + if module.params['state'] == 'present': + log('Applying actions for state present') + if not lbmonitor_exists(client, module): + if not module.check_mode: + log('Adding monitor') + lbmonitor_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not lbmonitor_identical(client, module, lbmonitor_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(lbmonitor_proxy, diff_list(client, module, lbmonitor_proxy).keys()) + if immutables_changed != []: + diff = diff_list(client, module, lbmonitor_proxy) + msg = 'Cannot update immutable attributes %s' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff, **module_result) + + if not module.check_mode: + log('Updating monitor') + lbmonitor_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Doing nothing for monitor') + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state present') + if not module.check_mode: + if not lbmonitor_exists(client, module): + module.fail_json(msg='lb monitor does not exist', **module_result) + if not lbmonitor_identical(client, module, lbmonitor_proxy): + module.fail_json( + msg='lb monitor is not configured correctly', + diff=diff_list(client, module, lbmonitor_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if lbmonitor_exists(client, module): + if not module.check_mode: + lbmonitor_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state absent') + if not module.check_mode: + if lbmonitor_exists(client, module): + module.fail_json(msg='lb monitor still exists', **module_result) + + module_result['actual_attributes'] = lbmonitor_proxy.get_actual_rw_attributes(filter='monitorname') + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_vserver.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_vserver.py new file mode 100644 index 00000000..492a7d1a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_lb_vserver.py @@ -0,0 +1,1936 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_lb_vserver +short_description: Manage load balancing vserver configuration +description: + - Manage load balancing vserver configuration + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character, and + must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon C(:), at sign + C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the virtual server is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'DNS' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'RTSP' + - 'PUSH' + - 'SSL_PUSH' + - 'RADIUS' + - 'RDP' + - 'MYSQL' + - 'MSSQL' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'ORACLE' + - 'SMPP' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol used by the service (also called the service type)." + + ipv46: + description: + - "IPv4 or IPv6 address to assign to the virtual server." + + ippattern: + description: + - >- + IP address pattern, in dotted decimal notation, for identifying packets to be accepted by the virtual + server. The IP Mask parameter specifies which part of the destination IP address is matched against + the pattern. Mutually exclusive with the IP Address parameter. + - >- + For example, if the IP pattern assigned to the virtual server is C(198.51.100.0) and the IP mask is + C(255.255.240.0) (a forward mask), the first 20 bits in the destination IP addresses are matched with + the first 20 bits in the pattern. The virtual server accepts requests with IP addresses that range + from C(198.51.96.1) to C(198.51.111.254). You can also use a pattern such as C(0.0.2.2) and a mask such as + C(0.0.255.255) (a reverse mask). + - >- + If a destination IP address matches more than one IP pattern, the pattern with the longest match is + selected, and the associated virtual server processes the request. For example, if virtual servers + C(vs1) and C(vs2) have the same IP pattern, C(0.0.100.128), but different IP masks of C(0.0.255.255) and + C(0.0.224.255), a destination IP address of C(198.51.100.128) has the longest match with the IP pattern of + vs1. If a destination IP address matches two or more virtual servers to the same extent, the request + is processed by the virtual server whose port number matches the port number in the request. + + ipmask: + description: + - >- + IP mask, in dotted decimal notation, for the IP Pattern parameter. Can have leading or trailing + non-zero octets (for example, C(255.255.240.0) or C(0.0.255.255)). Accordingly, the mask specifies whether + the first n bits or the last n bits of the destination IP address in a client request are to be + matched with the corresponding bits in the IP pattern. The former is called a forward mask. The + latter is called a reverse mask. + + port: + description: + - "Port number for the virtual server." + - "Range C(1) - C(65535)" + - "* in CLI is represented as C(65535) in NITRO API" + + range: + description: + - >- + Number of IP addresses that the appliance must generate and assign to the virtual server. The virtual + server then functions as a network virtual server, accepting traffic on any of the generated IP + addresses. The IP addresses are generated automatically, as follows: + - >- + * For a range of n, the last octet of the address specified by the IP Address parameter increments + n-1 times. + - "* If the last octet exceeds 255, it rolls over to 0 and the third octet increments by 1." + - >- + Note: The Range parameter assigns multiple IP addresses to one virtual server. To generate an array + of virtual servers, each of which owns only one IP address, use brackets in the IP Address and Name + parameters to specify the range. For example: + - "add lb vserver my_vserver[1-3] HTTP 192.0.2.[1-3] 80." + - "Minimum value = C(1)" + - "Maximum value = C(254)" + + persistencetype: + choices: + - 'SOURCEIP' + - 'COOKIEINSERT' + - 'SSLSESSION' + - 'RULE' + - 'URLPASSIVE' + - 'CUSTOMSERVERID' + - 'DESTIP' + - 'SRCIPDESTIP' + - 'CALLID' + - 'RTSPSID' + - 'DIAMETER' + - 'FIXSESSION' + - 'NONE' + description: + - "Type of persistence for the virtual server. Available settings function as follows:" + - "* C(SOURCEIP) - Connections from the same client IP address belong to the same persistence session." + - >- + * C(COOKIEINSERT) - Connections that have the same HTTP Cookie, inserted by a Set-Cookie directive from + a server, belong to the same persistence session. + - "* C(SSLSESSION) - Connections that have the same SSL Session ID belong to the same persistence session." + - >- + * C(CUSTOMSERVERID) - Connections with the same server ID form part of the same session. For this + persistence type, set the Server ID (CustomServerID) parameter for each service and configure the + Rule parameter to identify the server ID in a request. + - "* C(RULE) - All connections that match a user defined rule belong to the same persistence session." + - >- + * C(URLPASSIVE) - Requests that have the same server ID in the URL query belong to the same persistence + session. The server ID is the hexadecimal representation of the IP address and port of the service to + which the request must be forwarded. This persistence type requires a rule to identify the server ID + in the request. + - "* C(DESTIP) - Connections to the same destination IP address belong to the same persistence session." + - >- + * C(SRCIPDESTIP) - Connections that have the same source IP address and destination IP address belong to + the same persistence session. + - "* C(CALLID) - Connections that have the same CALL-ID SIP header belong to the same persistence session." + - "* C(RTSPSID) - Connections that have the same RTSP Session ID belong to the same persistence session." + - >- + * FIXSESSION - Connections that have the same SenderCompID and TargetCompID values belong to the same + persistence session. + + timeout: + description: + - "Time period for which a persistence session is in effect." + - "Minimum value = C(0)" + - "Maximum value = C(1440)" + + persistencebackup: + choices: + - 'SOURCEIP' + - 'NONE' + description: + - >- + Backup persistence type for the virtual server. Becomes operational if the primary persistence + mechanism fails. + + backuppersistencetimeout: + description: + - "Time period for which backup persistence is in effect." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + lbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'URLHASH' + - 'DOMAINHASH' + - 'DESTINATIONIPHASH' + - 'SOURCEIPHASH' + - 'SRCIPDESTIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'TOKEN' + - 'SRCIPSRCPORTHASH' + - 'LRTM' + - 'CALLIDHASH' + - 'CUSTOMLOAD' + - 'LEASTREQUEST' + - 'AUDITLOGHASH' + - 'STATICPROXIMITY' + description: + - "Load balancing method. The available settings function as follows:" + - >- + * C(ROUNDROBIN) - Distribute requests in rotation, regardless of the load. Weights can be assigned to + services to enforce weighted round robin distribution. + - "* C(LEASTCONNECTION) (default) - Select the service with the fewest connections." + - "* C(LEASTRESPONSETIME) - Select the service with the lowest average response time." + - "* C(LEASTBANDWIDTH) - Select the service currently handling the least traffic." + - "* C(LEASTPACKETS) - Select the service currently serving the lowest number of packets per second." + - "* C(CUSTOMLOAD) - Base service selection on the SNMP metrics obtained by custom load monitors." + - >- + * C(LRTM) - Select the service with the lowest response time. Response times are learned through + monitoring probes. This method also takes the number of active connections into account. + - >- + Also available are a number of hashing methods, in which the appliance extracts a predetermined + portion of the request, creates a hash of the portion, and then checks whether any previous requests + had the same hash value. If it finds a match, it forwards the request to the service that served + those previous requests. Following are the hashing methods: + - "* C(URLHASH) - Create a hash of the request URL (or part of the URL)." + - >- + * C(DOMAINHASH) - Create a hash of the domain name in the request (or part of the domain name). The + domain name is taken from either the URL or the Host header. If the domain name appears in both + locations, the URL is preferred. If the request does not contain a domain name, the load balancing + method defaults to C(LEASTCONNECTION). + - "* C(DESTINATIONIPHASH) - Create a hash of the destination IP address in the IP header." + - "* C(SOURCEIPHASH) - Create a hash of the source IP address in the IP header." + - >- + * C(TOKEN) - Extract a token from the request, create a hash of the token, and then select the service + to which any previous requests with the same token hash value were sent. + - >- + * C(SRCIPDESTIPHASH) - Create a hash of the string obtained by concatenating the source IP address and + destination IP address in the IP header. + - "* C(SRCIPSRCPORTHASH) - Create a hash of the source IP address and source port in the IP header." + - "* C(CALLIDHASH) - Create a hash of the SIP Call-ID header." + + hashlength: + description: + - >- + Number of bytes to consider for the hash value used in the URLHASH and DOMAINHASH load balancing + methods. + - "Minimum value = C(1)" + - "Maximum value = C(4096)" + + netmask: + description: + - >- + IPv4 subnet mask to apply to the destination IP address or source IP address when the load balancing + method is C(DESTINATIONIPHASH) or C(SOURCEIPHASH). + - "Minimum length = 1" + + v6netmasklen: + description: + - >- + Number of bits to consider in an IPv6 destination or source IP address, for creating the hash that is + required by the C(DESTINATIONIPHASH) and C(SOURCEIPHASH) load balancing methods. + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + backuplbmethod: + choices: + - 'ROUNDROBIN' + - 'LEASTCONNECTION' + - 'LEASTRESPONSETIME' + - 'SOURCEIPHASH' + - 'LEASTBANDWIDTH' + - 'LEASTPACKETS' + - 'CUSTOMLOAD' + description: + - "Backup load balancing method. Becomes operational if the primary load balancing me" + - "thod fails or cannot be used." + - "Valid only if the primary method is based on static proximity." + + cookiename: + description: + - >- + Use this parameter to specify the cookie name for C(COOKIE) persistence type. It specifies the name of + cookie with a maximum of 32 characters. If not specified, cookie name is internally generated. + + + listenpolicy: + description: + - >- + Default syntax expression identifying traffic accepted by the virtual server. Can be either an + expression (for example, C(CLIENT.IP.DST.IN_SUBNET(192.0.2.0/24)) or the name of a named expression. In + the above example, the virtual server accepts all requests whose destination IP address is in the + 192.0.2.0/24 subnet. + + listenpriority: + description: + - >- + Integer specifying the priority of the listen policy. A higher number specifies a lower priority. If + a request matches the listen policies of more than one virtual server the virtual server whose listen + policy has the highest priority (the lowest priority number) accepts the request. + - "Minimum value = C(0)" + - "Maximum value = C(101)" + + resrule: + description: + - >- + Default syntax expression specifying which part of a server's response to use for creating rule based + persistence sessions (persistence type RULE). Can be either an expression or the name of a named + expression. + - "Example:" + - 'C(HTTP.RES.HEADER("setcookie").VALUE(0).TYPECAST_NVLIST_T("=",";").VALUE("server1")).' + + persistmask: + description: + - "Persistence mask for IP based persistence types, for IPv4 virtual servers." + - "Minimum length = 1" + + v6persistmasklen: + description: + - "Persistence mask for IP based persistence types, for IPv6 virtual servers." + - "Minimum value = C(1)" + - "Maximum value = C(128)" + + rtspnat: + description: + - "Use network address translation (NAT) for RTSP data connections." + type: bool + + m: + choices: + - 'IP' + - 'MAC' + - 'IPTUNNEL' + - 'TOS' + description: + - "Redirection mode for load balancing. Available settings function as follows:" + - >- + * C(IP) - Before forwarding a request to a server, change the destination IP address to the server's IP + address. + - >- + * C(MAC) - Before forwarding a request to a server, change the destination MAC address to the server's + MAC address. The destination IP address is not changed. MAC-based redirection mode is used mostly in + firewall load balancing deployments. + - >- + * C(IPTUNNEL) - Perform IP-in-IP encapsulation for client IP packets. In the outer IP headers, set the + destination IP address to the IP address of the server and the source IP address to the subnet IP + (SNIP). The client IP packets are not modified. Applicable to both IPv4 and IPv6 packets. + - "* C(TOS) - Encode the virtual server's TOS ID in the TOS field of the IP header." + - "You can use either the C(IPTUNNEL) or the C(TOS) option to implement Direct Server Return (DSR)." + + tosid: + description: + - >- + TOS ID of the virtual server. Applicable only when the load balancing redirection mode is set to TOS. + - "Minimum value = C(1)" + - "Maximum value = C(63)" + + datalength: + description: + - >- + Length of the token to be extracted from the data segment of an incoming packet, for use in the token + method of load balancing. The length of the token, specified in bytes, must not be greater than 24 + KB. Applicable to virtual servers of type TCP. + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + dataoffset: + description: + - >- + Offset to be considered when extracting a token from the TCP payload. Applicable to virtual servers, + of type TCP, using the token method of load balancing. Must be within the first 24 KB of the TCP + payload. + - "Minimum value = C(0)" + - "Maximum value = C(25400)" + + sessionless: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Perform load balancing on a per-packet basis, without establishing sessions. Recommended for load + balancing of intrusion detection system (IDS) servers and scenarios involving direct server return + (DSR), where session information is unnecessary. + + connfailover: + choices: + - 'DISABLED' + - 'STATEFUL' + - 'STATELESS' + description: + - >- + Mode in which the connection failover feature must operate for the virtual server. After a failover, + established TCP connections and UDP packet flows are kept active and resumed on the secondary + appliance. Clients remain connected to the same servers. Available settings function as follows: + - >- + * C(STATEFUL) - The primary appliance shares state information with the secondary appliance, in real + time, resulting in some runtime processing overhead. + - >- + * C(STATELESS) - State information is not shared, and the new primary appliance tries to re-create the + packet flow on the basis of the information contained in the packets it receives. + - "* C(DISABLED) - Connection failover does not occur." + + redirurl: + description: + - "URL to which to redirect traffic if the virtual server becomes unavailable." + - >- + WARNING! Make sure that the domain in the URL does not match the domain specified for a content + switching policy. If it does, requests are continuously redirected to the unavailable virtual server. + - "Minimum length = 1" + + cacheable: + description: + - >- + Route cacheable requests to a cache redirection virtual server. The load balancing virtual server can + forward requests only to a transparent cache redirection virtual server that has an IP address and + port combination of *:80, so such a cache redirection virtual server must be configured on the + appliance. + type: bool + + clttimeout: + description: + - "Idle time, in seconds, after which a client connection is terminated." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:" + - "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold." + - >- + * DYNAMICCONNECTION - Spillover occurs when the number of client connections at the virtual server + exceeds the sum of the maximum client (Max Clients) settings for bound services. Do not specify a + spillover threshold for this setting, because the threshold is implied by the Max Clients settings of + bound services. + - >- + * C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the virtual server's incoming and + outgoing traffic exceeds the threshold. + - >- + * C(HEALTH) - Spillover occurs when the percentage of weights of the services that are UP drops below + the threshold. For example, if services svc1, svc2, and svc3 are bound to a virtual server, with + weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if svc1 and svc3 or svc2 + and svc3 transition to DOWN. + - "* C(NONE) - Spillover does not occur." + + sopersistence: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If spillover occurs, maintain source IP address based persistence for both primary and backup virtual + servers. + + sopersistencetimeout: + description: + - "Timeout for spillover persistence, in minutes." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + healththreshold: + description: + - >- + Threshold in percent of active services below which vserver state is made down. If this threshold is + 0, vserver state will be up even if one bound service is up. + - "Minimum value = C(0)" + - "Maximum value = C(100)" + + sothreshold: + description: + - >- + Threshold at which spillover occurs. Specify an integer for the C(CONNECTION) spillover method, a + bandwidth value in kilobits per second for the C(BANDWIDTH) method (do not enter the units), or a + percentage for the C(HEALTH) method (do not enter the percentage symbol). + - "Minimum value = C(1)" + - "Maximum value = C(4294967287)" + + sobackupaction: + choices: + - 'DROP' + - 'ACCEPT' + - 'REDIRECT' + description: + - >- + Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or + exists. + + redirectportrewrite: + choices: + - 'enabled' + - 'disabled' + description: + - "Rewrite the port and change the protocol to ensure successful HTTP redirects from services." + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a virtual server whose state transitions from UP to + DOWN. Do not enable this option for applications that must complete their transactions. + + disableprimaryondown: + choices: + - 'enabled' + - 'disabled' + description: + - >- + If the primary virtual server goes down, do not allow it to return to primary status until manually + enabled. + + insertvserveripport: + choices: + - 'OFF' + - 'VIPADDR' + - 'V6TOV4MAPPING' + description: + - >- + Insert an HTTP header, whose value is the IP address and port number of the virtual server, before + forwarding a request to the server. The format of the header is : _, where vipHeader is the name that you specify for the header. If the virtual + server has an IPv6 address, the address in the header is enclosed in brackets ([ and ]) to separate + it from the port number. If you have mapped an IPv4 address to a virtual server's IPv6 address, the + value of this parameter determines which IP address is inserted in the header, as follows: + - >- + * C(VIPADDR) - Insert the IP address of the virtual server in the HTTP header regardless of whether the + virtual server has an IPv4 address or an IPv6 address. A mapped IPv4 address, if configured, is + ignored. + - >- + * C(V6TOV4MAPPING) - Insert the IPv4 address that is mapped to the virtual server's IPv6 address. If a + mapped IPv4 address is not configured, insert the IPv6 address. + - "* C(OFF) - Disable header insertion." + + vipheader: + description: + - "Name for the inserted header. The default name is vip-header." + - "Minimum length = 1" + + authenticationhost: + description: + - >- + Fully qualified domain name (FQDN) of the authentication virtual server to which the user must be + redirected for authentication. Make sure that the Authentication parameter is set to C(yes). + - "Minimum length = 3" + - "Maximum length = 252" + + authentication: + description: + - "Enable or disable user authentication." + type: bool + + authn401: + description: + - "Enable or disable user authentication with HTTP 401 responses." + type: bool + + authnvsname: + description: + - "Name of an authentication virtual server with which to authenticate users." + - "Minimum length = 1" + - "Maximum length = 252" + + push: + choices: + - 'enabled' + - 'disabled' + description: + - "Process traffic with the push virtual server that is bound to this load balancing virtual server." + + pushvserver: + description: + - >- + Name of the load balancing virtual server, of type PUSH or SSL_PUSH, to which the server pushes + updates received on the load balancing virtual server that you are configuring. + - "Minimum length = 1" + + pushlabel: + description: + - >- + Expression for extracting a label from the server's response. Can be either an expression or the name + of a named expression. + + pushmulticlients: + description: + - >- + Allow multiple Web 2.0 connections from the same client to connect to the virtual server and expect + updates. + type: bool + + tcpprofilename: + description: + - "Name of the TCP profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + dbprofilename: + description: + - "Name of the DB profile whose settings are to be applied to the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + comment: + description: + - "Any comments that you might want to associate with the virtual server." + + l2conn: + description: + - >- + Use Layer 2 parameters (channel number, MAC address, and VLAN ID) in addition to the 4-tuple (::::) that is used to identify a connection. Allows + multiple TCP and non-TCP connections with the same 4-tuple to co-exist on the NetScaler appliance. + type: bool + + oracleserverversion: + choices: + - '10G' + - '11G' + description: + - "Oracle server version." + + mssqlserverversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - >- + For a load balancing virtual server of type C(MSSQL), the Microsoft SQL Server version. Set this + parameter if you expect some clients to run a version different from the version of the database. + This setting provides compatibility between the client-side and server-side connections by ensuring + that all communication conforms to the server's version. + + mysqlprotocolversion: + description: + - "MySQL protocol version that the virtual server advertises to clients." + + mysqlserverversion: + description: + - "MySQL server version string that the virtual server advertises to clients." + - "Minimum length = 1" + - "Maximum length = 31" + + mysqlcharacterset: + description: + - "Character set that the virtual server advertises to clients." + + mysqlservercapabilities: + description: + - "Server capabilities that the virtual server advertises to clients." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Apply AppFlow logging to the virtual server." + + netprofile: + description: + - >- + Name of the network profile to associate with the virtual server. If you set this parameter, the + virtual server uses only the IP addresses in the network profile as source IP addresses when + initiating connections with servers. + - "Minimum length = 1" + - "Maximum length = 127" + + icmpvsrresponse: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - >- + How the NetScaler appliance responds to ping requests received for an IP address that is common to + one or more virtual servers. Available settings function as follows: + - >- + * If set to C(PASSIVE) on all the virtual servers that share the IP address, the appliance always + responds to the ping requests. + - >- + * If set to C(ACTIVE) on all the virtual servers that share the IP address, the appliance responds to + the ping requests if at least one of the virtual servers is UP. Otherwise, the appliance does not + respond. + - >- + * If set to C(ACTIVE) on some virtual servers and PASSIVE on the others, the appliance responds if at + least one virtual server with the ACTIVE setting is UP. Otherwise, the appliance does not respond. + - >- + Note: This parameter is available at the virtual server level. A similar parameter, ICMP Response, is + available at the IP address level, for IPv4 addresses of type VIP. To set that parameter, use the add + ip command in the CLI or the Create IP dialog box in the GUI. + + rhistate: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - >- + Route Health Injection (RHI) functionality of the NetSaler appliance for advertising the route of the + VIP address associated with the virtual server. When Vserver RHI Level (RHI) parameter is set to + VSVR_CNTRLD, the following are different RHI behaviors for the VIP address on the basis of RHIstate + (RHI STATE) settings on the virtual servers associated with the VIP address: + - >- + * If you set C(rhistate) to C(PASSIVE) on all virtual servers, the NetScaler ADC always advertises the + route for the VIP address. + - >- + * If you set C(rhistate) to C(ACTIVE) on all virtual servers, the NetScaler ADC advertises the route for + the VIP address if at least one of the associated virtual servers is in UP state. + - >- + * If you set C(rhistate) to C(ACTIVE) on some and PASSIVE on others, the NetScaler ADC advertises the + route for the VIP address if at least one of the associated virtual servers, whose C(rhistate) set to + C(ACTIVE), is in UP state. + + newservicerequest: + description: + - >- + Number of requests, or percentage of the load on existing services, by which to increase the load on + a new service at each interval in slow-start mode. A non-zero value indicates that slow-start is + applicable. A zero value indicates that the global RR startup parameter is applied. Changing the + value to zero will cause services currently in slow start to take the full traffic as determined by + the LB method. Subsequently, any new services added will use the global RR factor. + + newservicerequestunit: + choices: + - 'PER_SECOND' + - 'PERCENT' + description: + - "Units in which to increment load at each interval in slow-start mode." + + newservicerequestincrementinterval: + description: + - >- + Interval, in seconds, between successive increments in the load on a new service or a service whose + state has just changed from DOWN to UP. A value of 0 (zero) specifies manual slow start. + - "Minimum value = C(0)" + - "Maximum value = C(3600)" + + minautoscalemembers: + description: + - "Minimum number of members expected to be present when vserver is used in Autoscale." + - "Minimum value = C(0)" + - "Maximum value = C(5000)" + + maxautoscalemembers: + description: + - "Maximum number of members expected to be present when vserver is used in Autoscale." + - "Minimum value = C(0)" + - "Maximum value = C(5000)" + + persistavpno: + description: + - "Persist AVP number for Diameter Persistency." + - "In case this AVP is not defined in Base RFC 3588 and it is nested inside a Grouped AVP," + - "define a sequence of AVP numbers (max 3) in order of parent to child. So say persist AVP number X" + - "is nested inside AVP Y which is nested in Z, then define the list as Z Y X." + - "Minimum value = C(1)" + + skippersistency: + choices: + - 'Bypass' + - 'ReLb' + - 'None' + description: + - >- + This argument decides the behavior incase the service which is selected from an existing persistence + session has reached threshold. + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = C(0)" + - "Maximum value = C(4094)" + + authnprofile: + description: + - "Name of the authentication profile to be used when authentication is turned on." + + macmoderetainvlan: + choices: + - 'enabled' + - 'disabled' + description: + - "This option is used to retain vlan information of incoming packet when macmode is enabled." + + dbslb: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable database specific load balancing for MySQL and MSSQL service types." + + dns64: + choices: + - 'enabled' + - 'disabled' + description: + - "This argument is for enabling/disabling the C(dns64) on lbvserver." + + bypassaaaa: + description: + - >- + If this option is enabled while resolving DNS64 query AAAA queries are not sent to back end dns + server. + type: bool + + recursionavailable: + description: + - >- + When set to YES, this option causes the DNS replies from this vserver to have the RA bit turned on. + Typically one would set this option to YES, when the vserver is load balancing a set of DNS servers + thatsupport recursive queries. + type: bool + + processlocal: + choices: + - 'enabled' + - 'disabled' + description: + - >- + By turning on this option packets destined to a vserver in a cluster will not under go any steering. + Turn this option for single packet request response mode or when the upstream device is performing a + proper RSS for connection based distribution. + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the VServer. DNS profile properties will be applied to + the transactions processed by a VServer. This parameter is valid only for DNS and DNS-TCP VServers. + - "Minimum length = 1" + - "Maximum length = 127" + + servicebindings: + description: + - List of services along with the weights that are load balanced. + - The following suboptions are available. + suboptions: + servicename: + description: + - "Service to bind to the virtual server." + - "Minimum length = 1" + weight: + description: + - "Weight to assign to the specified service." + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + servicegroupbindings: + description: + - List of service groups along with the weights that are load balanced. + - The following suboptions are available. + suboptions: + servicegroupname: + description: + - "The service group name bound to the selected load balancing virtual server." + weight: + description: + - >- + Integer specifying the weight of the service. A larger number specifies a greater weight. Defines the + capacity of the service relative to the other services in the load balancing configuration. + Determines the priority given to the service in load balancing decisions. + - "Minimum value = C(1)" + - "Maximum value = C(100)" + + ssl_certkey: + description: + - The name of the ssl certificate that is bound to this service. + - The ssl certificate must already exist. + - Creating the certificate can be done with the M(community.network.netscaler_ssl_certkey) module. + - This option is only applicable only when C(servicetype) is C(SSL). + + disabled: + description: + - When set to C(yes) the lb vserver will be disabled. + - When set to C(no) the lb vserver will be enabled. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: 'no' + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# Netscaler services service-http-1, service-http-2 must have been already created with the netscaler_service module + +- name: Create a load balancing vserver bound to services + delegate_to: localhost + community.network.netscaler_lb_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + name: lb_vserver_1 + servicetype: HTTP + timeout: 12 + ipv46: 6.93.3.3 + port: 80 + servicebindings: + - servicename: service-http-1 + weight: 80 + - servicename: service-http-2 + weight: 20 + +# Service group service-group-1 must have been already created with the netscaler_servicegroup module + +- name: Create load balancing vserver bound to servicegroup + delegate_to: localhost + community.network.netscaler_lb_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + state: present + + name: lb_vserver_2 + servicetype: HTTP + ipv46: 6.92.2.2 + port: 80 + timeout: 10 + servicegroupbindings: + - servicegroupname: service-group-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + get_immutables_intersection, + ensure_feature_is_enabled +) +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver import lbvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver_servicegroup_binding import lbvserver_servicegroup_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding import lbvserver_service_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding import sslvserver_sslcertkey_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + + PYTHON_SDK_IMPORTED = True +except ImportError as e: + IMPORT_ERROR = str(e) + PYTHON_SDK_IMPORTED = False + + +def lb_vserver_exists(client, module): + log('Checking if lb vserver exists') + if lbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def lb_vserver_identical(client, module, lbvserver_proxy): + log('Checking if configured lb vserver is identical') + lbvserver_list = lbvserver.get_filtered(client, 'name:%s' % module.params['name']) + if lbvserver_proxy.has_equal_attributes(lbvserver_list[0]): + return True + else: + return False + + +def lb_vserver_diff(client, module, lbvserver_proxy): + lbvserver_list = lbvserver.get_filtered(client, 'name:%s' % module.params['name']) + return lbvserver_proxy.diff_object(lbvserver_list[0]) + + +def get_configured_service_bindings(client, module): + log('Getting configured service bindings') + + readwrite_attrs = [ + 'weight', + 'name', + 'servicename', + 'servicegroupname' + ] + readonly_attrs = [ + 'preferredlocation', + 'vserverid', + 'vsvrbindsvcip', + 'servicetype', + 'cookieipport', + 'port', + 'vsvrbindsvcport', + 'curstate', + 'ipv46', + 'dynamicweight', + ] + + configured_bindings = {} + if 'servicebindings' in module.params and module.params['servicebindings'] is not None: + for binding in module.params['servicebindings']: + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['name'] = module.params['name'] + key = binding['servicename'].strip() + configured_bindings[key] = ConfigProxy( + actual=lbvserver_service_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + return configured_bindings + + +def get_configured_servicegroup_bindings(client, module): + log('Getting configured service group bindings') + readwrite_attrs = [ + 'weight', + 'name', + 'servicename', + 'servicegroupname', + ] + readonly_attrs = [] + + configured_bindings = {} + + if 'servicegroupbindings' in module.params and module.params['servicegroupbindings'] is not None: + for binding in module.params['servicegroupbindings']: + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['name'] = module.params['name'] + key = binding['servicegroupname'].strip() + configured_bindings[key] = ConfigProxy( + actual=lbvserver_servicegroup_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + + return configured_bindings + + +def get_actual_service_bindings(client, module): + log('Getting actual service bindings') + bindings = {} + try: + if lbvserver_service_binding.count(client, module.params['name']) == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + bindigs_list = lbvserver_service_binding.get(client, module.params['name']) + + for item in bindigs_list: + key = item.servicename + bindings[key] = item + + return bindings + + +def get_actual_servicegroup_bindings(client, module): + log('Getting actual service group bindings') + bindings = {} + + try: + if lbvserver_servicegroup_binding.count(client, module.params['name']) == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + bindigs_list = lbvserver_servicegroup_binding.get(client, module.params['name']) + + for item in bindigs_list: + key = item.servicegroupname + bindings[key] = item + + return bindings + + +def service_bindings_identical(client, module): + log('service_bindings_identical') + + # Compare service keysets + configured_service_bindings = get_configured_service_bindings(client, module) + service_bindings = get_actual_service_bindings(client, module) + configured_keyset = set(configured_service_bindings.keys()) + service_keyset = set(service_bindings.keys()) + if len(configured_keyset ^ service_keyset) > 0: + return False + + # Compare service item to item + for key in configured_service_bindings.keys(): + conf = configured_service_bindings[key] + serv = service_bindings[key] + log('s diff %s' % conf.diff_object(serv)) + if not conf.has_equal_attributes(serv): + return False + + # Fallthrough to success + return True + + +def servicegroup_bindings_identical(client, module): + log('servicegroup_bindings_identical') + + # Compare servicegroup keysets + configured_servicegroup_bindings = get_configured_servicegroup_bindings(client, module) + servicegroup_bindings = get_actual_servicegroup_bindings(client, module) + configured_keyset = set(configured_servicegroup_bindings.keys()) + service_keyset = set(servicegroup_bindings.keys()) + log('len %s' % len(configured_keyset ^ service_keyset)) + if len(configured_keyset ^ service_keyset) > 0: + return False + + # Compare servicegroup item to item + for key in configured_servicegroup_bindings.keys(): + conf = configured_servicegroup_bindings[key] + serv = servicegroup_bindings[key] + log('sg diff %s' % conf.diff_object(serv)) + if not conf.has_equal_attributes(serv): + return False + + # Fallthrough to success + return True + + +def sync_service_bindings(client, module): + log('sync_service_bindings') + + actual_bindings = get_actual_service_bindings(client, module) + configured_bindigns = get_configured_service_bindings(client, module) + + # Delete actual but not configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindigns.keys())) + for key in delete_keys: + log('Deleting service binding %s' % key) + actual_bindings[key].servicegroupname = '' + actual_bindings[key].delete(client, actual_bindings[key]) + + # Add configured but not in actual + add_keys = list(set(configured_bindigns.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding service binding %s' % key) + configured_bindigns[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindigns.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindigns[key].has_equal_attributes(actual_bindings[key]): + log('Updating service binding %s' % key) + actual_bindings[key].servicegroupname = '' + actual_bindings[key].delete(client, actual_bindings[key]) + configured_bindigns[key].add() + + +def sync_servicegroup_bindings(client, module): + log('sync_servicegroup_bindings') + + actual_bindings = get_actual_servicegroup_bindings(client, module) + configured_bindigns = get_configured_servicegroup_bindings(client, module) + + # Delete actual but not configured + delete_keys = list(set(actual_bindings.keys()) - set(configured_bindigns.keys())) + for key in delete_keys: + log('Deleting servicegroup binding %s' % key) + actual_bindings[key].servicename = None + actual_bindings[key].delete(client, actual_bindings[key]) + + # Add configured but not in actual + add_keys = list(set(configured_bindigns.keys()) - set(actual_bindings.keys())) + for key in add_keys: + log('Adding servicegroup binding %s' % key) + configured_bindigns[key].add() + + # Update existing if changed + modify_keys = list(set(configured_bindigns.keys()) & set(actual_bindings.keys())) + for key in modify_keys: + if not configured_bindigns[key].has_equal_attributes(actual_bindings[key]): + log('Updating servicegroup binding %s' % key) + actual_bindings[key].servicename = None + actual_bindings[key].delete(client, actual_bindings[key]) + configured_bindigns[key].add() + + +def ssl_certkey_bindings_identical(client, module): + log('Entering ssl_certkey_bindings_identical') + vservername = module.params['name'] + + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + log('Existing certs %s' % bindings) + + if module.params['ssl_certkey'] is None: + if len(bindings) == 0: + return True + else: + return False + else: + certificate_list = [item.certkeyname for item in bindings] + log('certificate_list %s' % certificate_list) + if certificate_list == [module.params['ssl_certkey']]: + return True + else: + return False + + +def ssl_certkey_bindings_sync(client, module): + log('Syncing ssl certificates') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + log('bindings len is %s' % len(bindings)) + + # Delete existing bindings + for binding in bindings: + sslvserver_sslcertkey_binding.delete(client, binding) + + # Add binding if appropriate + if module.params['ssl_certkey'] is not None: + binding = sslvserver_sslcertkey_binding() + binding.vservername = module.params['name'] + binding.certkeyname = module.params['ssl_certkey'] + sslvserver_sslcertkey_binding.add(client, binding) + + +def do_state_change(client, module, lbvserver_proxy): + if module.params['disabled']: + log('Disabling lb server') + result = lbvserver.disable(client, lbvserver_proxy.actual) + else: + log('Enabling lb server') + result = lbvserver.enable(client, lbvserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'DNS', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'RTSP', + 'PUSH', + 'SSL_PUSH', + 'RADIUS', + 'RDP', + 'MYSQL', + 'MSSQL', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'ORACLE', + 'SMPP', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX', + ] + ), + ipv46=dict(type='str'), + ippattern=dict(type='str'), + ipmask=dict(type='str'), + port=dict(type='int'), + range=dict(type='float'), + persistencetype=dict( + type='str', + choices=[ + 'SOURCEIP', + 'COOKIEINSERT', + 'SSLSESSION', + 'RULE', + 'URLPASSIVE', + 'CUSTOMSERVERID', + 'DESTIP', + 'SRCIPDESTIP', + 'CALLID', + 'RTSPSID', + 'DIAMETER', + 'FIXSESSION', + 'NONE', + ] + ), + timeout=dict(type='float'), + persistencebackup=dict( + type='str', + choices=[ + 'SOURCEIP', + 'NONE', + ] + ), + backuppersistencetimeout=dict(type='float'), + lbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'URLHASH', + 'DOMAINHASH', + 'DESTINATIONIPHASH', + 'SOURCEIPHASH', + 'SRCIPDESTIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'TOKEN', + 'SRCIPSRCPORTHASH', + 'LRTM', + 'CALLIDHASH', + 'CUSTOMLOAD', + 'LEASTREQUEST', + 'AUDITLOGHASH', + 'STATICPROXIMITY', + ] + ), + hashlength=dict(type='float'), + netmask=dict(type='str'), + v6netmasklen=dict(type='float'), + backuplbmethod=dict( + type='str', + choices=[ + 'ROUNDROBIN', + 'LEASTCONNECTION', + 'LEASTRESPONSETIME', + 'SOURCEIPHASH', + 'LEASTBANDWIDTH', + 'LEASTPACKETS', + 'CUSTOMLOAD', + ] + ), + cookiename=dict(type='str'), + listenpolicy=dict(type='str'), + listenpriority=dict(type='float'), + persistmask=dict(type='str'), + v6persistmasklen=dict(type='float'), + rtspnat=dict(type='bool'), + m=dict( + type='str', + choices=[ + 'IP', + 'MAC', + 'IPTUNNEL', + 'TOS', + ] + ), + tosid=dict(type='float'), + datalength=dict(type='float'), + dataoffset=dict(type='float'), + sessionless=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + connfailover=dict( + type='str', + choices=[ + 'DISABLED', + 'STATEFUL', + 'STATELESS', + ] + ), + redirurl=dict(type='str'), + cacheable=dict(type='bool'), + clttimeout=dict(type='float'), + somethod=dict( + type='str', + choices=[ + 'CONNECTION', + 'DYNAMICCONNECTION', + 'BANDWIDTH', + 'HEALTH', + 'NONE', + ] + ), + sopersistence=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + sopersistencetimeout=dict(type='float'), + healththreshold=dict(type='float'), + sothreshold=dict(type='float'), + sobackupaction=dict( + type='str', + choices=[ + 'DROP', + 'ACCEPT', + 'REDIRECT', + ] + ), + redirectportrewrite=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + insertvserveripport=dict( + type='str', + choices=[ + 'OFF', + 'VIPADDR', + 'V6TOV4MAPPING', + ] + ), + vipheader=dict(type='str'), + authenticationhost=dict(type='str'), + authentication=dict(type='bool'), + authn401=dict(type='bool'), + authnvsname=dict(type='str'), + push=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + pushvserver=dict(type='str'), + pushlabel=dict(type='str'), + pushmulticlients=dict(type='bool'), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + dbprofilename=dict(type='str'), + comment=dict(type='str'), + l2conn=dict(type='bool'), + oracleserverversion=dict( + type='str', + choices=[ + '10G', + '11G', + ] + ), + mssqlserverversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + mysqlprotocolversion=dict(type='float'), + mysqlserverversion=dict(type='str'), + mysqlcharacterset=dict(type='float'), + mysqlservercapabilities=dict(type='float'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + icmpvsrresponse=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + rhistate=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + newservicerequest=dict(type='float'), + newservicerequestunit=dict( + type='str', + choices=[ + 'PER_SECOND', + 'PERCENT', + ] + ), + newservicerequestincrementinterval=dict(type='float'), + minautoscalemembers=dict(type='float'), + maxautoscalemembers=dict(type='float'), + skippersistency=dict( + type='str', + choices=[ + 'Bypass', + 'ReLb', + 'None', + ] + ), + authnprofile=dict(type='str'), + macmoderetainvlan=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dbslb=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dns64=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + bypassaaaa=dict(type='bool'), + recursionavailable=dict(type='bool'), + processlocal=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + dnsprofilename=dict(type='str'), + ) + + hand_inserted_arguments = dict( + servicebindings=dict(type='list'), + servicegroupbindings=dict(type='list'), + ssl_certkey=dict(type='str'), + disabled=dict( + type='bool', + default=False + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'name', + 'servicetype', + 'ipv46', + 'ippattern', + 'ipmask', + 'port', + 'range', + 'persistencetype', + 'timeout', + 'persistencebackup', + 'backuppersistencetimeout', + 'lbmethod', + 'hashlength', + 'netmask', + 'v6netmasklen', + 'backuplbmethod', + 'cookiename', + 'listenpolicy', + 'listenpriority', + 'persistmask', + 'v6persistmasklen', + 'rtspnat', + 'm', + 'tosid', + 'datalength', + 'dataoffset', + 'sessionless', + 'connfailover', + 'redirurl', + 'cacheable', + 'clttimeout', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'healththreshold', + 'sothreshold', + 'sobackupaction', + 'redirectportrewrite', + 'downstateflush', + 'disableprimaryondown', + 'insertvserveripport', + 'vipheader', + 'authenticationhost', + 'authentication', + 'authn401', + 'authnvsname', + 'push', + 'pushvserver', + 'pushlabel', + 'pushmulticlients', + 'tcpprofilename', + 'httpprofilename', + 'dbprofilename', + 'comment', + 'l2conn', + 'oracleserverversion', + 'mssqlserverversion', + 'mysqlprotocolversion', + 'mysqlserverversion', + 'mysqlcharacterset', + 'mysqlservercapabilities', + 'appflowlog', + 'netprofile', + 'icmpvsrresponse', + 'rhistate', + 'newservicerequest', + 'newservicerequestunit', + 'newservicerequestincrementinterval', + 'minautoscalemembers', + 'maxautoscalemembers', + 'skippersistency', + 'authnprofile', + 'macmoderetainvlan', + 'dbslb', + 'dns64', + 'bypassaaaa', + 'recursionavailable', + 'processlocal', + 'dnsprofilename', + ] + + readonly_attrs = [ + 'value', + 'ipmapping', + 'ngname', + 'type', + 'curstate', + 'effectivestate', + 'status', + 'lbrrreason', + 'redirect', + 'precedence', + 'homepage', + 'dnsvservername', + 'domain', + 'policyname', + 'cachevserver', + 'health', + 'gotopriorityexpression', + 'ruletype', + 'groupname', + 'cookiedomain', + 'map', + 'gt2gb', + 'consolidatedlconn', + 'consolidatedlconngbl', + 'thresholdvalue', + 'bindpoint', + 'invoke', + 'labeltype', + 'labelname', + 'version', + 'totalservices', + 'activeservices', + 'statechangetimesec', + 'statechangetimeseconds', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'isgslb', + 'vsvrdynconnsothreshold', + 'backupvserverstatus', + '__count', + ] + + immutable_attrs = [ + 'name', + 'servicetype', + 'ipv46', + 'port', + 'range', + 'state', + 'redirurl', + 'vipheader', + 'newservicerequestunit', + 'td', + ] + + transforms = { + 'rtspnat': ['bool_on_off'], + 'authn401': ['bool_on_off'], + 'bypassaaaa': ['bool_yes_no'], + 'authentication': ['bool_on_off'], + 'cacheable': ['bool_yes_no'], + 'l2conn': ['bool_on_off'], + 'pushmulticlients': ['bool_yes_no'], + 'recursionavailable': ['bool_yes_no'], + 'sessionless': [lambda v: v.upper()], + 'sopersistence': [lambda v: v.upper()], + 'redirectportrewrite': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'disableprimaryondown': [lambda v: v.upper()], + 'push': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + 'macmoderetainvlan': [lambda v: v.upper()], + 'dbslb': [lambda v: v.upper()], + 'dns64': [lambda v: v.upper()], + 'processlocal': [lambda v: v.upper()], + } + + lbvserver_proxy = ConfigProxy( + actual=lbvserver(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'LB') + if module.params['state'] == 'present': + log('Applying actions for state present') + + if not lb_vserver_exists(client, module): + log('Add lb vserver') + if not module.check_mode: + lbvserver_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not lb_vserver_identical(client, module, lbvserver_proxy): + + # Check if we try to change value of immutable attributes + diff_dict = lb_vserver_diff(client, module, lbvserver_proxy) + immutables_changed = get_immutables_intersection(lbvserver_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + log('Update lb vserver') + if not module.check_mode: + lbvserver_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Present noop') + + if not service_bindings_identical(client, module): + if not module.check_mode: + sync_service_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not servicegroup_bindings_identical(client, module): + if not module.check_mode: + sync_servicegroup_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if module.params['servicetype'] != 'SSL' and module.params['ssl_certkey'] is not None: + module.fail_json(msg='ssl_certkey is applicable only to SSL vservers', **module_result) + + # Check if SSL certkey is sane + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + if not module.check_mode: + ssl_certkey_bindings_sync(client, module) + + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, lbvserver_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check + log('Sanity checks for state present') + if not module.check_mode: + if not lb_vserver_exists(client, module): + module.fail_json(msg='Did not create lb vserver', **module_result) + + if not lb_vserver_identical(client, module, lbvserver_proxy): + msg = 'lb vserver is not configured correctly' + module.fail_json(msg=msg, diff=lb_vserver_diff(client, module, lbvserver_proxy), **module_result) + + if not service_bindings_identical(client, module): + module.fail_json(msg='service bindings are not identical', **module_result) + + if not servicegroup_bindings_identical(client, module): + module.fail_json(msg='servicegroup bindings are not identical', **module_result) + + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + module.fail_json(msg='sll certkey bindings not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if lb_vserver_exists(client, module): + if not module.check_mode: + log('Delete lb vserver') + lbvserver_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + log('Absent noop') + module_result['changed'] = False + + # Sanity check + log('Sanity checks for state absent') + if not module.check_mode: + if lb_vserver_exists(client, module): + module.fail_json(msg='lb vserver still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_nitro_request.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_nitro_request.py new file mode 100644 index 00000000..31292c7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_nitro_request.py @@ -0,0 +1,902 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_nitro_request +short_description: Issue Nitro API requests to a Netscaler instance. +description: + - Issue Nitro API requests to a Netscaler instance. + - This is intended to be a short hand for using the uri Ansible module to issue the raw HTTP requests directly. + - It provides consistent return values and has no other dependencies apart from the base Ansible runtime environment. + - This module is intended to run either on the Ansible control node or a bastion (jumpserver) with access to the actual Netscaler instance + + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + nsip: + description: + - The IP address of the Netscaler or MAS instance where the Nitro API calls will be made. + - "The port can be specified with the colon C(:). E.g. C(192.168.1.1:555)." + + nitro_user: + description: + - The username with which to authenticate to the Netscaler node. + required: true + + nitro_pass: + description: + - The password with which to authenticate to the Netscaler node. + required: true + + nitro_protocol: + choices: [ 'http', 'https' ] + default: http + description: + - Which protocol to use when accessing the Nitro API objects. + + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. + default: 'yes' + type: bool + + nitro_auth_token: + description: + - The authentication token provided by the C(mas_login) operation. It is required when issuing Nitro API calls through a MAS proxy. + + resource: + description: + - The type of resource we are operating on. + - It is required for all I(operation) values except C(mas_login) and C(save_config). + + name: + description: + - The name of the resource we are operating on. + - "It is required for the following I(operation) values: C(update), C(get), C(delete)." + + attributes: + description: + - The attributes of the Nitro object we are operating on. + - "It is required for the following I(operation) values: C(add), C(update), C(action)." + + args: + description: + - A dictionary which defines the key arguments by which we will select the Nitro object to operate on. + - "It is required for the following I(operation) values: C(get_by_args), C('delete_by_args')." + + filter: + description: + - A dictionary which defines the filter with which to refine the Nitro objects returned by the C(get_filtered) I(operation). + + operation: + description: + - Define the Nitro operation that we want to perform. + choices: + - add + - update + - get + - get_by_args + - get_filtered + - get_all + - delete + - delete_by_args + - count + - mas_login + - save_config + - action + + expected_nitro_errorcode: + description: + - A list of numeric values that signify that the operation was successful. + default: [0] + required: true + + action: + description: + - The action to perform when the I(operation) value is set to C(action). + - Some common values for this parameter are C(enable), C(disable), C(rename). + + instance_ip: + description: + - The IP address of the target Netscaler instance when issuing a Nitro request through a MAS proxy. + + instance_name: + description: + - The name of the target Netscaler instance when issuing a Nitro request through a MAS proxy. + + instance_id: + description: + - The id of the target Netscaler instance when issuing a Nitro request through a MAS proxy. +''' + +EXAMPLES = ''' +- name: Add a server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: add + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.1 + +- name: Update server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: update + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.2 + +- name: Get server + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get + resource: server + name: test-server-1 + +- name: Delete server + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: delete + resource: server + name: test-server-1 + +- name: Rename server + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: action + action: rename + resource: server + attributes: + name: test-server-1 + newname: test-server-2 + +- name: Get server by args + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get_by_args + resource: server + args: + name: test-server-1 + +- name: Get server by filter + delegate_to: localhost + register: result + community.network.netscaler_nitro_request: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: get_filtered + resource: server + filter: + ipaddress: 192.168.1.2 + +# Doing a NITRO request through MAS. +# Requires to have an authentication token from the mas_login and used as the nitro_auth_token parameter +# Also nsip is the MAS address and the target Netscaler IP must be defined with instance_ip +# The rest of the task arguments remain the same as when issuing the NITRO request directly to a Netscaler instance. + +- name: Do mas login + delegate_to: localhost + register: login_result + community.network.netscaler_nitro_request: + nsip: "{{ mas_ip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + operation: mas_login + +- name: Add resource through MAS proxy + delegate_to: localhost + community.network.netscaler_nitro_request: + nsip: "{{ mas_ip }}" + nitro_auth_token: "{{ login_result.nitro_auth_token }}" + instance_ip: "{{ nsip }}" + operation: add + resource: server + name: test-server-1 + attributes: + name: test-server-1 + ipaddress: 192.168.1.7 +''' + +RETURN = ''' +nitro_errorcode: + description: A numeric value containing the return code of the NITRO operation. When 0 the operation is successful. Any non zero value indicates an error. + returned: always + type: int + sample: 0 + +nitro_message: + description: A string containing a human readable explanation for the NITRO operation result. + returned: always + type: str + sample: Success + +nitro_severity: + description: A string describing the severity of the NITRO operation error or NONE. + returned: always + type: str + sample: NONE + +http_response_data: + description: A dictionary that contains all the HTTP response's data. + returned: always + type: dict + sample: "status: 200" + +http_response_body: + description: A string with the actual HTTP response body content if existent. If there is no HTTP response body it is an empty string. + returned: always + type: str + sample: "{ errorcode: 0, message: Done, severity: NONE }" + +nitro_object: + description: The object returned from the NITRO operation. This is applicable to the various get operations which return an object. + returned: when applicable + type: list + sample: + - + ipaddress: "192.168.1.8" + ipv6address: "NO" + maxbandwidth: "0" + name: "test-server-1" + port: 0 + sp: "OFF" + state: "ENABLED" + +nitro_auth_token: + description: The token returned by the C(mas_login) operation when successful. + returned: when applicable + type: str + sample: "##E8D7D74DDBD907EE579E8BB8FF4529655F22227C1C82A34BFC93C9539D66" +''' + + +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.basic import AnsibleModule +import codecs + + +class NitroAPICaller(object): + + _argument_spec = dict( + nsip=dict( + fallback=(env_fallback, ['NETSCALER_NSIP']), + ), + nitro_user=dict( + fallback=(env_fallback, ['NETSCALER_NITRO_USER']), + ), + nitro_pass=dict( + fallback=(env_fallback, ['NETSCALER_NITRO_PASS']), + no_log=True + ), + nitro_protocol=dict( + choices=['http', 'https'], + fallback=(env_fallback, ['NETSCALER_NITRO_PROTOCOL']), + default='http' + ), + validate_certs=dict( + default=True, + type='bool' + ), + nitro_auth_token=dict( + type='str', + no_log=True + ), + resource=dict(type='str'), + name=dict(type='str'), + attributes=dict(type='dict'), + + args=dict(type='dict'), + filter=dict(type='dict'), + + operation=dict( + type='str', + required=True, + choices=[ + 'add', + 'update', + 'get', + 'get_by_args', + 'get_filtered', + 'get_all', + 'delete', + 'delete_by_args', + 'count', + + 'mas_login', + + # Actions + 'save_config', + + # Generic action handler + 'action', + ] + ), + expected_nitro_errorcode=dict( + type='list', + default=[0], + ), + action=dict(type='str'), + instance_ip=dict(type='str'), + instance_name=dict(type='str'), + instance_id=dict(type='str'), + ) + + def __init__(self): + + self._module = AnsibleModule( + argument_spec=self._argument_spec, + supports_check_mode=False, + ) + + self._module_result = dict( + failed=False, + ) + + # Prepare the http headers according to module arguments + self._headers = {} + self._headers['Content-Type'] = 'application/json' + + # Check for conflicting authentication methods + have_token = self._module.params['nitro_auth_token'] is not None + have_userpass = None not in (self._module.params['nitro_user'], self._module.params['nitro_pass']) + login_operation = self._module.params['operation'] == 'mas_login' + + if have_token and have_userpass: + self.fail_module(msg='Cannot define both authentication token and username/password') + + if have_token: + self._headers['Cookie'] = "NITRO_AUTH_TOKEN=%s" % self._module.params['nitro_auth_token'] + + if have_userpass and not login_operation: + self._headers['X-NITRO-USER'] = self._module.params['nitro_user'] + self._headers['X-NITRO-PASS'] = self._module.params['nitro_pass'] + + # Do header manipulation when doing a MAS proxy call + if self._module.params['instance_ip'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_IP'] = self._module.params['instance_ip'] + elif self._module.params['instance_name'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_NAME'] = self._module.params['instance_name'] + elif self._module.params['instance_id'] is not None: + self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_ID'] = self._module.params['instance_id'] + + def edit_response_data(self, r, info, result, success_status): + # Search for body in both http body and http data + if r is not None: + result['http_response_body'] = codecs.decode(r.read(), 'utf-8') + elif 'body' in info: + result['http_response_body'] = codecs.decode(info['body'], 'utf-8') + del info['body'] + else: + result['http_response_body'] = '' + + result['http_response_data'] = info + + # Update the nitro_* parameters according to expected success_status + # Use explicit return values from http response or deduce from http status code + + # Nitro return code in http data + result['nitro_errorcode'] = None + result['nitro_message'] = None + result['nitro_severity'] = None + + if result['http_response_body'] != '': + try: + data = self._module.from_json(result['http_response_body']) + except ValueError: + data = {} + result['nitro_errorcode'] = data.get('errorcode') + result['nitro_message'] = data.get('message') + result['nitro_severity'] = data.get('severity') + + # If we do not have the nitro errorcode from body deduce it from the http status + if result['nitro_errorcode'] is None: + # HTTP status failed + if result['http_response_data'].get('status') != success_status: + result['nitro_errorcode'] = -1 + result['nitro_message'] = result['http_response_data'].get('msg', 'HTTP status %s' % result['http_response_data']['status']) + result['nitro_severity'] = 'ERROR' + # HTTP status succeeded + else: + result['nitro_errorcode'] = 0 + result['nitro_message'] = 'Success' + result['nitro_severity'] = 'NONE' + + def handle_get_return_object(self, result): + result['nitro_object'] = [] + if result['nitro_errorcode'] == 0: + if result['http_response_body'] != '': + data = self._module.from_json(result['http_response_body']) + if self._module.params['resource'] in data: + result['nitro_object'] = data[self._module.params['resource']] + else: + del result['nitro_object'] + + def fail_module(self, msg, **kwargs): + self._module_result['failed'] = True + self._module_result['changed'] = False + self._module_result.update(kwargs) + self._module_result['msg'] = msg + self._module.fail_json(**self._module_result) + + def main(self): + if self._module.params['operation'] == 'add': + result = self.add() + + if self._module.params['operation'] == 'update': + result = self.update() + + if self._module.params['operation'] == 'delete': + result = self.delete() + + if self._module.params['operation'] == 'delete_by_args': + result = self.delete_by_args() + + if self._module.params['operation'] == 'get': + result = self.get() + + if self._module.params['operation'] == 'get_by_args': + result = self.get_by_args() + + if self._module.params['operation'] == 'get_filtered': + result = self.get_filtered() + + if self._module.params['operation'] == 'get_all': + result = self.get_all() + + if self._module.params['operation'] == 'count': + result = self.count() + + if self._module.params['operation'] == 'mas_login': + result = self.mas_login() + + if self._module.params['operation'] == 'action': + result = self.action() + + if self._module.params['operation'] == 'save_config': + result = self.save_config() + + if result['nitro_errorcode'] not in self._module.params['expected_nitro_errorcode']: + self.fail_module(msg='NITRO Failure', **result) + + self._module_result.update(result) + self._module.exit_json(**self._module_result) + + def exit_module(self): + self._module.exit_json() + + def add(self): + # Check if required attributes are present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=201) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def update(self): + # Check if required attributes are arguments present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource name is undefined.') + + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='PUT', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def get(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource name is undefined.') + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_by_args(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['args'] is None: + self.fail_module(msg='NITRO args is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + args_dict = self._module.params['args'] + args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict]) + + args = 'args=' + args + + url = '?'.join([url, args]) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + result = {} + self.edit_response_data(r, info, result, success_status=200) + + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_filtered(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['filter'] is None: + self.fail_module(msg='NITRO filter is undefined.') + + filter_str = ','.join('%s:%s' % (k, v) for k, v in self._module.params['filter'].items()) + + url = '%s://%s/nitro/v1/config/%s?filter=%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + filter_str, + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def get_all(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + print('headers %s' % self._headers) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + self.handle_get_return_object(result) + self._module_result['changed'] = False + + return result + + def delete(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['name'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + # Deletion by name takes precedence over deletion by attributes + + url = '%s://%s/nitro/v1/config/%s/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['name'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='DELETE', + ) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def delete_by_args(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + if self._module.params['args'] is None: + self.fail_module(msg='NITRO args is undefined.') + + url = '%s://%s/nitro/v1/config/%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + args_dict = self._module.params['args'] + args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict]) + + args = 'args=' + args + + url = '?'.join([url, args]) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='DELETE', + ) + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def count(self): + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + + url = '%s://%s/nitro/v1/config/%s?count=yes' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + ) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + method='GET', + ) + + result = {} + self.edit_response_data(r, info, result) + + if result['http_response_body'] != '': + data = self._module.from_json(result['http_response_body']) + + result['nitro_errorcode'] = data['errorcode'] + result['nitro_message'] = data['message'] + result['nitro_severity'] = data['severity'] + if self._module.params['resource'] in data: + result['nitro_count'] = data[self._module.params['resource']][0]['__count'] + + self._module_result['changed'] = False + + return result + + def action(self): + # Check if required attributes are present + if self._module.params['resource'] is None: + self.fail_module(msg='NITRO resource is undefined.') + if self._module.params['attributes'] is None: + self.fail_module(msg='NITRO resource attributes are undefined.') + if self._module.params['action'] is None: + self.fail_module(msg='NITRO action is undefined.') + + url = '%s://%s/nitro/v1/config/%s?action=%s' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + self._module.params['resource'], + self._module.params['action'], + ) + + data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']}) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + self._module_result['changed'] = True + else: + self._module_result['changed'] = False + + return result + + def mas_login(self): + url = '%s://%s/nitro/v1/config/login' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + ) + + login_credentials = { + 'login': { + + 'username': self._module.params['nitro_user'], + 'password': self._module.params['nitro_pass'], + } + } + + data = 'object=\n%s' % self._module.jsonify(login_credentials) + + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + print(r, info) + + result = {} + self.edit_response_data(r, info, result, success_status=200) + + if result['nitro_errorcode'] == 0: + body_data = self._module.from_json(result['http_response_body']) + result['nitro_auth_token'] = body_data['login'][0]['sessionid'] + + self._module_result['changed'] = False + + return result + + def save_config(self): + + url = '%s://%s/nitro/v1/config/nsconfig?action=save' % ( + self._module.params['nitro_protocol'], + self._module.params['nsip'], + ) + + data = self._module.jsonify( + { + 'nsconfig': {}, + } + ) + r, info = fetch_url( + self._module, + url=url, + headers=self._headers, + data=data, + method='POST', + ) + + result = {} + + self.edit_response_data(r, info, result, success_status=200) + self._module_result['changed'] = False + + return result + + +def main(): + + nitro_api_caller = NitroAPICaller() + nitro_api_caller.main() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_save_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_save_config.py new file mode 100644 index 00000000..bcc43f10 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_save_config.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_save_config +short_description: Save Netscaler configuration. +description: + - This module unconditionally saves the configuration on the target netscaler node. + - This module does not support check mode. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + nsip: + description: + - The ip address of the netscaler appliance where the nitro API calls will be made. + - "The port can be specified with the colon (:). E.g. C(192.168.1.1:555)." + required: True + + nitro_user: + description: + - The username with which to authenticate to the netscaler node. + required: True + + nitro_pass: + description: + - The password with which to authenticate to the netscaler node. + required: True + + nitro_protocol: + choices: [ 'http', 'https' ] + default: http + description: + - Which protocol to use when accessing the nitro API objects. + + validate_certs: + description: + - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. + required: false + default: 'yes' + type: bool + + nitro_timeout: + description: + - Time in seconds until a timeout error is thrown when establishing a new session with Netscaler. + default: 310 + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +--- +- name: Save netscaler configuration + delegate_to: localhost + community.network.netscaler_save_config: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + +- name: Setup server without saving configuration + delegate_to: localhost + notify: Save configuration + netscaler_server: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + save_config: no + + name: server-1 + ipaddress: 192.168.1.1 + +# Under playbook's handlers + +- name: Save configuration + delegate_to: localhost + community.network.netscaler_save_config: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +''' + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import get_nitro_client, log, loglines, netscaler_common_arguments + + +def main(): + + argument_spec = copy.deepcopy(netscaler_common_arguments) + + # Delete common arguments irrelevant to this module + del argument_spec['state'] + del argument_spec['save_config'] + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + try: + log('Saving configuration') + client.save_config() + except nitro_exception as e: + msg = "nitro exception errorcode=" + str(e.errorcode) + ",message=" + e.message + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_server.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_server.py new file mode 100644 index 00000000..e33adb20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_server.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_server +short_description: Manage server configuration +description: + - Manage server entities configuration. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + name: + description: + - "Name for the server." + - >- + Must begin with an ASCII alphabetic or underscore C(_) character, and must contain only ASCII + alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals C(=), and hyphen C(-) + characters. + - "Can be changed after the name is created." + - "Minimum length = 1" + + ipaddress: + description: + - >- + IPv4 or IPv6 address of the server. If you create an IP address based server, you can specify the + name of the server, instead of its IP address, when creating a service. Note: If you do not create a + server entry, the server IP address that you enter when you create a service becomes the name of the + server. + + domain: + description: + - "Domain name of the server. For a domain based configuration, you must create the server first." + - "Minimum length = 1" + + translationip: + description: + - "IP address used to transform the server's DNS-resolved IP address." + + translationmask: + description: + - "The netmask of the translation ip." + + domainresolveretry: + description: + - >- + Time, in seconds, for which the NetScaler appliance must wait, after DNS resolution fails, before + sending the next DNS query to resolve the domain name. + - "Minimum value = C(5)" + - "Maximum value = C(20939)" + default: 5 + + ipv6address: + description: + - >- + Support IPv6 addressing mode. If you configure a server with the IPv6 addressing mode, you cannot use + the server in the IPv4 addressing mode. + default: false + type: bool + + comment: + description: + - "Any information about the server." + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = C(0)" + - "Maximum value = C(4094)" + + graceful: + description: + - >- + Shut down gracefully, without accepting any new connections, and disabling each service when all of + its connections are closed. + - This option is meaningful only when setting the I(disabled) option to C(true) + type: bool + + delay: + description: + - Time, in seconds, after which all the services configured on the server are disabled. + - This option is meaningful only when setting the I(disabled) option to C(true) + + disabled: + description: + - When set to C(true) the server state will be set to C(disabled). + - When set to C(false) the server state will be set to C(enabled). + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup server + delegate_to: localhost + community.network.netscaler_server: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: server-1 + ipaddress: 192.168.1.1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' } +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.server import server + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, + netscaler_common_arguments, + log, loglines, + get_immutables_intersection) + + +def server_exists(client, module): + log('Checking if server exists') + if server.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def server_identical(client, module, server_proxy): + log('Checking if configured server is identical') + if server.count_filtered(client, 'name:%s' % module.params['name']) == 0: + return False + diff = diff_list(client, module, server_proxy) + + # Remove options that are not present in nitro server object + # These are special options relevant to the disabled action + for option in ['graceful', 'delay']: + if option in diff: + del diff[option] + + if diff == {}: + return True + else: + return False + + +def diff_list(client, module, server_proxy): + ret_val = server_proxy.diff_object(server.get_filtered(client, 'name:%s' % module.params['name'])[0]), + return ret_val[0] + + +def do_state_change(client, module, server_proxy): + if module.params['disabled']: + log('Disabling server') + result = server.disable(client, server_proxy.actual) + else: + log('Enabling server') + result = server.enable(client, server_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + ipaddress=dict(type='str'), + domain=dict(type='str'), + translationip=dict(type='str'), + translationmask=dict(type='str'), + domainresolveretry=dict(type='int'), + ipv6address=dict( + type='bool', + default=False + ), + comment=dict(type='str'), + td=dict(type='float'), + graceful=dict(type='bool'), + delay=dict(type='float') + ) + + hand_inserted_arguments = dict( + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + + client = get_nitro_client(module) + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate Server Config object + readwrite_attrs = [ + 'name', + 'ipaddress', + 'domain', + 'translationip', + 'translationmask', + 'domainresolveretry', + 'ipv6address', + 'graceful', + 'delay', + 'comment', + 'td', + ] + + readonly_attrs = [ + 'statechangetimesec', + 'tickssincelaststatechange', + 'autoscale', + 'customserverid', + 'monthreshold', + 'maxclient', + 'maxreq', + 'maxbandwidth', + 'usip', + 'cka', + 'tcpb', + 'cmp', + 'clttimeout', + 'svrtimeout', + 'cipheader', + 'cip', + 'cacheable', + 'sc', + 'sp', + 'downstateflush', + 'appflowlog', + 'boundtd', + '__count', + ] + + immutable_attrs = [ + 'name', + 'domain', + 'ipv6address', + 'td', + ] + + transforms = { + 'graceful': ['bool_yes_no'], + 'ipv6address': ['bool_yes_no'], + } + + server_proxy = ConfigProxy( + actual=server(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not server_exists(client, module): + if not module.check_mode: + server_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not server_identical(client, module, server_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(server_proxy, diff_list(client, module, server_proxy).keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_list(client, module, server_proxy), **module_result) + if not module.check_mode: + server_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, server_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for result + log('Sanity checks for state present') + if not module.check_mode: + if not server_exists(client, module): + module.fail_json(msg='Server does not seem to exist', **module_result) + if not server_identical(client, module, server_proxy): + module.fail_json( + msg='Server is not configured according to parameters given', + diff=diff_list(client, module, server_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if server_exists(client, module): + if not module.check_mode: + server_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for result + log('Sanity checks for state absent') + if not module.check_mode: + if server_exists(client, module): + module.fail_json(msg='Server seems to be present', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_service.py new file mode 100644 index 00000000..430d2253 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_service.py @@ -0,0 +1,959 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_service +short_description: Manage service configuration in Netscaler +description: + - Manage service configuration in Netscaler. + - This module allows the creation, deletion and modification of Netscaler services. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + - This module supports check mode. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the service. Must begin with an ASCII alphabetic or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Cannot be changed after the service has been created. + - "Minimum length = 1" + + ip: + description: + - "IP to assign to the service." + - "Minimum length = 1" + + servername: + description: + - "Name of the server that hosts the service." + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'RPCSVR' + - 'DNS' + - 'ADNS' + - 'SNMP' + - 'RTSP' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'ADNS_TCP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + - 'RADIUS' + - 'RADIUSListener' + - 'RDP' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'SMPP' + - 'PPTP' + - 'GRE' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol in which data is exchanged with the service." + + port: + description: + - "Port number of the service." + - "Range 1 - 65535" + - "* in CLI is represented as 65535 in NITRO API" + + cleartextport: + description: + - >- + Port to which clear text data must be sent after the appliance decrypts incoming SSL traffic. + Applicable to transparent SSL services. + - "Minimum value = 1" + + cachetype: + choices: + - 'TRANSPARENT' + - 'REVERSE' + - 'FORWARD' + description: + - "Cache type supported by the cache server." + + maxclient: + description: + - "Maximum number of simultaneous open connections to the service." + - "Minimum value = 0" + - "Maximum value = 4294967294" + + healthmonitor: + description: + - "Monitor the health of this service" + default: yes + type: bool + + maxreq: + description: + - "Maximum number of requests that can be sent on a persistent connection to the service." + - "Note: Connection requests beyond this value are rejected." + - "Minimum value = 0" + - "Maximum value = 65535" + + cacheable: + description: + - "Use the transparent cache redirection virtual server to forward requests to the cache server." + - "Note: Do not specify this parameter if you set the Cache Type parameter." + default: no + type: bool + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Before forwarding a request to the service, insert an HTTP header with the client's IPv4 or IPv6 + address as its value. Used if the server needs the client's IP address for security, accounting, or + other purposes, and setting the Use Source IP parameter is not a viable option. + + cipheader: + description: + - >- + Name for the HTTP header whose value must be set to the IP address of the client. Used with the + Client IP parameter. If you set the Client IP parameter, and you do not specify a name for the + header, the appliance uses the header name specified for the global Client IP Header parameter (the + cipHeader parameter in the set ns param CLI command or the Client IP Header parameter in the + Configure HTTP Parameters dialog box at System > Settings > Change HTTP parameters). If the global + Client IP Header parameter is not specified, the appliance inserts a header with the name + "client-ip.". + - "Minimum length = 1" + + usip: + description: + - >- + Use the client's IP address as the source IP address when initiating a connection to the server. When + creating a service, if you do not set this parameter, the service inherits the global Use Source IP + setting (available in the enable ns mode and disable ns mode CLI commands, or in the System > + Settings > Configure modes > Configure Modes dialog box). However, you can override this setting + after you create the service. + type: bool + + pathmonitor: + description: + - "Path monitoring for clustering." + + pathmonitorindv: + description: + - "Individual Path monitoring decisions." + + useproxyport: + description: + - >- + Use the proxy port as the source port when initiating connections with the server. With the NO + setting, the client-side connection port is used as the source port for the server-side connection. + - "Note: This parameter is available only when the Use Source IP (USIP) parameter is set to YES." + type: bool + + sp: + description: + - "Enable surge protection for the service." + type: bool + + rtspsessionidremap: + description: + - "Enable RTSP session ID mapping for the service." + default: off + type: bool + + clttimeout: + description: + - "Time, in seconds, after which to terminate an idle client connection." + - "Minimum value = 0" + - "Maximum value = 31536000" + + svrtimeout: + description: + - "Time, in seconds, after which to terminate an idle server connection." + - "Minimum value = 0" + - "Maximum value = 31536000" + + customserverid: + description: + - >- + Unique identifier for the service. Used when the persistency type for the virtual server is set to + Custom Server ID. + default: 'None' + + serverid: + description: + - "The identifier for the service. This is used when the persistency type is set to Custom Server ID." + + cka: + description: + - "Enable client keep-alive for the service." + type: bool + + tcpb: + description: + - "Enable TCP buffering for the service." + type: bool + + cmp: + description: + - "Enable compression for the service." + type: bool + + maxbandwidth: + description: + - "Maximum bandwidth, in Kbps, allocated to the service." + - "Minimum value = 0" + - "Maximum value = 4294967287" + + accessdown: + description: + - >- + Use Layer 2 mode to bridge the packets sent to this service if it is marked as DOWN. If the service + is DOWN, and this parameter is disabled, the packets are dropped. + default: no + type: bool + monthreshold: + description: + - >- + Minimum sum of weights of the monitors that are bound to this service. Used to determine whether to + mark a service as UP or DOWN. + - "Minimum value = 0" + - "Maximum value = 65535" + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with a service whose state transitions from UP to DOWN. Do + not enable this option for applications that must complete their transactions. + + tcpprofilename: + description: + - "Name of the TCP profile that contains TCP configuration settings for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile that contains HTTP configuration settings for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + hashid: + description: + - >- + A numerical identifier that can be used by hash based load balancing methods. Must be unique for each + service. + - "Minimum value = 1" + + comment: + description: + - "Any information about the service." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging of AppFlow information." + + netprofile: + description: + - "Network profile to use for the service." + - "Minimum length = 1" + - "Maximum length = 127" + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = 0" + - "Maximum value = 4094" + + processlocal: + choices: + - 'enabled' + - 'disabled' + description: + - >- + By turning on this option packets destined to a service in a cluster will not under go any steering. + Turn this option for single packet request response mode or when the upstream device is performing a + proper RSS for connection based distribution. + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the service. DNS profile properties will applied to the + transactions processed by a service. This parameter is valid only for ADNS and ADNS-TCP services. + - "Minimum length = 1" + - "Maximum length = 127" + + ipaddress: + description: + - "The new IP address of the service." + + graceful: + description: + - >- + Shut down gracefully, not accepting any new connections, and disabling the service when all of its + connections are closed. + default: no + type: bool + + monitor_bindings: + description: + - A list of load balancing monitors to bind to this service. + - Each monitor entry is a dictionary which may contain the following options. + - Note that if not using the built in monitors they must first be setup. + suboptions: + monitorname: + description: + - Name of the monitor. + weight: + description: + - Weight to assign to the binding between the monitor and service. + dup_state: + choices: + - 'enabled' + - 'disabled' + description: + - State of the monitor. + - The state setting for a monitor of a given type affects all monitors of that type. + - For example, if an HTTP monitor is enabled, all HTTP monitors on the appliance are (or remain) enabled. + - If an HTTP monitor is disabled, all HTTP monitors on the appliance are disabled. + dup_weight: + description: + - Weight to assign to the binding between the monitor and service. + + disabled: + description: + - When set to C(yes) the service state will be set to DISABLED. + - When set to C(no) the service state will be set to ENABLED. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# Monitor monitor-1 must have been already setup + +- name: Setup http service + gather_facts: False + delegate_to: localhost + community.network.netscaler_service: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: service-http-1 + servicetype: HTTP + ipaddress: 10.78.0.1 + port: 80 + + monitor_bindings: + - monitor-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +diff: + description: A dictionary with a list of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' }" +''' + +import copy + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service import service + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding import service_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_service_binding import lbmonitor_service_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) + + +def service_exists(client, module): + if service.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def service_identical(client, module, service_proxy): + service_list = service.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = service_proxy.diff_object(service_list[0]) + # the actual ip address is stored in the ipaddress attribute + # of the retrieved object + if 'ip' in diff_dict: + del diff_dict['ip'] + if 'graceful' in diff_dict: + del diff_dict['graceful'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff(client, module, service_proxy): + service_list = service.get_filtered(client, 'name:%s' % module.params['name']) + diff_object = service_proxy.diff_object(service_list[0]) + if 'ip' in diff_object: + del diff_object['ip'] + return diff_object + + +def get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs): + bindings = {} + if module.params['monitor_bindings'] is not None: + for binding in module.params['monitor_bindings']: + attribute_values_dict = copy.deepcopy(binding) + # attribute_values_dict['servicename'] = module.params['name'] + attribute_values_dict['servicegroupname'] = module.params['name'] + binding_proxy = ConfigProxy( + actual=lbmonitor_service_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=monitor_bindings_rw_attrs, + ) + key = binding_proxy.monitorname + bindings[key] = binding_proxy + return bindings + + +def get_actual_monitor_bindings(client, module): + bindings = {} + if service_lbmonitor_binding.count(client, module.params['name']) == 0: + return bindings + + # Fallthrough to rest of execution + for binding in service_lbmonitor_binding.get(client, module.params['name']): + # Excluding default monitors since we cannot operate on them + if binding.monitor_name in ('tcp-default', 'ping-default'): + continue + key = binding.monitor_name + actual = lbmonitor_service_binding() + actual.weight = binding.weight + actual.monitorname = binding.monitor_name + actual.dup_weight = binding.dup_weight + actual.servicename = module.params['name'] + bindings[key] = actual + + return bindings + + +def monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs) + actual_bindings = get_actual_monitor_bindings(client, module) + + configured_key_set = set(configured_proxys.keys()) + actual_key_set = set(actual_bindings.keys()) + symmetrical_diff = configured_key_set ^ actual_key_set + if len(symmetrical_diff) > 0: + return False + + # Compare key to key + for monitor_name in configured_key_set: + proxy = configured_proxys[monitor_name] + actual = actual_bindings[monitor_name] + diff_dict = proxy.diff_object(actual) + if 'servicegroupname' in diff_dict: + if proxy.servicegroupname == actual.servicename: + del diff_dict['servicegroupname'] + if len(diff_dict) > 0: + return False + + # Fallthrought to success + return True + + +def sync_monitor_bindings(client, module, monitor_bindings_rw_attrs): + configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs) + actual_bindings = get_actual_monitor_bindings(client, module) + configured_keyset = set(configured_proxys.keys()) + actual_keyset = set(actual_bindings.keys()) + + # Delete extra + delete_keys = list(actual_keyset - configured_keyset) + for monitor_name in delete_keys: + log('Deleting binding for monitor %s' % monitor_name) + lbmonitor_service_binding.delete(client, actual_bindings[monitor_name]) + + # Delete and re-add modified + common_keyset = list(configured_keyset & actual_keyset) + for monitor_name in common_keyset: + proxy = configured_proxys[monitor_name] + actual = actual_bindings[monitor_name] + if not proxy.has_equal_attributes(actual): + log('Deleting and re adding binding for monitor %s' % monitor_name) + lbmonitor_service_binding.delete(client, actual) + proxy.add() + + # Add new + new_keys = list(configured_keyset - actual_keyset) + for monitor_name in new_keys: + log('Adding binding for monitor %s' % monitor_name) + configured_proxys[monitor_name].add() + + +def all_identical(client, module, service_proxy, monitor_bindings_rw_attrs): + return service_identical(client, module, service_proxy) and monitor_bindings_identical(client, module, monitor_bindings_rw_attrs) + + +def do_state_change(client, module, service_proxy): + if module.params['disabled']: + log('Disabling service') + result = service.disable(client, service_proxy.actual) + else: + log('Enabling service') + result = service.enable(client, service_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + name=dict(type='str'), + ip=dict(type='str'), + servername=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'RPCSVR', + 'DNS', + 'ADNS', + 'SNMP', + 'RTSP', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'ADNS_TCP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + 'RADIUS', + 'RADIUSListener', + 'RDP', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'SMPP', + 'PPTP', + 'GRE', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX' + ] + ), + port=dict(type='int'), + cleartextport=dict(type='int'), + cachetype=dict( + type='str', + choices=[ + 'TRANSPARENT', + 'REVERSE', + 'FORWARD', + ] + ), + maxclient=dict(type='float'), + healthmonitor=dict( + type='bool', + default=True, + ), + maxreq=dict(type='float'), + cacheable=dict( + type='bool', + default=False, + ), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + usip=dict(type='bool'), + useproxyport=dict(type='bool'), + sp=dict(type='bool'), + rtspsessionidremap=dict( + type='bool', + default=False, + ), + clttimeout=dict(type='float'), + svrtimeout=dict(type='float'), + customserverid=dict( + type='str', + default='None', + ), + cka=dict(type='bool'), + tcpb=dict(type='bool'), + cmp=dict(type='bool'), + maxbandwidth=dict(type='float'), + accessdown=dict( + type='bool', + default=False + ), + monthreshold=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + hashid=dict(type='float'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + netprofile=dict(type='str'), + processlocal=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ], + ), + dnsprofilename=dict(type='str'), + ipaddress=dict(type='str'), + graceful=dict( + type='bool', + default=False, + ), + ) + + hand_inserted_arguments = dict( + monitor_bindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Fallthrough to rest of execution + + # Instantiate Service Config object + readwrite_attrs = [ + 'name', + 'ip', + 'servername', + 'servicetype', + 'port', + 'cleartextport', + 'cachetype', + 'maxclient', + 'healthmonitor', + 'maxreq', + 'cacheable', + 'cip', + 'cipheader', + 'usip', + 'useproxyport', + 'sp', + 'rtspsessionidremap', + 'clttimeout', + 'svrtimeout', + 'customserverid', + 'cka', + 'tcpb', + 'cmp', + 'maxbandwidth', + 'accessdown', + 'monthreshold', + 'downstateflush', + 'tcpprofilename', + 'httpprofilename', + 'hashid', + 'comment', + 'appflowlog', + 'netprofile', + 'processlocal', + 'dnsprofilename', + 'ipaddress', + 'graceful', + ] + + readonly_attrs = [ + 'numofconnections', + 'policyname', + 'serviceconftype', + 'serviceconftype2', + 'value', + 'gslb', + 'dup_state', + 'publicip', + 'publicport', + 'svrstate', + 'monitor_state', + 'monstatcode', + 'lastresponse', + 'responsetime', + 'riseapbrstatsmsgcode2', + 'monstatparam1', + 'monstatparam2', + 'monstatparam3', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'stateupdatereason', + 'clmonowner', + 'clmonview', + 'serviceipstr', + 'oracleserverversion', + ] + + immutable_attrs = [ + 'name', + 'ip', + 'servername', + 'servicetype', + 'port', + 'cleartextport', + 'cachetype', + 'cipheader', + 'serverid', + 'state', + 'td', + 'monitor_name_svc', + 'riseapbrstatsmsgcode', + 'all', + 'Internal', + 'newname', + ] + + transforms = { + 'pathmonitorindv': ['bool_yes_no'], + 'cacheable': ['bool_yes_no'], + 'cka': ['bool_yes_no'], + 'pathmonitor': ['bool_yes_no'], + 'tcpb': ['bool_yes_no'], + 'sp': ['bool_on_off'], + 'graceful': ['bool_yes_no'], + 'usip': ['bool_yes_no'], + 'healthmonitor': ['bool_yes_no'], + 'useproxyport': ['bool_yes_no'], + 'rtspsessionidremap': ['bool_on_off'], + 'accessdown': ['bool_yes_no'], + 'cmp': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + 'processlocal': [lambda v: v.upper()], + } + + monitor_bindings_rw_attrs = [ + 'servicename', + 'servicegroupname', + 'dup_state', + 'dup_weight', + 'monitorname', + 'weight', + ] + + # Translate module arguments to correspondign config object attributes + if module.params['ip'] is None: + module.params['ip'] = module.params['ipaddress'] + + service_proxy = ConfigProxy( + actual=service(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not service_exists(client, module): + if not module.check_mode: + service_proxy.add() + sync_monitor_bindings(client, module, monitor_bindings_rw_attrs) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, service_proxy, monitor_bindings_rw_attrs): + + # Check if we try to change value of immutable attributes + diff_dict = diff(client, module, service_proxy) + immutables_changed = get_immutables_intersection(service_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + # Service sync + if not service_identical(client, module, service_proxy): + if not module.check_mode: + service_proxy.update() + + # Monitor bindings sync + if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + if not module.check_mode: + sync_monitor_bindings(client, module, monitor_bindings_rw_attrs) + + module_result['changed'] = True + if not module.check_mode: + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + if not module.check_mode: + res = do_state_change(client, module, service_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not service_exists(client, module): + module.fail_json(msg='Service does not exist', **module_result) + + if not service_identical(client, module, service_proxy): + module.fail_json(msg='Service differs from configured', diff=diff(client, module, service_proxy), **module_result) + + if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs): + module.fail_json(msg='Monitor bindings are not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if service_exists(client, module): + if not module.check_mode: + service_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if service_exists(client, module): + module.fail_json(msg='Service still exists', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_servicegroup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_servicegroup.py new file mode 100644 index 00000000..85e11492 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_servicegroup.py @@ -0,0 +1,1041 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_servicegroup +short_description: Manage service group configuration in Netscaler +description: + - Manage service group configuration in Netscaler. + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + servicegroupname: + description: + - >- + Name of the service group. Must begin with an ASCII alphabetic or underscore C(_) character, and must + contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals + C(=), and hyphen C(-) characters. Can be changed after the name is created. + - "Minimum length = 1" + + servicetype: + choices: + - 'HTTP' + - 'FTP' + - 'TCP' + - 'UDP' + - 'SSL' + - 'SSL_BRIDGE' + - 'SSL_TCP' + - 'DTLS' + - 'NNTP' + - 'RPCSVR' + - 'DNS' + - 'ADNS' + - 'SNMP' + - 'RTSP' + - 'DHCPRA' + - 'ANY' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'DNS_TCP' + - 'ADNS_TCP' + - 'MYSQL' + - 'MSSQL' + - 'ORACLE' + - 'RADIUS' + - 'RADIUSListener' + - 'RDP' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'TFTP' + - 'SMPP' + - 'PPTP' + - 'GRE' + - 'SYSLOGTCP' + - 'SYSLOGUDP' + - 'FIX' + - 'SSL_FIX' + description: + - "Protocol used to exchange data with the service." + + cachetype: + choices: + - 'TRANSPARENT' + - 'REVERSE' + - 'FORWARD' + description: + - "Cache type supported by the cache server." + + maxclient: + description: + - "Maximum number of simultaneous open connections for the service group." + - "Minimum value = C(0)" + - "Maximum value = C(4294967294)" + + maxreq: + description: + - "Maximum number of requests that can be sent on a persistent connection to the service group." + - "Note: Connection requests beyond this value are rejected." + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + cacheable: + description: + - "Use the transparent cache redirection virtual server to forward the request to the cache server." + - "Note: Do not set this parameter if you set the Cache Type." + type: bool + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - "Insert the Client IP header in requests forwarded to the service." + + cipheader: + description: + - >- + Name of the HTTP header whose value must be set to the IP address of the client. Used with the Client + IP parameter. If client IP insertion is enabled, and the client IP header is not specified, the value + of Client IP Header parameter or the value set by the set ns config command is used as client's IP + header name. + - "Minimum length = 1" + + usip: + description: + - >- + Use client's IP address as the source IP address when initiating connection to the server. With the + NO setting, which is the default, a mapped IP (MIP) address or subnet IP (SNIP) address is used as + the source IP address to initiate server side connections. + type: bool + + pathmonitor: + description: + - "Path monitoring for clustering." + type: bool + + pathmonitorindv: + description: + - "Individual Path monitoring decisions." + type: bool + + useproxyport: + description: + - >- + Use the proxy port as the source port when initiating connections with the server. With the NO + setting, the client-side connection port is used as the source port for the server-side connection. + - "Note: This parameter is available only when the Use Source IP C(usip) parameter is set to C(yes)." + type: bool + + healthmonitor: + description: + - "Monitor the health of this service. Available settings function as follows:" + - "C(yes) - Send probes to check the health of the service." + - >- + C(no) - Do not send probes to check the health of the service. With the NO option, the appliance shows + the service as UP at all times. + type: bool + + sp: + description: + - "Enable surge protection for the service group." + type: bool + + rtspsessionidremap: + description: + - "Enable RTSP session ID mapping for the service group." + type: bool + + clttimeout: + description: + - "Time, in seconds, after which to terminate an idle client connection." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + svrtimeout: + description: + - "Time, in seconds, after which to terminate an idle server connection." + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + cka: + description: + - "Enable client keep-alive for the service group." + type: bool + + tcpb: + description: + - "Enable TCP buffering for the service group." + type: bool + + cmp: + description: + - "Enable compression for the specified service." + type: bool + + maxbandwidth: + description: + - "Maximum bandwidth, in Kbps, allocated for all the services in the service group." + - "Minimum value = C(0)" + - "Maximum value = C(4294967287)" + + monthreshold: + description: + - >- + Minimum sum of weights of the monitors that are bound to this service. Used to determine whether to + mark a service as UP or DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with all the services in the service group whose state + transitions from UP to DOWN. Do not enable this option for applications that must complete their + transactions. + + tcpprofilename: + description: + - "Name of the TCP profile that contains TCP configuration settings for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - "Name of the HTTP profile that contains HTTP configuration settings for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + comment: + description: + - "Any information about the service group." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging of AppFlow information for the specified service group." + + netprofile: + description: + - "Network profile for the service group." + - "Minimum length = 1" + - "Maximum length = 127" + + autoscale: + choices: + - 'DISABLED' + - 'DNS' + - 'POLICY' + description: + - "Auto scale option for a servicegroup." + + memberport: + description: + - "member port." + + graceful: + description: + - "Wait for all existing connections to the service to terminate before shutting down the service." + type: bool + + servicemembers: + description: + - A list of dictionaries describing each service member of the service group. + suboptions: + ip: + description: + - IP address of the service. Must not overlap with an existing server entity defined by name. + + port: + description: + - Server port number. + - Range C(1) - C(65535) + - "* in CLI is represented as 65535 in NITRO API" + state: + choices: + - 'enabled' + - 'disabled' + description: + - Initial state of the service after binding. + hashid: + description: + - The hash identifier for the service. + - This must be unique for each service. + - This parameter is used by hash based load balancing methods. + - Minimum value = C(1) + + serverid: + description: + - The identifier for the service. + - This is used when the persistency type is set to Custom Server ID. + + servername: + description: + - Name of the server to which to bind the service group. + - The server must already be configured as a named server. + - Minimum length = 1 + + customserverid: + description: + - The identifier for this IP:Port pair. + - Used when the persistency type is set to Custom Server ID. + + weight: + description: + - Weight to assign to the servers in the service group. + - Specifies the capacity of the servers relative to the other servers in the load balancing configuration. + - The higher the weight, the higher the percentage of requests sent to the service. + - Minimum value = C(1) + - Maximum value = C(100) + + monitorbindings: + description: + - A list of monitornames to bind to this service + - Note that the monitors must have already been setup possibly using the M(community.network.netscaler_lb_monitor) module or some other method + suboptions: + monitorname: + description: + - The monitor name to bind to this servicegroup. + weight: + description: + - Weight to assign to the binding between the monitor and servicegroup. + + disabled: + description: + - When set to C(yes) the service group state will be set to DISABLED. + - When set to C(no) the service group state will be set to ENABLED. + - >- + Note that due to limitations of the underlying NITRO API a C(disabled) state change alone + does not cause the module result to report a changed status. + type: bool + default: false + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# The LB Monitors monitor-1 and monitor-2 must already exist +# Service members defined by C(ip) must not redefine an existing server's ip address. +# Service members defined by C(servername) must already exist. + +- name: Setup http service with ip members + delegate_to: localhost + community.network.netscaler_servicegroup: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + servicegroupname: service-group-1 + servicetype: HTTP + servicemembers: + - ip: 10.78.78.78 + port: 80 + weight: 50 + - ip: 10.79.79.79 + port: 80 + weight: 40 + - servername: server-1 + port: 80 + weight: 10 + + monitorbindings: + - monitorname: monitor-1 + weight: 50 + - monitorname: monitor-2 + weight: 50 + +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: ['message 1', 'message 2'] + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' } +''' + +from ansible.module_utils.basic import AnsibleModule +import copy + +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup import servicegroup + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup_servicegroupmember_binding import servicegroup_servicegroupmember_binding + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + + from nssrc.com.citrix.netscaler.nitro.resource.config.basic.servicegroup_lbmonitor_binding import servicegroup_lbmonitor_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_servicegroup_binding import lbmonitor_servicegroup_binding + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + + +def servicegroup_exists(client, module): + log('Checking if service group exists') + count = servicegroup.count_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + log('count is %s' % count) + if count > 0: + return True + else: + return False + + +def servicegroup_identical(client, module, servicegroup_proxy): + log('Checking if service group is identical') + servicegroups = servicegroup.get_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + if servicegroup_proxy.has_equal_attributes(servicegroups[0]): + return True + else: + return False + + +def get_configured_service_members(client, module): + log('get_configured_service_members') + readwrite_attrs = [ + 'servicegroupname', + 'ip', + 'port', + 'state', + 'hashid', + 'serverid', + 'servername', + 'customserverid', + 'weight' + ] + readonly_attrs = [ + 'delay', + 'statechangetimesec', + 'svrstate', + 'tickssincelaststatechange', + 'graceful', + ] + + members = [] + if module.params['servicemembers'] is None: + return members + + for config in module.params['servicemembers']: + # Make a copy to update + config = copy.deepcopy(config) + config['servicegroupname'] = module.params['servicegroupname'] + member_proxy = ConfigProxy( + actual=servicegroup_servicegroupmember_binding(), + client=client, + attribute_values_dict=config, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs + ) + members.append(member_proxy) + return members + + +def get_actual_service_members(client, module): + try: + # count() raises nitro exception instead of returning 0 + count = servicegroup_servicegroupmember_binding.count(client, module.params['servicegroupname']) + if count > 0: + servicegroup_members = servicegroup_servicegroupmember_binding.get(client, module.params['servicegroupname']) + else: + servicegroup_members = [] + except nitro_exception as e: + if e.errorcode == 258: + servicegroup_members = [] + else: + raise + return servicegroup_members + + +def servicemembers_identical(client, module): + log('servicemembers_identical') + + servicegroup_members = get_actual_service_members(client, module) + log('servicemembers %s' % servicegroup_members) + module_servicegroups = get_configured_service_members(client, module) + log('Number of service group members %s' % len(servicegroup_members)) + if len(servicegroup_members) != len(module_servicegroups): + return False + + # Fallthrough to member evaluation + identical_count = 0 + for actual_member in servicegroup_members: + for member in module_servicegroups: + if member.has_equal_attributes(actual_member): + identical_count += 1 + break + if identical_count != len(servicegroup_members): + return False + + # Fallthrough to success + return True + + +def sync_service_members(client, module): + log('sync_service_members') + configured_service_members = get_configured_service_members(client, module) + actual_service_members = get_actual_service_members(client, module) + skip_add = [] + skip_delete = [] + + # Find positions of identical service members + for (configured_index, configured_service) in enumerate(configured_service_members): + for (actual_index, actual_service) in enumerate(actual_service_members): + if configured_service.has_equal_attributes(actual_service): + skip_add.append(configured_index) + skip_delete.append(actual_index) + + # Delete actual that are not identical to any configured + for (actual_index, actual_service) in enumerate(actual_service_members): + # Skip identical + if actual_index in skip_delete: + log('Skipping actual delete at index %s' % actual_index) + continue + + # Fallthrouth to deletion + if all([ + hasattr(actual_service, 'ip'), + actual_service.ip is not None, + hasattr(actual_service, 'servername'), + actual_service.servername is not None, + ]): + actual_service.ip = None + + actual_service.servicegroupname = module.params['servicegroupname'] + servicegroup_servicegroupmember_binding.delete(client, actual_service) + + # Add configured that are not already present in actual + for (configured_index, configured_service) in enumerate(configured_service_members): + + # Skip identical + if configured_index in skip_add: + log('Skipping configured add at index %s' % configured_index) + continue + + # Fallthrough to addition + configured_service.add() + + +def monitor_binding_equal(configured, actual): + if any([configured.monitorname != actual.monitor_name, + configured.servicegroupname != actual.servicegroupname, + configured.weight != float(actual.weight)]): + return False + return True + + +def get_configured_monitor_bindings(client, module): + log('Entering get_configured_monitor_bindings') + bindings = {} + if 'monitorbindings' in module.params and module.params['monitorbindings'] is not None: + for binding in module.params['monitorbindings']: + readwrite_attrs = [ + 'monitorname', + 'servicegroupname', + 'weight', + ] + readonly_attrs = [] + attribute_values_dict = copy.deepcopy(binding) + attribute_values_dict['servicegroupname'] = module.params['servicegroupname'] + binding_proxy = ConfigProxy( + actual=lbmonitor_servicegroup_binding(), + client=client, + attribute_values_dict=attribute_values_dict, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + ) + key = attribute_values_dict['monitorname'] + bindings[key] = binding_proxy + return bindings + + +def get_actual_monitor_bindings(client, module): + log('Entering get_actual_monitor_bindings') + bindings = {} + try: + # count() raises nitro exception instead of returning 0 + count = servicegroup_lbmonitor_binding.count(client, module.params['servicegroupname']) + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + if count == 0: + return bindings + + # Fallthrough to rest of execution + for binding in servicegroup_lbmonitor_binding.get(client, module.params['servicegroupname']): + log('Gettign actual monitor with name %s' % binding.monitor_name) + key = binding.monitor_name + bindings[key] = binding + + return bindings + + +def monitor_bindings_identical(client, module): + log('Entering monitor_bindings_identical') + configured_bindings = get_configured_monitor_bindings(client, module) + actual_bindings = get_actual_monitor_bindings(client, module) + + configured_key_set = set(configured_bindings.keys()) + actual_key_set = set(actual_bindings.keys()) + symmetrical_diff = configured_key_set ^ actual_key_set + for default_monitor in ('tcp-default', 'ping-default'): + if default_monitor in symmetrical_diff: + log('Excluding %s monitor from key comparison' % default_monitor) + symmetrical_diff.remove(default_monitor) + if len(symmetrical_diff) > 0: + return False + + # Compare key to key + for key in configured_key_set: + configured_proxy = configured_bindings[key] + + # Follow nscli convention for missing weight value + if not hasattr(configured_proxy, 'weight'): + configured_proxy.weight = 1 + log('configured_proxy %s' % [configured_proxy.monitorname, configured_proxy.servicegroupname, configured_proxy.weight]) + log('actual_bindings %s' % [actual_bindings[key].monitor_name, actual_bindings[key].servicegroupname, actual_bindings[key].weight]) + if not monitor_binding_equal(configured_proxy, actual_bindings[key]): + return False + + # Fallthrought to success + return True + + +def sync_monitor_bindings(client, module): + log('Entering sync_monitor_bindings') + + actual_bindings = get_actual_monitor_bindings(client, module) + + # Exclude default monitors from deletion + for monitorname in ('tcp-default', 'ping-default'): + if monitorname in actual_bindings: + del actual_bindings[monitorname] + + configured_bindings = get_configured_monitor_bindings(client, module) + + to_remove = list(set(actual_bindings.keys()) - set(configured_bindings.keys())) + to_add = list(set(configured_bindings.keys()) - set(actual_bindings.keys())) + to_modify = list(set(configured_bindings.keys()) & set(actual_bindings.keys())) + + # Delete existing and modifiable bindings + for key in to_remove + to_modify: + binding = actual_bindings[key] + b = lbmonitor_servicegroup_binding() + b.monitorname = binding.monitor_name + b.servicegroupname = module.params['servicegroupname'] + # Cannot remove default monitor bindings + if b.monitorname in ('tcp-default', 'ping-default'): + continue + lbmonitor_servicegroup_binding.delete(client, b) + + # Add new and modified bindings + for key in to_add + to_modify: + binding = configured_bindings[key] + log('Adding %s' % binding.monitorname) + binding.add() + + +def diff(client, module, servicegroup_proxy): + servicegroup_list = servicegroup.get_filtered(client, 'servicegroupname:%s' % module.params['servicegroupname']) + diff_object = servicegroup_proxy.diff_object(servicegroup_list[0]) + return diff_object + + +def do_state_change(client, module, servicegroup_proxy): + if module.params['disabled']: + log('Disabling service') + result = servicegroup.disable(client, servicegroup_proxy.actual) + else: + log('Enabling service') + result = servicegroup.enable(client, servicegroup_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + servicegroupname=dict(type='str'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'FTP', + 'TCP', + 'UDP', + 'SSL', + 'SSL_BRIDGE', + 'SSL_TCP', + 'DTLS', + 'NNTP', + 'RPCSVR', + 'DNS', + 'ADNS', + 'SNMP', + 'RTSP', + 'DHCPRA', + 'ANY', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'DNS_TCP', + 'ADNS_TCP', + 'MYSQL', + 'MSSQL', + 'ORACLE', + 'RADIUS', + 'RADIUSListener', + 'RDP', + 'DIAMETER', + 'SSL_DIAMETER', + 'TFTP', + 'SMPP', + 'PPTP', + 'GRE', + 'SYSLOGTCP', + 'SYSLOGUDP', + 'FIX', + 'SSL_FIX', + ] + ), + cachetype=dict( + type='str', + choices=[ + 'TRANSPARENT', + 'REVERSE', + 'FORWARD', + ] + ), + maxclient=dict(type='float'), + maxreq=dict(type='float'), + cacheable=dict(type='bool'), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + usip=dict(type='bool'), + pathmonitor=dict(type='bool'), + pathmonitorindv=dict(type='bool'), + useproxyport=dict(type='bool'), + healthmonitor=dict(type='bool'), + sp=dict(type='bool'), + rtspsessionidremap=dict(type='bool'), + clttimeout=dict(type='float'), + svrtimeout=dict(type='float'), + cka=dict(type='bool'), + tcpb=dict(type='bool'), + cmp=dict(type='bool'), + maxbandwidth=dict(type='float'), + monthreshold=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + netprofile=dict(type='str'), + autoscale=dict( + type='str', + choices=[ + 'DISABLED', + 'DNS', + 'POLICY', + ] + ), + memberport=dict(type='int'), + graceful=dict(type='bool'), + ) + + hand_inserted_arguments = dict( + servicemembers=dict(type='list'), + monitorbindings=dict(type='list'), + disabled=dict( + type='bool', + default=False, + ), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + argument_spec.update(module_specific_arguments) + argument_spec.update(hand_inserted_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + # Instantiate service group configuration object + readwrite_attrs = [ + 'servicegroupname', + 'servicetype', + 'cachetype', + 'maxclient', + 'maxreq', + 'cacheable', + 'cip', + 'cipheader', + 'usip', + 'pathmonitor', + 'pathmonitorindv', + 'useproxyport', + 'healthmonitor', + 'sp', + 'rtspsessionidremap', + 'clttimeout', + 'svrtimeout', + 'cka', + 'tcpb', + 'cmp', + 'maxbandwidth', + 'monthreshold', + 'downstateflush', + 'tcpprofilename', + 'httpprofilename', + 'comment', + 'appflowlog', + 'netprofile', + 'autoscale', + 'memberport', + 'graceful', + ] + + readonly_attrs = [ + 'numofconnections', + 'serviceconftype', + 'value', + 'svrstate', + 'ip', + 'monstatcode', + 'monstatparam1', + 'monstatparam2', + 'monstatparam3', + 'statechangetimemsec', + 'stateupdatereason', + 'clmonowner', + 'clmonview', + 'groupcount', + 'riseapbrstatsmsgcode2', + 'serviceipstr', + 'servicegroupeffectivestate' + ] + + immutable_attrs = [ + 'servicegroupname', + 'servicetype', + 'cachetype', + 'td', + 'cipheader', + 'state', + 'autoscale', + 'memberport', + 'servername', + 'port', + 'serverid', + 'monitor_name_svc', + 'dup_weight', + 'riseapbrstatsmsgcode', + 'delay', + 'graceful', + 'includemembers', + 'newname', + ] + + transforms = { + 'pathmonitorindv': ['bool_yes_no'], + 'cacheable': ['bool_yes_no'], + 'cka': ['bool_yes_no'], + 'pathmonitor': ['bool_yes_no'], + 'tcpb': ['bool_yes_no'], + 'sp': ['bool_on_off'], + 'usip': ['bool_yes_no'], + 'healthmonitor': ['bool_yes_no'], + 'useproxyport': ['bool_yes_no'], + 'rtspsessionidremap': ['bool_on_off'], + 'graceful': ['bool_yes_no'], + 'cmp': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # Instantiate config proxy + servicegroup_proxy = ConfigProxy( + actual=servicegroup(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + if module.params['state'] == 'present': + log('Applying actions for state present') + if not servicegroup_exists(client, module): + if not module.check_mode: + log('Adding service group') + servicegroup_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not servicegroup_identical(client, module, servicegroup_proxy): + + # Check if we try to change value of immutable attributes + diff_dict = diff(client, module, servicegroup_proxy) + immutables_changed = get_immutables_intersection(servicegroup_proxy, diff_dict.keys()) + if immutables_changed != []: + msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,) + module.fail_json(msg=msg, diff=diff_dict, **module_result) + + if not module.check_mode: + servicegroup_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Check bindings + if not monitor_bindings_identical(client, module): + if not module.check_mode: + sync_monitor_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not servicemembers_identical(client, module): + if not module.check_mode: + sync_service_members(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, servicegroup_proxy) + if res.errorcode != 0: + msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) + module.fail_json(msg=msg, **module_result) + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not servicegroup_exists(client, module): + module.fail_json(msg='Service group is not present', **module_result) + if not servicegroup_identical(client, module, servicegroup_proxy): + module.fail_json( + msg='Service group is not identical to configuration', + diff=diff(client, module, servicegroup_proxy), + **module_result + ) + if not servicemembers_identical(client, module): + module.fail_json(msg='Service group members differ from configuration', **module_result) + if not monitor_bindings_identical(client, module): + module.fail_json(msg='Monitor bindings are not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if servicegroup_exists(client, module): + if not module.check_mode: + servicegroup_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if servicegroup_exists(client, module): + module.fail_json(msg='Service group is present', **module_result) + + except nitro_exception as e: + msg = "nitro exception errorcode=" + str(e.errorcode) + ",message=" + e.message + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_ssl_certkey.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_ssl_certkey.py new file mode 100644 index 00000000..6fe589c4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netscaler/netscaler_ssl_certkey.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: netscaler_ssl_certkey +short_description: Manage ssl certificate keys. +description: + - Manage ssl certificate keys. + + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + certkey: + description: + - >- + Name for the certificate and private-key pair. Must begin with an ASCII alphanumeric or underscore + C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), + colon C(:), at C(@), equals C(=), and hyphen C(-) characters. Cannot be changed after the certificate-key + pair is created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, "my cert" or 'my cert'). + - "Minimum length = 1" + + cert: + description: + - >- + Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key + pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + key: + description: + - >- + Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. + The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + password: + description: + - >- + Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys + in PEM format. + + inform: + choices: + - 'DER' + - 'PEM' + - 'PFX' + description: + - >- + Input format of the certificate and the private-key files. The three formats supported by the + appliance are: + - "PEM - Privacy Enhanced Mail" + - "DER - Distinguished Encoding Rule" + - "PFX - Personal Information Exchange." + + passplain: + description: + - >- + Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM + format. + - "Minimum length = 1" + + expirymonitor: + choices: + - 'enabled' + - 'disabled' + description: + - "Issue an alert when the certificate is about to expire." + + notificationperiod: + description: + - >- + Time, in number of days, before certificate expiration, at which to generate an alert that the + certificate is about to expire. + - "Minimum value = C(10)" + - "Maximum value = C(100)" + + +extends_documentation_fragment: +- community.network.netscaler + +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' + +- name: Setup ssl certkey + delegate_to: localhost + community.network.netscaler_ssl_certkey: + nitro_user: nsroot + nitro_pass: nsroot + nsip: 172.18.0.2 + + certkey: certirificate_1 + cert: server.crt + key: server.key + expirymonitor: enabled + notificationperiod: 30 + inform: PEM + password: False + passplain: somesecret +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: str + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey import sslcertkey + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, + log, loglines, get_immutables_intersection) + + +def key_exists(client, module): + log('Checking if key exists') + log('certkey is %s' % module.params['certkey']) + all_certificates = sslcertkey.get(client) + certkeys = [item.certkey for item in all_certificates] + if module.params['certkey'] in certkeys: + return True + else: + return False + + +def key_identical(client, module, sslcertkey_proxy): + log('Checking if configured key is identical') + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + diff_dict = sslcertkey_proxy.diff_object(sslcertkey_list[0]) + if 'password' in diff_dict: + del diff_dict['password'] + if 'passplain' in diff_dict: + del diff_dict['passplain'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, sslcertkey_proxy): + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + return sslcertkey_proxy.diff_object(sslcertkey_list[0]) + + +def main(): + + module_specific_arguments = dict( + certkey=dict(type='str'), + cert=dict(type='str'), + key=dict(type='str'), + password=dict(type='bool'), + inform=dict( + type='str', + choices=[ + 'DER', + 'PEM', + 'PFX', + ] + ), + passplain=dict( + type='str', + no_log=True, + ), + expirymonitor=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + notificationperiod=dict(type='float'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + 'expirymonitor', + 'notificationperiod', + ] + + readonly_attrs = [ + 'signaturealg', + 'certificatetype', + 'serial', + 'issuer', + 'clientcertnotbefore', + 'clientcertnotafter', + 'daystoexpiration', + 'subject', + 'publickey', + 'publickeysize', + 'version', + 'priority', + 'status', + 'passcrypt', + 'data', + 'servicename', + ] + + immutable_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + ] + + transforms = { + 'expirymonitor': [lambda v: v.upper()], + } + + # Instantiate config proxy + sslcertkey_proxy = ConfigProxy( + actual=sslcertkey(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + if module.params['state'] == 'present': + log('Applying actions for state present') + if not key_exists(client, module): + if not module.check_mode: + log('Adding certificate key') + sslcertkey_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not key_identical(client, module, sslcertkey_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(sslcertkey_proxy, diff_list(client, module, sslcertkey_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, sslcertkey_proxy), + **module_result + ) + + if not module.check_mode: + sslcertkey_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not key_exists(client, module): + module.fail_json(msg='SSL certkey does not exist') + if not key_identical(client, module, sslcertkey_proxy): + module.fail_json(msg='SSL certkey differs from configured', diff=diff_list(client, module, sslcertkey_proxy)) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if key_exists(client, module): + if not module.check_mode: + sslcertkey_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if key_exists(client, module): + module.fail_json(msg='SSL certkey still exists') + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list.py new file mode 100644 index 00000000..8b923544 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_access_list +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete access-list +description: + - This module can be used to create and delete an access list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use 'present' to create access-list and + 'absent' to delete access-list. + required: True + choices: [ "present", "absent"] + pn_name: + description: + - Access List Name. + required: false + type: str + pn_scope: + description: + - 'scope. Available valid values - local or fabric.' + required: false + choices: ['local', 'fabric'] +''' + +EXAMPLES = """ +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "present" + +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "absent" + +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "fabric" + state: "present" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the access-list command. + returned: always + type: list +stderr: + description: set of error responses from the access-list command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the access-list-show command. + If a list with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + list_name = module.params['pn_name'] + + cli += ' access-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if list_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='access-list-create', + absent='access-list-delete', + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_name=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + list_name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + ACC_LIST_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, list_name) + + if command == 'access-list-delete': + if ACC_LIST_EXISTS is False: + module.exit_json( + skipped=True, + msg='access-list with name %s does not exist' % list_name + ) + else: + if command == 'access-list-create': + if ACC_LIST_EXISTS is True: + module.exit_json( + skipped=True, + msg='access list with name %s already exists' % list_name + ) + cli += ' scope %s ' % scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list_ip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list_ip.py new file mode 100644 index 00000000..78371d8b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_access_list_ip.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_access_list_ip +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove access-list-ip +description: + - This modules can be used to add and remove IPs associated with access list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use 'present' to add access-list-ip and + 'absent' to remove access-list-ip. + required: True + choices: ["present", "absent"] + pn_ip: + description: + - IP associated with the access list. + required: False + default: '::' + type: str + pn_name: + description: + - Access List Name. + required: False + type: str +''' + +EXAMPLES = """ +- name: Access list ip functionality + community.network.pn_access_list_ip: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ip: "172.16.3.1" + state: "present" + +- name: Access list ip functionality + community.network.pn_access_list_ip: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ip: "172.16.3.1" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the access-list-ip command. + returned: always + type: list +stderr: + description: set of error responses from the access-list-ip command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the access-list-ip-show command. + If ip exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + ip = module.params['pn_ip'] + clicopy = cli + + cli += ' access-list-show name %s no-show-headers ' % name + out = run_commands(module, cli)[1] + + if name not in out: + module.fail_json( + failed=True, + msg='access-list with name %s does not exist' % name + ) + + cli = clicopy + cli += ' access-list-ip-show name %s format ip no-show-headers' % name + + out = run_commands(module, cli)[1] + out = out.split() + return True if ip in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='access-list-ip-add', + absent='access-list-ip-remove', + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_ip=dict(required=False, type='str', default='::'), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name"]], + ["state", "absent", ["pn_name", "pn_ip"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ip = module.params['pn_ip'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + IP_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'access-list-ip-remove': + if IP_EXISTS is False: + module.exit_json( + skipped=True, + msg='access-list with ip %s does not exist' % ip + ) + if ip: + cli += ' ip ' + ip + else: + if command == 'access-list-ip-add': + if IP_EXISTS is True: + module.exit_json( + skipped=True, + msg='access list with ip %s already exists' % ip + ) + if ip: + cli += ' ip ' + ip + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_service.py new file mode 100644 index 00000000..3ce9ce6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_service.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_service +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify admin-service +description: + - This module is used to modify services on the server-switch. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the admin-service. + required: True + type: str + choices: ['update'] + pn_web: + description: + - Web (HTTP) to enable or disable. + required: False + type: bool + pn_web_ssl: + description: + - Web SSL (HTTPS) to enable or disable. + required: False + type: bool + pn_snmp: + description: + - Simple Network Monitoring Protocol (SNMP) to enable or disable. + required: False + type: bool + pn_web_port: + description: + - Web (HTTP) port to enable or disable. + required: False + type: str + pn_web_ssl_port: + description: + - Web SSL (HTTPS) port to enable or disable. + required: False + type: str + pn_nfs: + description: + - Network File System (NFS) to enable or disable. + required: False + type: bool + pn_ssh: + description: + - Secure Shell to enable or disable. + required: False + type: bool + pn_web_log: + description: + - Web logging to enable or disable. + required: False + type: bool + pn__if: + description: + - administrative service interface. + required: False + type: str + choices: ['mgmt', 'data'] + pn_icmp: + description: + - Internet Message Control Protocol (ICMP) to enable or disable. + required: False + type: bool + pn_net_api: + description: + - Netvisor API to enable or disable APIs. + required: False + type: bool +''' + +EXAMPLES = """ +- name: Admin service functionality + community.network.pn_admin_service: + pn_cliswitch: "sw01" + state: "update" + pn__if: "mgmt" + pn_web: False + pn_icmp: True + +- name: Admin service functionality + community.network.pn_admin_service: + pn_cliswitch: "sw01" + state: "update" + pn_web: False + pn__if: "mgmt" + pn_snmp: True + pn_net_api: True + pn_ssh: True +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-service command. + returned: always + type: list +stderr: + description: set of error responses from the admin-service command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, booleanArgs, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='admin-service-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_web=dict(required=False, type='bool'), + pn_web_ssl=dict(required=False, type='bool'), + pn_snmp=dict(required=False, type='bool'), + pn_web_port=dict(required=False, type='str'), + pn_web_ssl_port=dict(required=False, type='str'), + pn_nfs=dict(required=False, type='bool'), + pn_ssh=dict(required=False, type='bool'), + pn_web_log=dict(required=False, type='bool'), + pn__if=dict(required=False, type='str', choices=['mgmt', 'data']), + pn_icmp=dict(required=False, type='bool'), + pn_net_api=dict(required=False, type='bool'), + ), + required_if=([['state', 'update', ['pn__if']]]), + required_one_of=[['pn_web', 'pn_web_ssl', 'pn_snmp', + 'pn_web_port', 'pn_web_ssl_port', 'pn_nfs', + 'pn_ssh', 'pn_web_log', 'pn_icmp', 'pn_net_api']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + web = module.params['pn_web'] + web_ssl = module.params['pn_web_ssl'] + snmp = module.params['pn_snmp'] + web_port = module.params['pn_web_port'] + web_ssl_port = module.params['pn_web_ssl_port'] + nfs = module.params['pn_nfs'] + ssh = module.params['pn_ssh'] + web_log = module.params['pn_web_log'] + _if = module.params['pn__if'] + icmp = module.params['pn_icmp'] + net_api = module.params['pn_net_api'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'admin-service-modify': + cli += ' %s ' % command + + if _if: + cli += ' if ' + _if + if web_port: + cli += ' web-port ' + web_port + if web_ssl_port: + cli += ' web-ssl-port ' + web_ssl_port + + cli += booleanArgs(web, 'web', 'no-web') + cli += booleanArgs(web_ssl, 'web-ssl', 'no-web-ssl') + cli += booleanArgs(snmp, 'snmp', 'no-snmp') + cli += booleanArgs(nfs, 'nfs', 'no-nfs') + cli += booleanArgs(ssh, 'ssh', 'no-ssh') + cli += booleanArgs(icmp, 'icmp', 'no-icmp') + cli += booleanArgs(net_api, 'net-api', 'no-net-api') + cli += booleanArgs(web_log, 'web-log', 'no-web-log') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_session_timeout.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_session_timeout.py new file mode 100644 index 00000000..0c69c88c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_session_timeout.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_session_timeout +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify admin-session-timeout +description: + - This module can be used to modify admin session timeout. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. + C(update) to modify the admin-session-timeout. + required: True + type: str + choices: ['update'] + pn_timeout: + description: + - Maximum time to wait for user activity before + terminating login session. Minimum should be 60s. + required: False + type: str +''' + +EXAMPLES = """ +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "61s" + +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "1d" + +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "10d20m3h15s" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-session-timeout command. + returned: always + type: list +stderr: + description: set of error responses from the admin-session-timeout command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='admin-session-timeout-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_timeout=dict(required=False, type='str'), + ), + required_together=[['state', 'pn_timeout']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + timeout = module.params['pn_timeout'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + if command == 'admin-session-timeout-modify': + cli += ' %s ' % command + if timeout: + cli += ' timeout ' + timeout + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_syslog.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_syslog.py new file mode 100644 index 00000000..06821213 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_admin_syslog.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_syslog +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete admin-syslog +description: + - This module can be used to create the scope and other parameters of syslog event collection. + - This module can be used to modify parameters of syslog event collection. + - This module can be used to delete the scope and other parameters of syslog event collection. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create admin-syslog and + C(absent) to delete admin-syslog C(update) to modify the admin-syslog. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - Scope of the system log. + required: False + type: str + choices: ['local', 'fabric'] + pn_host: + description: + - Hostname to log system events. + required: False + type: str + pn_port: + description: + - Host port. + required: False + type: str + pn_transport: + description: + - Transport for log events - tcp/tls or udp. + required: False + type: str + choices: ['tcp-tls', 'udp'] + default: 'udp' + pn_message_format: + description: + - message-format for log events - structured or legacy. + required: False + choices: ['structured', 'legacy'] + type: str + pn_name: + description: + - name of the system log. + required: False + type: str +''' + +EXAMPLES = """ +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + pn_scope: "local" + +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "present" + pn_name: "foo" + pn_scope: "local" + pn_host: "166.68.224.46" + pn_message_format: "structured" + +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "update" + pn_name: "foo" + pn_host: "166.68.224.10" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-syslog command. + returned: always + type: list +stderr: + description: set of error responses from the admin-syslog command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the admin-syslog-show command. + If a user with given name exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + + name = module.params['pn_name'] + + cli += ' admin-syslog-show format name no-show-headers' + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='admin-syslog-create', + absent='admin-syslog-delete', + update='admin-syslog-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_host=dict(required=False, type='str'), + pn_port=dict(required=False, type='str'), + pn_transport=dict(required=False, type='str', + choices=['tcp-tls', 'udp'], default='udp'), + pn_message_format=dict(required=False, type='str', + choices=['structured', 'legacy']), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_name', 'pn_host', 'pn_scope']], + ['state', 'absent', ['pn_name']], + ['state', 'update', ['pn_name']] + ), + required_one_of=[['pn_port', 'pn_message_format', + 'pn_host', 'pn_transport', 'pn_scope']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + host = module.params['pn_host'] + port = module.params['pn_port'] + transport = module.params['pn_transport'] + message_format = module.params['pn_message_format'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + SYSLOG_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'admin-syslog-modify': + if SYSLOG_EXISTS is False: + module.fail_json( + failed=True, + msg='admin syslog with name %s does not exist' % name + ) + + if command == 'admin-syslog-delete': + if SYSLOG_EXISTS is False: + module.exit_json( + skipped=True, + msg='admin syslog with name %s does not exist' % name + ) + + if command == 'admin-syslog-create': + if SYSLOG_EXISTS is True: + module.exit_json( + skipped=True, + msg='admin syslog user with name %s already exists' % name + ) + + if command == 'admin-syslog-create': + if scope: + cli += ' scope ' + scope + + if command != 'admin-syslog-delete': + if host: + cli += ' host ' + host + if port: + cli += ' port ' + port + if transport: + cli += ' transport ' + transport + if message_format: + cli += ' message-format ' + message_format + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cluster.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cluster.py new file mode 100644 index 00000000..c0d43802 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cluster.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +""" PN CLI cluster-create/cluster-delete """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_cluster +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete a cluster. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute cluster-create or cluster-delete command. + - A cluster allows two switches to cooperate in high-availability (HA) + deployments. The nodes that form the cluster must be members of the same + fabric. Clusters are typically used in conjunction with a virtual link + aggregation group (VLAG) that allows links physically connected to two + separate switches appear as a single trunk to a third device. The third + device can be a switch,server, or any Ethernet device. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the cli on. + required: False + default: 'local' + state: + description: + - Specify action to perform. Use 'present' to create cluster and 'absent' + to delete cluster. + required: true + choices: ['present', 'absent'] + pn_name: + description: + - Specify the name of the cluster. + required: true + pn_cluster_node1: + description: + - Specify the name of the first switch in the cluster. + - Required for 'cluster-create'. + pn_cluster_node2: + description: + - Specify the name of the second switch in the cluster. + - Required for 'cluster-create'. + pn_validate: + description: + - Validate the inter-switch links and state of switches in the cluster. + type: bool +''' + +EXAMPLES = """ +- name: Create spine cluster + community.network.pn_cluster: + state: 'present' + pn_name: 'spine-cluster' + pn_cluster_node1: 'spine01' + pn_cluster_node2: 'spine02' + pn_validate: True + pn_quiet: True + +- name: Delete spine cluster + community.network.pn_cluster: + state: 'absent' + pn_name: 'spine-cluster' + pn_quiet: True +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the cluster command. + returned: always + type: list +stderr: + description: The set of error responses from the cluster command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +NAME_EXISTS = None +NODE1_EXISTS = None +NODE2_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the cluster-show command. + If a cluster with given name exists, return NAME_EXISTS as True else False. + If the given cluster-node-1 is already a part of another cluster, return + NODE1_EXISTS as True else False. + If the given cluster-node-2 is already a part of another cluster, return + NODE2_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: NAME_EXISTS, NODE1_EXISTS, NODE2_EXISTS + """ + name = module.params['pn_name'] + node1 = module.params['pn_cluster_node1'] + node2 = module.params['pn_cluster_node2'] + + show = cli + ' cluster-show format name,cluster-node-1,cluster-node-2 ' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global NAME_EXISTS, NODE1_EXISTS, NODE2_EXISTS + + if name in out: + NAME_EXISTS = True + else: + NAME_EXISTS = False + if node1 in out: + NODE1_EXISTS = True + else: + NODE2_EXISTS = False + if node2 in out: + NODE2_EXISTS = True + else: + NODE2_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'cluster-create' + if state == 'absent': + command = 'cluster-delete' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_name=dict(required=True, type='str'), + pn_cluster_node1=dict(type='str'), + pn_cluster_node2=dict(type='str'), + pn_validate=dict(type='bool') + ), + required_if=( + ["state", "present", + ["pn_name", "pn_cluster_node1", "pn_cluster_node2"]], + ["state", "absent", ["pn_name"]] + ) + ) + + # Accessing the parameters + state = module.params['state'] + name = module.params['pn_name'] + cluster_node1 = module.params['pn_cluster_node1'] + cluster_node2 = module.params['pn_cluster_node2'] + validate = module.params['pn_validate'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'cluster-create': + + check_cli(module, cli) + + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='Cluster with name %s already exists' % name + ) + if NODE1_EXISTS is True: + module.exit_json( + skipped=True, + msg='Node %s already part of a cluster' % cluster_node1 + ) + if NODE2_EXISTS is True: + module.exit_json( + skipped=True, + msg='Node %s already part of a cluster' % cluster_node2 + ) + + cli += ' %s name %s ' % (command, name) + cli += 'cluster-node-1 %s cluster-node-2 %s ' % (cluster_node1, + cluster_node2) + if validate is True: + cli += ' validate ' + if validate is False: + cli += ' no-validate ' + + if command == 'cluster-delete': + + check_cli(module, cli) + + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='Cluster with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_connection_stats_settings.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_connection_stats_settings.py new file mode 100644 index 00000000..580ad005 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_connection_stats_settings.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_connection_stats_settings +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify connection-stats-settings +description: + - This module can be used to modify the settings for collecting statistical + data about connections. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the + connection-stats-settings. + required: True + type: str + choices: ['update'] + pn_enable: + description: + - Enable or disable collecting connections statistics. + required: False + type: bool + pn_connection_backup_enable: + description: + - Enable backup for connection statistics collection. + required: False + type: bool + pn_client_server_stats_max_memory: + description: + - maximum memory for client server statistics. + required: False + type: str + pn_connection_stats_log_disk_space: + description: + - disk-space allocated for statistics (including rotated log files). + required: False + type: str + pn_client_server_stats_log_enable: + description: + - Enable or disable statistics. + required: False + type: bool + pn_service_stat_max_memory: + description: + - maximum memory allowed for service statistics. + required: False + type: str + pn_connection_stats_log_interval: + description: + - interval to collect statistics. + required: False + type: str + pn_fabric_connection_backup_interval: + description: + - backup interval for fabric connection statistics collection. + required: False + type: str + pn_connection_backup_interval: + description: + - backup interval for connection statistics collection. + required: False + type: str + pn_connection_stats_log_enable: + description: + - enable or disable statistics. + required: False + type: bool + pn_fabric_connection_max_memory: + description: + - maximum memory allowed for fabric connection statistics. + required: False + type: str + pn_fabric_connection_backup_enable: + description: + - enable backup for fabric connection statistics collection. + required: False + type: bool + pn_client_server_stats_log_disk_space: + description: + - disk-space allocated for statistics (including rotated log files). + required: False + type: str + pn_connection_max_memory: + description: + - maximum memory allowed for connection statistics. + required: False + type: str + pn_connection_stats_max_memory: + description: + - maximum memory allowed for connection statistics. + required: False + type: str + pn_client_server_stats_log_interval: + description: + - interval to collect statistics. + required: False + type: str +''' + +EXAMPLES = """ +- name: "Modify connection stats settings" + community.network.pn_connection_stats_settings: + pn_cliswitch: "sw01" + state: "update" + pn_enable: False + pn_fabric_connection_max_memory: "1000" + +- name: "Modify connection stats settings" + community.network.pn_connection_stats_settings: + pn_cliswitch: "sw01" + state: "update" + pn_enable: True +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the connection-stats-settings command. + returned: always + type: list +stderr: + description: set of error responses from the connection-stats-settings command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='connection-stats-settings-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_enable=dict(required=False, type='bool'), + pn_connection_backup_enable=dict(required=False, type='bool'), + pn_client_server_stats_max_memory=dict(required=False, type='str'), + pn_connection_stats_log_disk_space=dict(required=False, + type='str'), + pn_client_server_stats_log_enable=dict(required=False, + type='bool'), + pn_service_stat_max_memory=dict(required=False, type='str'), + pn_connection_stats_log_interval=dict(required=False, type='str'), + pn_fabric_connection_backup_interval=dict(required=False, + type='str'), + pn_connection_backup_interval=dict(required=False, type='str'), + pn_connection_stats_log_enable=dict(required=False, type='bool'), + pn_fabric_connection_max_memory=dict(required=False, type='str'), + pn_fabric_connection_backup_enable=dict(required=False, + type='bool'), + pn_client_server_stats_log_disk_space=dict(required=False, + type='str'), + pn_connection_max_memory=dict(required=False, type='str'), + pn_connection_stats_max_memory=dict(required=False, type='str'), + pn_client_server_stats_log_interval=dict(required=False, + type='str'), + ), + required_one_of=[['pn_enable', 'pn_connection_backup_enable', + 'pn_client_server_stats_max_memory', + 'pn_connection_stats_log_disk_space', + 'pn_client_server_stats_log_enable', + 'pn_service_stat_max_memory', + 'pn_connection_stats_log_interval', + 'pn_connection_backup_interval', + 'pn_connection_stats_log_enable', + 'pn_fabric_connection_max_memory', + 'pn_fabric_connection_backup_enable', + 'pn_client_server_stats_log_disk_space', + 'pn_connection_max_memory', + 'pn_connection_stats_max_memory', + 'pn_client_server_stats_log_interval']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + enable = module.params['pn_enable'] + connection_backup_enable = module.params['pn_connection_backup_enable'] + client_server_stats_max_memory = module.params['pn_client_server_stats_max_memory'] + connection_stats_log_disk_space = module.params['pn_connection_stats_log_disk_space'] + client_server_stats_log_enable = module.params['pn_client_server_stats_log_enable'] + service_stat_max_memory = module.params['pn_service_stat_max_memory'] + connection_stats_log_interval = module.params['pn_connection_stats_log_interval'] + fabric_connection_backup_interval = module.params['pn_fabric_connection_backup_interval'] + connection_backup_interval = module.params['pn_connection_backup_interval'] + connection_stats_log_enable = module.params['pn_connection_stats_log_enable'] + fabric_connection_max_memory = module.params['pn_fabric_connection_max_memory'] + fabric_connection_backup_enable = module.params['pn_fabric_connection_backup_enable'] + client_server_stats_log_disk_space = module.params['pn_client_server_stats_log_disk_space'] + connection_max_memory = module.params['pn_connection_max_memory'] + connection_stats_max_memory = module.params['pn_connection_stats_max_memory'] + client_server_stats_log_interval = module.params['pn_client_server_stats_log_interval'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'connection-stats-settings-modify': + cli += ' %s ' % command + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(connection_backup_enable, 'connection-backup-enable', 'connection-backup-disable') + cli += booleanArgs(client_server_stats_log_enable, 'client-server-stats-log-enable', 'client-server-stats-log-disable') + cli += booleanArgs(connection_stats_log_enable, 'connection-stats-log-enable', 'connection-stats-log-disable') + cli += booleanArgs(fabric_connection_backup_enable, 'fabric-connection-backup-enable', 'fabric-connection-backup-disable') + + if client_server_stats_max_memory: + cli += ' client-server-stats-max-memory ' + client_server_stats_max_memory + if connection_stats_log_disk_space: + cli += ' connection-stats-log-disk-space ' + connection_stats_log_disk_space + if service_stat_max_memory: + cli += ' service-stat-max-memory ' + service_stat_max_memory + if connection_stats_log_interval: + cli += ' connection-stats-log-interval ' + connection_stats_log_interval + if fabric_connection_backup_interval: + cli += ' fabric-connection-backup-interval ' + fabric_connection_backup_interval + if connection_backup_interval: + cli += ' connection-backup-interval ' + connection_backup_interval + if fabric_connection_max_memory: + cli += ' fabric-connection-max-memory ' + fabric_connection_max_memory + if client_server_stats_log_disk_space: + cli += ' client-server-stats-log-disk-space ' + client_server_stats_log_disk_space + if connection_max_memory: + cli += ' connection-max-memory ' + connection_max_memory + if connection_stats_max_memory: + cli += ' connection-stats-max-memory ' + connection_stats_max_memory + if client_server_stats_log_interval: + cli += ' client-server-stats-log-interval ' + client_server_stats_log_interval + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_class.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_class.py new file mode 100644 index 00000000..3ef89c81 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_class.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_cpu_class +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete cpu-class +description: + - This module can be used to create, modify and delete CPU class information. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create cpu-class and + C(absent) to delete cpu-class C(update) to modify the cpu-class. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - scope for CPU class. + required: false + choices: ['local', 'fabric'] + pn_hog_protect: + description: + - enable host-based hog protection. + required: False + type: str + choices: ['disable', 'enable', 'enable-and-drop'] + pn_rate_limit: + description: + - rate-limit for CPU class. + required: False + type: str + pn_name: + description: + - name for the CPU class. + required: False + type: str +''' + +EXAMPLES = """ +- name: Create cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'present' + pn_name: 'icmp' + pn_rate_limit: '1000' + pn_scope: 'local' + +- name: Delete cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'icmp' + + +- name: Modify cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'icmp' + pn_rate_limit: '2000' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the cpu-class command. + returned: always + type: list +stderr: + description: set of error responses from the cpu-class command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the cpu-class-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + clicopy = cli + + cli += ' system-settings-show format cpu-class-enable no-show-headers' + out = run_commands(module, cli)[1] + out = out.split() + + if 'on' not in out: + module.fail_json( + failed=True, + msg='Enable CPU class before creating or deleting' + ) + + cli = clicopy + cli += ' cpu-class-show format name no-show-headers' + out = run_commands(module, cli)[1] + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='cpu-class-create', + absent='cpu-class-delete', + update='cpu-class-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_hog_protect=dict(required=False, type='str', + choices=['disable', 'enable', + 'enable-and-drop']), + pn_rate_limit=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_name', 'pn_scope', 'pn_rate_limit']], + ['state', 'absent', ['pn_name']], + ['state', 'update', ['pn_name']], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + hog_protect = module.params['pn_hog_protect'] + rate_limit = module.params['pn_rate_limit'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'cpu-class-modify': + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='cpu class with name %s does not exist' % name + ) + + if command == 'cpu-class-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='cpu class with name %s does not exist' % name + ) + + if command == 'cpu-class-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='cpu class with name %s already exists' % name + ) + if scope: + cli += ' scope %s ' % scope + + if command != 'cpu-class-delete': + if hog_protect: + cli += ' hog-protect %s ' % hog_protect + if rate_limit: + cli += ' rate-limit %s ' % rate_limit + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_mgmt_class.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_mgmt_class.py new file mode 100644 index 00000000..cf00bc3b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_cpu_mgmt_class.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_cpu_mgmt_class +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify cpu-mgmt-class +description: + - This module can we used to update mgmt port ingress policers. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to modify cpu-mgmt-class. + type: str + required: true + choices: ['update'] + pn_burst_size: + description: + - ingress traffic burst size (bytes) or default. + required: false + type: str + pn_name: + description: + - mgmt port ingress traffic class. + type: str + required: false + choices: ['arp', 'icmp', 'ssh', 'snmp', 'fabric', 'bcast', 'nfs', + 'web', 'web-ssl', 'net-api'] + pn_rate_limit: + description: + - ingress rate limit on mgmt port(bps) or unlimited. + type: str + required: false +''' + +EXAMPLES = """ +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "icmp" + pn_rate_limit: "10000" + pn_burst_size: "14000" + +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "snmp" + pn_burst_size: "8000" + pn_rate_limit: "100000" + +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "web" + pn_rate_limit: "10000" + pn_burst_size: "1000" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the cpu-mgmt-class command. + returned: always + type: list +stderr: + description: set of error responses from the cpu-mgmt-class command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='cpu-mgmt-class-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', choices=state_map.keys()), + pn_burst_size=dict(required=False, type='str'), + pn_name=dict(required=False, type='str', + choices=['arp', 'icmp', 'ssh', 'snmp', + 'fabric', 'bcast', 'nfs', 'web', + 'web-ssl', 'net-api']), + pn_rate_limit=dict(required=False, type='str'), + ), + required_if=([['state', 'update', ['pn_name', 'pn_burst_size', 'pn_rate_limit']]]), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + burst_size = module.params['pn_burst_size'] + name = module.params['pn_name'] + rate_limit = module.params['pn_rate_limit'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'cpu-mgmt-class-modify': + cli += ' %s name %s ' % (command, name) + cli += ' burst-size %s rate-limit %s' % (burst_size, rate_limit) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dhcp_filter.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dhcp_filter.py new file mode 100644 index 00000000..d876d77c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dhcp_filter.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dhcp_filter +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete dhcp-filter +description: + - This module can be used to create, delete and modify a DHCP filter config. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create dhcp-filter and + C(absent) to delete dhcp-filter C(update) to modify the dhcp-filter. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_trusted_ports: + description: + - trusted ports of dhcp config. + required: False + type: str + pn_name: + description: + - name of the DHCP filter. + required: false + type: str +''' + +EXAMPLES = """ +- name: Dhcp filter create + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "present" + pn_trusted_ports: "1" + +- name: Dhcp filter delete + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" + pn_trusted_ports: "1" + +- name: Dhcp filter modify + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "update" + pn_trusted_ports: "1,2" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dhcp-filter command. + returned: always + type: list +stderr: + description: set of error responses from the dhcp-filter command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dhcp-filter-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + user_name = module.params['pn_name'] + + cli += ' dhcp-filter-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if user_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='dhcp-filter-create', + absent='dhcp-filter-delete', + update='dhcp-filter-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_trusted_ports=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + ), + required_if=[ + ["state", "present", ["pn_name", "pn_trusted_ports"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name", "pn_trusted_ports"]] + ] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + trusted_ports = module.params['pn_trusted_ports'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'dhcp-filter-modify': + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='dhcp-filter with name %s does not exist' % name + ) + if command == 'dhcp-filter-delete': + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='dhcp-filter with name %s does not exist' % name + ) + if command == 'dhcp-filter-create': + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='dhcp-filter with name %s already exists' % name + ) + if command != 'dhcp-filter-delete': + if trusted_ports: + cli += ' trusted-ports ' + trusted_ports + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map.py new file mode 100644 index 00000000..d5753fe6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dscp_map +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete dscp-map +description: + - This module can be used to create a DSCP priority mapping table. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create dscp-map and + C(absent) to delete. + required: True + type: str + choices: ["present", "absent"] + pn_name: + description: + - Name for the DSCP map. + required: False + type: str + pn_scope: + description: + - Scope for dscp map. + required: False + choices: ["local", "fabric"] +''' + +EXAMPLES = """ +- name: Dscp map create + community.network.pn_dscp_map: + pn_cliswitch: "sw01" + state: "present" + pn_name: "foo" + pn_scope: "local" + +- name: Dscp map delete + community.network.pn_dscp_map: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dscp-map command. + returned: always + type: list +stderr: + description: set of error responses from the dscp-map command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' dscp-map-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='dscp-map-create', + absent='dscp-map-delete' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_name=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'dscp-map-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='dscp map with name %s does not exist' % name + ) + else: + if command == 'dscp-map-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='dscp map with name %s already exists' % name + ) + + if scope: + cli += ' scope ' + scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map_pri_map.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map_pri_map.py new file mode 100644 index 00000000..a87b64ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_dscp_map_pri_map.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dscp_map_pri_map +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify dscp-map-pri-map +description: + - This module can be used to update priority mappings in tables. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the dscp-map-pri-map. + required: True + type: str + choices: ['update'] + pn_pri: + description: + - CoS priority. + required: False + type: str + pn_name: + description: + - Name for the DSCP map. + required: False + type: str + pn_dsmap: + description: + - DSCP value(s). + required: False + type: str +''' + +EXAMPLES = """ +- name: Dscp map pri map modify + community.network.pn_dscp_map_pri_map: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_pri: '0' + pn_dsmap: '40' + +- name: Dscp map pri map modify + community.network.pn_dscp_map_pri_map: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_pri: '1' + pn_dsmap: '8,10,12,14' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dscp-map-pri-map command. + returned: always + type: list +stderr: + description: set of error responses from the dscp-map-pri-map command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' dscp-map-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='dscp-map-pri-map-modify' + ) + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_pri=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + pn_dsmap=dict(required=False, type='str'), + ), + required_if=( + ['state', 'update', ['pn_name', 'pn_pri']], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + pri = module.params['pn_pri'] + name = module.params['pn_name'] + dsmap = module.params['pn_dsmap'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + if command == 'dscp-map-pri-map-modify': + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='Create dscp map with name %s before updating' % name + ) + cli += ' %s ' % command + if pri: + cli += ' pri ' + pri + if name: + cli += ' name ' + name + if dsmap: + cli += ' dsmap ' + dsmap + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_fabric_local.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_fabric_local.py new file mode 100644 index 00000000..e2e3e863 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_fabric_local.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_fabric_local +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify fabric-local +description: + - This module can be used to modify fabric local information. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: true + type: str + state: + description: + - State the action to perform. Use C(update) to modify the fabric-local. + required: false + type: str + choices: ['update'] + default: 'update' + pn_fabric_network: + description: + - fabric administration network. + required: false + choices: ['in-band', 'mgmt', 'vmgmt'] + default: 'mgmt' + pn_vlan: + description: + - VLAN assigned to fabric. + required: false + type: str + pn_control_network: + description: + - control plane network. + required: false + choices: ['in-band', 'mgmt', 'vmgmt'] + pn_fabric_advertisement_network: + description: + - network to send fabric advertisements on. + required: false + choices: ['inband-mgmt', 'inband-only', 'inband-vmgmt', 'mgmt-only'] +''' + +EXAMPLES = """ +- name: Fabric local module + community.network.pn_fabric_local: + pn_cliswitch: "sw01" + pn_vlan: "500" + +- name: Fabric local module + community.network.pn_fabric_local: + pn_cliswitch: "sw01" + pn_fabric_advertisement_network: "mgmt-only" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the fabric-local command. + returned: always + type: list +stderr: + description: set of error responses from the fabric-local command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='fabric-local-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=True, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='update'), + pn_fabric_network=dict(required=False, type='str', + choices=['mgmt', 'in-band', 'vmgmt'], default='mgmt'), + pn_vlan=dict(required=False, type='str'), + pn_control_network=dict(required=False, type='str', + choices=['in-band', 'mgmt', 'vmgmt']), + pn_fabric_advertisement_network=dict(required=False, type='str', + choices=['inband-mgmt', 'inband-only', 'inband-vmgmt', 'mgmt-only']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[['pn_fabric_network', 'pn_vlan', + 'pn_control_network', + 'pn_fabric_advertisement_network']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + fabric_network = module.params['pn_fabric_network'] + vlan = module.params['pn_vlan'] + control_network = module.params['pn_control_network'] + fabric_adv_network = module.params['pn_fabric_advertisement_network'] + + command = state_map[state] + + if vlan: + if int(vlan) < 1 or int(vlan) > 4092: + module.fail_json( + failed=True, + msg='Valid vlan range is 1 to 4092' + ) + cli = pn_cli(module, cliswitch) + cli += ' vlan-show format id no-show-headers' + out = run_commands(module, cli)[1].split() + + if vlan in out and vlan != '1': + module.fail_json( + failed=True, + msg='vlan %s is already in used. Specify unused vlan' % vlan + ) + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'fabric-local-modify': + cli += ' %s ' % command + + if fabric_network: + cli += ' fabric-network ' + fabric_network + + if vlan: + cli += ' vlan ' + vlan + + if control_network: + cli += ' control-network ' + control_network + + if fabric_adv_network: + cli += ' fabric-advertisement-network ' + fabric_adv_network + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_igmp_snooping.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_igmp_snooping.py new file mode 100644 index 00000000..a2a1ce74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_igmp_snooping.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_igmp_snooping +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify igmp-snooping +description: + - This module can be used to modify Internet Group Management Protocol (IGMP) snooping. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the igmp-snooping. + required: True + type: str + choices: ['update'] + pn_enable: + description: + - enable or disable IGMP snooping. + required: False + type: bool + pn_query_interval: + description: + - IGMP query interval in seconds. + required: False + type: str + pn_igmpv2_vlans: + description: + - VLANs on which to use IGMPv2 protocol. + required: False + type: str + pn_igmpv3_vlans: + description: + - VLANs on which to use IGMPv3 protocol. + required: False + type: str + pn_enable_vlans: + description: + - enable per VLAN IGMP snooping. + required: False + type: str + pn_vxlan: + description: + - enable or disable IGMP snooping on vxlans. + required: False + type: bool + pn_query_max_response_time: + description: + - maximum response time, in seconds, advertised in IGMP queries. + required: False + type: str + pn_scope: + description: + - IGMP snooping scope - fabric or local. + required: False + choices: ['local', 'fabric'] + pn_no_snoop_linklocal_vlans: + description: + - Remove snooping of link-local groups(224.0.0.0/24) on these vlans. + required: False + type: str + pn_snoop_linklocal_vlans: + description: + - Allow snooping of link-local groups(224.0.0.0/24) on these vlans. + required: False + type: str +''' + +EXAMPLES = """ +- name: 'Modify IGMP Snooping' + community.network.pn_igmp_snooping: + pn_cliswitch: 'sw01' + state: 'update' + pn_vxlan: True + pn_enable_vlans: '1-399,401-4092' + pn_no_snoop_linklocal_vlans: 'none' + pn_igmpv3_vlans: '1-399,401-4092' + +- name: 'Modify IGMP Snooping' + community.network.pn_igmp_snooping: + pn_cliswitch: 'sw01' + state: 'update' + pn_vxlan: False + pn_enable_vlans: '1-399' + pn_no_snoop_linklocal_vlans: 'none' + pn_igmpv3_vlans: '1-399' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the igmp-snooping command. + returned: always + type: list +stderr: + description: set of error responses from the igmp-snooping command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='igmp-snooping-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_enable=dict(required=False, type='bool'), + pn_query_interval=dict(required=False, type='str'), + pn_igmpv2_vlans=dict(required=False, type='str'), + pn_igmpv3_vlans=dict(required=False, type='str'), + pn_enable_vlans=dict(required=False, type='str'), + pn_vxlan=dict(required=False, type='bool'), + pn_query_max_response_time=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_no_snoop_linklocal_vlans=dict(required=False, type='str'), + pn_snoop_linklocal_vlans=dict(required=False, type='str'), + ), + required_one_of=[['pn_enable', 'pn_query_interval', + 'pn_igmpv2_vlans', + 'pn_igmpv3_vlans', + 'pn_enable_vlans', + 'pn_vxlan', + 'pn_query_max_response_time', + 'pn_scope', + 'pn_no_snoop_linklocal_vlans', + 'pn_snoop_linklocal_vlans']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + enable = module.params['pn_enable'] + query_interval = module.params['pn_query_interval'] + igmpv2_vlans = module.params['pn_igmpv2_vlans'] + igmpv3_vlans = module.params['pn_igmpv3_vlans'] + enable_vlans = module.params['pn_enable_vlans'] + vxlan = module.params['pn_vxlan'] + query_max_response_time = module.params['pn_query_max_response_time'] + scope = module.params['pn_scope'] + no_snoop_linklocal_vlans = module.params['pn_no_snoop_linklocal_vlans'] + snoop_linklocal_vlans = module.params['pn_snoop_linklocal_vlans'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'igmp-snooping-modify': + cli += ' %s ' % command + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(vxlan, 'vxlan', 'no-vxlan') + + if query_interval: + cli += ' query-interval ' + query_interval + if igmpv2_vlans: + cli += ' igmpv2-vlans ' + igmpv2_vlans + if igmpv3_vlans: + cli += ' igmpv3-vlans ' + igmpv3_vlans + if enable_vlans: + cli += ' enable-vlans ' + enable_vlans + if query_max_response_time: + cli += ' query-max-response-time ' + query_max_response_time + if scope: + cli += ' scope ' + scope + if no_snoop_linklocal_vlans: + cli += ' no-snoop-linklocal-vlans ' + no_snoop_linklocal_vlans + if snoop_linklocal_vlans: + cli += ' snoop-linklocal-vlans ' + snoop_linklocal_vlans + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard.py new file mode 100644 index 00000000..6100480c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete ipv6security-raguard +description: + - This module can be used to add ipv6 RA Guard Policy, Update ipv6 RA guard Policy and Remove ipv6 RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard configuration command. + required: false + choices: ['present', 'update', 'absent'] + type: str + default: 'present' + pn_device: + description: + - RA Guard Device. host or router. + required: false + choices: ['host', 'router'] + type: str + pn_access_list: + description: + - RA Guard Access List of Source IPs. + required: false + type: str + pn_prefix_list: + description: + - RA Guard Prefix List. + required: false + type: str + pn_router_priority: + description: + - RA Guard Router Priority. + required: false + type: str + choices: ['low', 'medium', 'high'] + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security ragurad create + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_device: "host" + +- name: Ipv6 security ragurad create + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo1" + pn_device: "host" + pn_access_list: "sample" + pn_prefix_list: "sample" + pn_router_priority: "low" + +- name: Ipv6 security ragurad modify + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo1" + pn_device: "router" + pn_router_priority: "medium" + state: "update" + +- name: Ipv6 security ragurad delete + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module): + """ + This method checks for idempotency using the ipv6security-raguard-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli = 'ipv6security-raguard-show format name parsable-delim ,' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def check_list(module, list_name, command): + """ + This method checks for idempotency using provided command. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + + cli = '%s format name no-show-headers' % command + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + if list_name not in out: + module.fail_json( + failed=True, + msg='%s name %s does not exists' % (command, list_name) + ) + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-create', + absent='ipv6security-raguard-delete', + update='ipv6security-raguard-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_device=dict(required=False, type='str', choices=['host', 'router']), + pn_access_list=dict(required=False, type='str'), + pn_prefix_list=dict(required=False, type='str'), + pn_router_priority=dict(required=False, type='str', choices=['low', 'medium', 'high']), + pn_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ['pn_device']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + device = module.params['pn_device'] + access_list = module.params['pn_access_list'] + prefix_list = module.params['pn_prefix_list'] + router_priority = module.params['pn_router_priority'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module) + + if command == 'ipv6security-raguard-modify': + if not device and not access_list and not prefix_list and not router_priority: + module.fail_json( + failed=True, + msg='required one of device, access_list, prefix_list or router_priority' + ) + + if command == 'ipv6security-raguard-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='ipv6 security raguard with name %s already exists' % name + ) + + if command != 'ipv6security-raguard-create': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='ipv6 security raguard with name %s does not exist' % name + ) + + cli += ' %s name %s ' % (command, name) + if command != 'ipv6security-raguard-delete': + if device == 'router': + cli += ' device ' + device + if access_list: + check_list(module, access_list, 'access-list-show') + cli += ' access-list ' + access_list + if prefix_list: + check_list(module, prefix_list, 'prefix-list-show') + cli += ' prefix-list ' + prefix_list + if router_priority: + cli += ' router-priority ' + router_priority + if device == 'host': + cli += ' device ' + device + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_port.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_port.py new file mode 100644 index 00000000..10b467d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_port.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard_port +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove ipv6security-raguard-port +description: + - This module can be used to add ports to RA Guard Policy and remove ports to RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard-port configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str + pn_ports: + description: + - Ports attached to RA Guard Policy. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security raguard port add + community.network.pn_ipv6security_raguard_port: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ports: "1" + +- name: Ipv6 security raguard port remove + community.network.pn_ipv6security_raguard_port: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" + pn_ports: "1" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard-port command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard-port command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module): + """ + This method checks for idempotency using the ipv6security-raguard-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli = 'ipv6security-raguard-show format name parsable-delim ,' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-port-add', + absent='ipv6security-raguard-port-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_name=dict(required=True, type='str'), + pn_ports=dict(required=True, type='str') + ) + + module = AnsibleModule( + argument_spec=argument_spec + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + ports = module.params['pn_ports'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module) + + if command: + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='ipv6 security raguard with name %s does not exist to add ports' % name + ) + + cli += ' %s name %s ports %s' % (command, name, ports) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_vlan.py new file mode 100644 index 00000000..fe87d52f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ipv6security_raguard_vlan.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard_vlan +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove ipv6security-raguard-vlan +description: + - This module can be used to Add vlans to RA Guard Policy and Remove vlans to RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard-vlan configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_vlans: + description: + - Vlans attached to RA Guard Policy. + required: true + type: str + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security raguard vlan add + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100-105" + +- name: Ipv6 security raguard vlan add + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100" + +- name: Ipv6 security raguard vlan remove + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100-105" + state: 'absent' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard-vlan command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard-vlan command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the ipv6-security-reguard command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + vlans = module.params['pn_vlans'] + show = cli + + cli += ' ipv6security-raguard-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + NAME_EXISTS = True if name in out else False + + show += ' vlan-show format id no-show-headers' + out = run_commands(module, show)[1] + if out: + out = out.split() + + if vlans and '-' in vlans: + vlan_list = list() + vlans = vlans.strip().split('-') + for vlan in range(int(vlans[0]), int(vlans[1]) + 1): + vlan_list.append(str(vlan)) + + for vlan in vlan_list: + if vlan not in out: + module.fail_json( + failed=True, + msg='vlan id %s does not exist. Make sure you create vlan before adding it' % vlan + ) + else: + if vlans not in out: + module.fail_json( + failed=True, + msg='vlan id %s does not exist. Make sure you create vlan before adding it' % vlans + ) + + return NAME_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-vlan-add', + absent='ipv6security-raguard-vlan-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_vlans=dict(required=True, type='str'), + pn_name=dict(required=True, type='str'), + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + vlans = module.params['pn_vlans'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command: + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='ipv6security raguard with name %s does not exist' % name + ) + if vlans: + cli += ' vlans ' + vlans + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_log_audit_exception.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_log_audit_exception.py new file mode 100644 index 00000000..cae5b15e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_log_audit_exception.py @@ -0,0 +1,198 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/license/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_log_audit_exception +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete an audit exception +description: + - This module can be used to create an audit exception and delete an audit exception. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + pn_audit_type: + description: + - Specify the type of audit exception. + required: false + type: str + choices: ['cli', 'shell', 'vtysh'] + state: + description: + - State the action to perform. Use 'present' to create audit-exception and + 'absent' to delete audit-exception. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_pattern: + description: + - Specify a regular expression to match exceptions. + required: false + type: str + pn_scope: + description: + - scope - local or fabric. + required: false + type: str + choices: ['local', 'fabric'] + pn_access: + description: + - Specify the access type to match exceptions. + required: true + type: str + choices: ['any', 'read-only', 'read-write'] +''' + +EXAMPLES = """ +- name: Create a log-audit-exception + community.network.pn_log_audit_exception: + pn_audit_type: "cli" + pn_pattern: "test" + state: "present" + pn_access: "any" + pn_scope: "local" + +- name: Delete a log-audit-exception + community.network.pn_log_audit_exception: + pn_audit_type: "shell" + pn_pattern: "test" + state: "absent" + pn_access: "any" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the pn_log_audit_exceptions command. + returned: always + type: list +stderr: + description: set of error responses from the log_audit_exceptions command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the log-audit-exception command. + If a list with given name exists, return exists as True else False. + :param module: The Ansible module to fetch input parameters. + :return Booleans: True or False. + """ + state = module.params['state'] + audit_type = module.params['pn_audit_type'] + pattern = module.params['pn_pattern'] + access = module.params['pn_access'] + scope = module.params['pn_scope'] + cli += ' log-audit-exception-show' + cli += ' no-show-headers format ' + cli += ' type,pattern,access,scope parsable-delim DELIM' + + stdout = run_commands(module, cli)[1] + + if stdout: + linelist = stdout.strip().split('\n') + for line in linelist: + wordlist = line.split('DELIM') + count = 0 + + if wordlist[0] == audit_type: + count += 1 + if wordlist[1] == pattern: + count += 1 + if wordlist[2] == access: + count += 1 + if state == 'present' and wordlist[3] == scope: + count += 1 + elif state == 'absent' and count == 3: + return True + if state == 'present' and count == 4: + return True + + return False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='log-audit-exception-create', + absent='log-audit-exception-delete', + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + pn_pattern=dict(required=True, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_access=dict(required=True, type='str', choices=['any', 'read-only', 'read-write']), + pn_audit_type=dict(required=True, type='str', choices=['cli', 'shell', 'vtysh']), + pn_scope=dict(required=False, type='str', choices=['local', 'fabric']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_scope"]], + ), + ) + + # Accessing the arguments + + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + access = module.params['pn_access'] + audit_type = module.params['pn_audit_type'] + pattern = module.params['pn_pattern'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + audit_log_exists = check_cli(module, cli) + + cli += ' %s %s pattern %s %s' % (command, audit_type, pattern, access) + + if state == 'absent': + if audit_log_exists is False: + module.exit_json( + skipped=True, + msg='This audit log exception entry does not exist' + ) + run_cli(module, cli, state_map) + + elif state == 'present': + if audit_log_exists is True: + module.exit_json( + skipped=True, + msg='This audit log exception entry already exists' + ) + cli += ' scope %s ' % scope + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospf.py new file mode 100644 index 00000000..4ee2567e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospf.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +""" PN-CLI vrouter-ospf-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_ospf +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove ospf protocol to a vRouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-ospf-add, vrouter-ospf-remove command. + - This command adds/removes Open Shortest Path First(OSPF) routing + protocol to a virtual router(vRouter) service. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + default: 'local' + state: + description: + - Assert the state of the ospf. Use 'present' to add ospf + and 'absent' to remove ospf. + required: True + default: present + choices: ['present', 'absent'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: True + pn_network_ip: + description: + - Specify the network IP (IPv4 or IPv6) address. + required: True + pn_ospf_area: + description: + - Stub area number for the configuration. Required for vrouter-ospf-add. +''' + +EXAMPLES = """ +- name: "Add OSPF to vrouter" + community.network.pn_ospf: + state: present + pn_vrouter_name: name-string + pn_network_ip: 192.168.11.2/24 + pn_ospf_area: 1.0.0.0 + +- name: "Remove OSPF from vrouter" + community.network.pn_ospf: + state: absent + pn_vrouter_name: name-string +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the ospf command. + returned: always + type: list +stderr: + description: The set of error responses from the ospf command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +NETWORK_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-ospf-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If an OSPF network with the given ip exists on the given vRouter, + return NETWORK_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, NETWORK_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + network_ip = module.params['pn_network_ip'] + # Global flags + global VROUTER_EXISTS, NETWORK_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for OSPF networks + show = cli + ' vrouter-ospf-show vrouter-name %s ' % vrouter_name + show += 'format network no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if network_ip in out: + NETWORK_EXISTS = True + else: + NETWORK_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + cmd = shlex.split(cli) + + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-ospf-add' + if state == 'absent': + command = 'vrouter-ospf-remove' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(type='str', default='present', choices=['present', + 'absent']), + pn_vrouter_name=dict(required=True, type='str'), + pn_network_ip=dict(required=True, type='str'), + pn_ospf_area=dict(type='str') + ), + required_if=( + ['state', 'present', + ['pn_network_ip', 'pn_ospf_area']], + ['state', 'absent', ['pn_network_ip']] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + network_ip = module.params['pn_network_ip'] + ospf_area = module.params['pn_ospf_area'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + check_cli(module, cli) + + if state == 'present': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg=('OSPF with network ip %s already exists on %s' + % (network_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s network %s ospf-area %s' + % (command, vrouter_name, network_ip, ospf_area)) + + if state == 'absent': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg=('OSPF with network ip %s already exists on %s' + % (network_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s network %s' + % (command, vrouter_name, network_ip)) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospfarea.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospfarea.py new file mode 100644 index 00000000..305b9649 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_ospfarea.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +""" PN-CLI vrouter-ospf-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_ospfarea +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove ospf area to/from a vrouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-ospf-add, vrouter-ospf-remove command. + - This command adds/removes Open Shortest Path First(OSPF) area to/from + a virtual router(vRouter) service. +options: + pn_cliusername: + description: + - Login username. + required: true + pn_clipassword: + description: + - Login password. + required: true + pn_cliswitch: + description: + - Target switch(es) to run the CLI on. + required: False + state: + description: + - State the action to perform. Use 'present' to add ospf-area, 'absent' + to remove ospf-area and 'update' to modify ospf-area. + required: true + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: true + pn_ospf_area: + description: + - Specify the OSPF area number. + required: true + pn_stub_type: + description: + - Specify the OSPF stub type. + choices: ['none', 'stub', 'stub-no-summary', 'nssa', 'nssa-no-summary'] + pn_prefix_listin: + description: + - OSPF prefix list for filtering incoming packets. + pn_prefix_listout: + description: + - OSPF prefix list for filtering outgoing packets. + pn_quiet: + description: + - Enable/disable system information. + required: false + type: bool + default: true +''' + +EXAMPLES = """ +- name: "Add OSPF area to vrouter" + community.network.pn_ospfarea: + state: present + pn_cliusername: admin + pn_clipassword: admin + pn_ospf_area: 1.0.0.0 + pn_stub_type: stub + +- name: "Remove OSPF from vrouter" + pn_ospf: + state: absent + pn_cliusername: admin + pn_clipassword: admin + pn_vrouter_name: name-string + pn_ospf_area: 1.0.0.0 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the ospf command. + returned: always + type: list +stderr: + description: The set of error responses from the ospf command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-ospf-area-add' + if state == 'absent': + command = 'vrouter-ospf-area-remove' + if state == 'update': + command = 'vrouter-ospf-area-modify' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=True, type='str'), + pn_clipassword=dict(required=True, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_ospf_area=dict(required=True, type='str'), + pn_stub_type=dict(type='str', choices=['none', 'stub', 'nssa', + 'stub-no-summary', + 'nssa-no-summary']), + pn_prefix_listin=dict(type='str'), + pn_prefix_listout=dict(type='str'), + pn_quiet=dict(type='bool', default='True') + ) + ) + + # Accessing the arguments + cliusername = module.params['pn_cliusername'] + clipassword = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + ospf_area = module.params['pn_ospf_area'] + stub_type = module.params['pn_stub_type'] + prefix_listin = module.params['pn_prefix_listin'] + prefix_listout = module.params['pn_prefix_listout'] + quiet = module.params['pn_quiet'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = '/usr/bin/cli' + + if quiet is True: + cli += ' --quiet ' + + cli += ' --user %s:%s ' % (cliusername, clipassword) + + if cliswitch: + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + + cli += ' %s vrouter-name %s area %s ' % (command, vrouter_name, ospf_area) + + if stub_type: + cli += ' stub-type ' + stub_type + + if prefix_listin: + cli += ' prefix-list-in ' + prefix_listin + + if prefix_listout: + cli += ' prefix-list-out ' + prefix_listout + + # Run the CLI command + ospfcommand = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(ospfcommand) + + # Response in JSON format + if result != 0: + module.exit_json( + command=cli, + stderr=err.rstrip("\r\n"), + changed=False + ) + + else: + module.exit_json( + command=cli, + stdout=out.rstrip("\r\n"), + changed=True + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_config.py new file mode 100644 index 00000000..31f024b4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_config.py @@ -0,0 +1,377 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_config +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-config +description: + - This module can be used to modify a port configuration. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the port-config. + required: True + type: str + choices: ['update'] + pn_intf: + description: + - physical interface. + required: False + type: str + pn_crc_check_enable: + description: + - CRC check on ingress and rewrite on egress. + required: False + type: bool + pn_dscp_map: + description: + - DSCP map name to enable on port. + required: False + type: str + pn_autoneg: + description: + - physical port autonegotiation. + required: False + type: bool + pn_speed: + description: + - physical port speed. + required: False + choices: ['disable', '10m', '100m', '1g', + '2.5g', '10g', '25g', '40g', '50g', '100g'] + pn_port: + description: + - physical port. + required: False + type: str + pn_vxlan_termination: + description: + - physical port vxlan termination setting. + required: False + type: bool + pn_pause: + description: + - physical port pause. + required: False + type: bool + pn_loopback: + description: + - physical port loopback. + required: False + type: bool + pn_loop_vlans: + description: + - looping vlans. + required: False + type: str + pn_routing: + description: + - routing. + required: False + type: bool + pn_edge_switch: + description: + - physical port edge switch. + required: False + type: bool + pn_enable: + description: + - physical port enable. + required: False + type: bool + pn_description: + description: + - physical port description. + required: False + type: str + pn_host_enable: + description: + - Host facing port control setting. + required: False + type: bool + pn_allowed_tpid: + description: + - Allowed TPID in addition to 0x8100 on Vlan header. + required: False + type: str + choices: ['vlan', 'q-in-q', 'q-in-q-old'] + pn_mirror_only: + description: + - physical port mirror only. + required: False + type: bool + pn_reflect: + description: + - physical port reflection. + required: False + type: bool + pn_jumbo: + description: + - jumbo frames on physical port. + required: False + type: bool + pn_egress_rate_limit: + description: + - max egress port data rate limit. + required: False + type: str + pn_eth_mode: + description: + - physical Ethernet mode. + required: False + choices: ['1000base-x', 'sgmii', 'disabled', 'GMII'] + pn_fabric_guard: + description: + - Fabric guard configuration. + required: False + type: bool + pn_local_switching: + description: + - no-local-switching port cannot bridge traffic to + another no-local-switching port. + required: False + type: bool + pn_lacp_priority: + description: + - LACP priority from 1 to 65535. + required: False + type: str + pn_send_port: + description: + - send port. + required: False + type: str + pn_port_mac_address: + description: + - physical port MAC Address. + required: False + type: str + pn_defer_bringup: + description: + - defer port bringup. + required: False + type: bool +''' + +EXAMPLES = """ +- name: Port config modify + community.network.pn_port_config: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_dscp_map: "foo" + +- name: Port config modify + community.network.pn_port_config: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_host_enable: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-config command. + returned: always + type: list +stderr: + description: set of error responses from the port-config command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_dscp_map'] + + cli += ' dscp-map-show name %s format name no-show-headers' % name + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out[-1] else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-config-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['update']), + pn_intf=dict(required=False, type='str'), + pn_crc_check_enable=dict(required=False, type='bool'), + pn_dscp_map=dict(required=False, type='str'), + pn_autoneg=dict(required=False, type='bool'), + pn_speed=dict(required=False, type='str', + choices=['disable', '10m', '100m', + '1g', '2.5g', '10g', '25g', + '40g', '50g', '100g']), + pn_port=dict(required=False, type='str'), + pn_vxlan_termination=dict(required=False, type='bool'), + pn_pause=dict(required=False, type='bool'), + pn_loopback=dict(required=False, type='bool'), + pn_loop_vlans=dict(required=False, type='str'), + pn_routing=dict(required=False, type='bool'), + pn_edge_switch=dict(required=False, type='bool'), + pn_enable=dict(required=False, type='bool'), + pn_description=dict(required=False, type='str'), + pn_host_enable=dict(required=False, type='bool'), + pn_allowed_tpid=dict(required=False, type='str', + choices=['vlan', 'q-in-q', 'q-in-q-old']), + pn_mirror_only=dict(required=False, type='bool'), + pn_reflect=dict(required=False, type='bool'), + pn_jumbo=dict(required=False, type='bool'), + pn_egress_rate_limit=dict(required=False, type='str'), + pn_eth_mode=dict(required=False, type='str', + choices=['1000base-x', 'sgmii', + 'disabled', 'GMII']), + pn_fabric_guard=dict(required=False, type='bool'), + pn_local_switching=dict(required=False, type='bool'), + pn_lacp_priority=dict(required=False, type='str'), + pn_send_port=dict(required=False, type='str'), + pn_port_mac_address=dict(required=False, type='str'), + pn_defer_bringup=dict(required=False, type='bool'), + ), + required_if=( + ['state', 'update', ['pn_port']], + ), + required_one_of=[['pn_intf', 'pn_crc_check_enable', 'pn_dscp_map', + 'pn_speed', 'pn_autoneg', + 'pn_vxlan_termination', 'pn_pause', + 'pn_fec', 'pn_loopback', 'pn_loop_vlans', + 'pn_routing', 'pn_edge_switch', + 'pn_enable', 'pn_description', + 'pn_host_enable', 'pn_allowed_tpid', + 'pn_mirror_only', 'pn_reflect', + 'pn_jumbo', 'pn_egress_rate_limit', + 'pn_eth_mode', 'pn_fabric_guard', + 'pn_local_switching', 'pn_lacp_priority', + 'pn_send_port', 'pn_port_mac_address', + 'pn_defer_bringup']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + intf = module.params['pn_intf'] + crc_check_enable = module.params['pn_crc_check_enable'] + dscp_map = module.params['pn_dscp_map'] + autoneg = module.params['pn_autoneg'] + speed = module.params['pn_speed'] + port = module.params['pn_port'] + vxlan_termination = module.params['pn_vxlan_termination'] + pause = module.params['pn_pause'] + loopback = module.params['pn_loopback'] + loop_vlans = module.params['pn_loop_vlans'] + routing = module.params['pn_routing'] + edge_switch = module.params['pn_edge_switch'] + enable = module.params['pn_enable'] + description = module.params['pn_description'] + host_enable = module.params['pn_host_enable'] + allowed_tpid = module.params['pn_allowed_tpid'] + mirror_only = module.params['pn_mirror_only'] + reflect = module.params['pn_reflect'] + jumbo = module.params['pn_jumbo'] + egress_rate_limit = module.params['pn_egress_rate_limit'] + eth_mode = module.params['pn_eth_mode'] + fabric_guard = module.params['pn_fabric_guard'] + local_switching = module.params['pn_local_switching'] + lacp_priority = module.params['pn_lacp_priority'] + send_port = module.params['pn_send_port'] + port_mac_address = module.params['pn_port_mac_address'] + defer_bringup = module.params['pn_defer_bringup'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if dscp_map: + NAME_EXISTS = check_cli(module, cli) + + if command == 'port-config-modify': + cli += ' %s ' % command + if dscp_map: + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='Create dscp map with name %s before updating' % dscp_map + ) + + cli += ' dscp-map ' + dscp_map + if intf: + cli += ' intf ' + intf + if speed: + cli += ' speed ' + speed + if port: + cli += ' port ' + port + if allowed_tpid: + cli += ' allowed-tpid ' + allowed_tpid + if egress_rate_limit: + cli += ' egress-rate-limit ' + egress_rate_limit + if eth_mode: + cli += ' eth-mode ' + eth_mode + if lacp_priority: + cli += ' lacp-priority ' + lacp_priority + if send_port: + cli += ' send-port ' + send_port + if port_mac_address: + cli += ' port-mac-address ' + port_mac_address + + cli += booleanArgs(crc_check_enable, 'crc-check-enable', 'crc-check-disable') + cli += booleanArgs(autoneg, 'autoneg', 'no-autoneg') + cli += booleanArgs(vxlan_termination, 'vxlan-termination', 'no-vxlan-termination') + cli += booleanArgs(pause, 'pause', 'no-pause') + cli += booleanArgs(loopback, 'loopback', 'no-loopback') + cli += booleanArgs(routing, 'routing', 'no-routing') + cli += booleanArgs(edge_switch, 'edge-switch', 'no-edge-switch') + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(host_enable, 'host-enable', 'host-disable') + cli += booleanArgs(mirror_only, 'mirror-only', 'no-mirror-receive-only') + cli += booleanArgs(reflect, 'reflect', 'no-reflect') + cli += booleanArgs(jumbo, 'jumbo', 'no-jumbo') + cli += booleanArgs(fabric_guard, 'fabric-guard', 'no-fabric-guard') + cli += booleanArgs(local_switching, 'local-switching', 'no-local-switching') + cli += booleanArgs(defer_bringup, 'defer-bringup', 'no-defer-bringup') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_bw.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_bw.py new file mode 100644 index 00000000..11578dc8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_bw.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_cos_bw +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-cos-bw +description: + - This module can be used to update bw settings for CoS queues. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the port-cos-bw. + required: True + type: str + choices: ['update'] + pn_max_bw_limit: + description: + - Maximum b/w in percentage. + required: False + type: str + pn_cos: + description: + - CoS priority. + required: False + type: str + pn_port: + description: + - physical port number. + required: False + type: str + pn_weight: + description: + - Scheduling weight (1 to 127) after b/w guarantee met. + required: False + type: str + choices: ['priority', 'no-priority'] + pn_min_bw_guarantee: + description: + - Minimum b/w in percentage. + required: False + type: str +''' + +EXAMPLES = """ +- name: Port cos bw modify + community.network.pn_port_cos_bw: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_cos: "0" + pn_min_bw_guarantee: "60" + +- name: Port cos bw modify + community.network.pn_port_cos_bw: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_cos: "0" + pn_weight: "priority" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-cos-bw command. + returned: always + type: list +stderr: + description: set of error responses from the port-cos-bw command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-cos-bw-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_max_bw_limit=dict(required=False, type='str'), + pn_cos=dict(required=False, type='str'), + pn_port=dict(required=False, type='str'), + pn_weight=dict(required=False, type='str', + choices=['priority', 'no-priority']), + pn_min_bw_guarantee=dict(required=False, type='str'), + ), + required_if=( + ['state', 'update', ['pn_cos', 'pn_port']], + ), + required_one_of=[['pn_max_bw_limit', 'pn_min_bw_guarantee', 'pn_weight']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + max_bw_limit = module.params['pn_max_bw_limit'] + cos = module.params['pn_cos'] + port = module.params['pn_port'] + weight = module.params['pn_weight'] + min_bw_guarantee = module.params['pn_min_bw_guarantee'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'port-cos-bw-modify': + cli += ' %s ' % command + if max_bw_limit: + cli += ' max-bw-limit ' + max_bw_limit + if cos: + cli += ' cos ' + cos + if port: + cli += ' port ' + port + if weight: + cli += ' weight ' + weight + if min_bw_guarantee: + cli += ' min-bw-guarantee ' + min_bw_guarantee + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_rate_setting.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_rate_setting.py new file mode 100644 index 00000000..fbe0b9f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_port_cos_rate_setting.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_cos_rate_setting +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-cos-rate-setting +description: + - This modules can be used to update the port cos rate limit. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the port-cos-rate-setting. + required: true + type: str + choices: ['update'] + pn_cos0_rate: + description: + - cos0 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos1_rate: + description: + - cos1 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos2_rate: + description: + - cos2 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos3_rate: + description: + - cos3 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos4_rate: + description: + - cos4 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos5_rate: + description: + - cos5 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos6_rate: + description: + - cos6 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos7_rate: + description: + - cos7 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_port: + description: + - port. + required: false + type: str + choices: ['control-port', 'data-port', 'span-ports'] +''' + +EXAMPLES = """ +- name: Port cos rate modify + community.network.pn_port_cos_rate_setting: + pn_cliswitch: "sw01" + state: "update" + pn_port: "control-port" + pn_cos1_rate: "1000" + pn_cos5_rate: "1000" + pn_cos2_rate: "1000" + pn_cos0_rate: "1000" + +- name: Port cos rate modify + community.network.pn_port_cos_rate_setting: + pn_cliswitch: "sw01" + state: "update" + pn_port: "data-port" + pn_cos1_rate: "2000" + pn_cos5_rate: "2000" + pn_cos2_rate: "2000" + pn_cos0_rate: "2000" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-cos-rate-setting command. + returned: always + type: list +stderr: + description: set of error responses from the port-cos-rate-setting command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-cos-rate-setting-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_cos1_rate=dict(required=False, type='str'), + pn_cos5_rate=dict(required=False, type='str'), + pn_cos2_rate=dict(required=False, type='str'), + pn_cos0_rate=dict(required=False, type='str'), + pn_cos6_rate=dict(required=False, type='str'), + pn_cos3_rate=dict(required=False, type='str'), + pn_cos4_rate=dict(required=False, type='str'), + pn_cos7_rate=dict(required=False, type='str'), + pn_port=dict(required=False, type='str', + choices=['control-port', 'data-port', 'span-ports']), + ), + required_if=( + ['state', 'update', ['pn_port']], + ), + required_one_of=[['pn_cos0_rate', + 'pn_cos1_rate', + 'pn_cos2_rate', + 'pn_cos3_rate', + 'pn_cos4_rate', + 'pn_cos5_rate', + 'pn_cos6_rate', + 'pn_cos7_rate']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + cos1_rate = module.params['pn_cos1_rate'] + cos5_rate = module.params['pn_cos5_rate'] + cos2_rate = module.params['pn_cos2_rate'] + cos0_rate = module.params['pn_cos0_rate'] + cos6_rate = module.params['pn_cos6_rate'] + cos3_rate = module.params['pn_cos3_rate'] + cos4_rate = module.params['pn_cos4_rate'] + cos7_rate = module.params['pn_cos7_rate'] + port = module.params['pn_port'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'port-cos-rate-setting-modify': + cli += ' %s ' % command + if cos1_rate: + cli += ' cos1-rate ' + cos1_rate + if cos5_rate: + cli += ' cos5-rate ' + cos5_rate + if cos2_rate: + cli += ' cos2-rate ' + cos2_rate + if cos0_rate: + cli += ' cos0-rate ' + cos0_rate + if cos6_rate: + cli += ' cos6-rate ' + cos6_rate + if cos3_rate: + cli += ' cos3-rate ' + cos3_rate + if cos4_rate: + cli += ' cos4-rate ' + cos4_rate + if cos7_rate: + cli += ' cos7-rate ' + cos7_rate + if port: + cli += ' port ' + port + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list.py new file mode 100644 index 00000000..04baeb98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list.py @@ -0,0 +1,159 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_prefix_list +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete prefix-list +description: + - This module can be used to create or delete prefix list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create prefix-list and + C(absent) to delete prefix-list. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_name: + description: + - Prefix List Name. + required: true + type: str + pn_scope: + description: + - scope of prefix-list. + required: false + type: str + choices: ['local', 'fabric'] +''' + +EXAMPLES = """ +- name: Create prefix list + community.network.pn_prefix_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "present" + +- name: Delete prefix list + community.network.pn_prefix_list: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the prefix-list command. + returned: always + type: list +stderr: + description: set of error responses from the prefix-list command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the prefix-list-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' prefix-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='prefix-list-create', + absent='prefix-list-delete' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_name=dict(required=True, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command == 'prefix-list-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='prefix-list with name %s does not exist' % name + ) + else: + if command == 'prefix-list-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='prefix list with name %s already exists' % name + ) + cli += ' scope %s ' % scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list_network.py new file mode 100644 index 00000000..e024e5ad --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_prefix_list_network.py @@ -0,0 +1,185 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_prefix_list_network +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove prefix-list-network +description: + - This module is used to add network associated with prefix list + and remove networks associated with prefix list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create + prefix-list-network and C(absent) to delete prefix-list-network. + required: true + type: str + choices: ['present', 'absent'] + pn_netmask: + description: + - netmask of the network associated the prefix list. + required: false + type: str + pn_name: + description: + - Prefix List Name. + required: false + type: str + pn_network: + description: + - network associated with the prefix list. + required: false + type: str +''' + +EXAMPLES = """ +- name: Prefix list network add + community.network.pn_prefix_list_network: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_network: "172.16.3.1" + pn_netmask: "24" + state: "present" + +- name: Prefix list network remove + community.network.pn_prefix_list_network: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + pn_network: "172.16.3.1" + pn_netmask: "24" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the prefix-list-network command. + returned: always + type: list +stderr: + description: set of error responses from the prefix-list-network command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using prefix-list-network-show command. + If network exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + network = module.params['pn_network'] + show = cli + + cli += ' prefix-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if name not in out.split()[-1]: + module.fail_json( + failed=True, + msg='Prefix list with name %s does not exists' % name + ) + + cli = show + cli += ' prefix-list-network-show name %s format network no-show-headers' % name + rc, out, err = run_commands(module, cli) + + if out: + out = out.split()[-1] + return True if network in out.split('/')[0] else False + + return False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='prefix-list-network-add', + absent='prefix-list-network-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_netmask=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + pn_network=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_network", "pn_netmask"]], + ["state", "absent", ["pn_name", "pn_network", "pn_netmask"]], + ), + required_together=( + ["pn_network", "pn_netmask"], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + netmask = module.params['pn_netmask'] + name = module.params['pn_name'] + network = module.params['pn_network'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NETWORK_EXISTS = check_cli(module, cli) + cli += ' %s ' % command + + if command == 'prefix-list-network-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Prefix list with network %s does not exist' % network + ) + + if command == 'prefix-list-network-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Prefix list with network %s already exists' % network + ) + + if name: + cli += ' name ' + name + if network: + cli += ' network ' + network + if netmask: + cli += ' netmask ' + netmask + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_role.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_role.py new file mode 100644 index 00000000..78becdbd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_role.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_role +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete/modify role +description: + - This module can be used to create, delete and modify user roles. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create role and + C(absent) to delete role and C(update) to modify role. + required: true + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - local or fabric. + required: false + type: str + choices: ['local', 'fabric'] + pn_access: + description: + - type of access. + required: false + type: str + choices: ['read-only', 'read-write'] + pn_shell: + description: + - allow shell command. + required: false + type: bool + pn_sudo: + description: + - allow sudo from shell. + required: false + type: bool + pn_running_config: + description: + - display running configuration of switch. + required: false + type: bool + pn_name: + description: + - role name. + required: true + type: str + pn_delete_from_users: + description: + - delete from users. + required: false + type: bool +''' + +EXAMPLES = """ +- name: Role create + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'present' + pn_name: 'foo' + pn_scope: 'local' + pn_access: 'read-only' + +- name: Role delete + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'foo' + +- name: Role modify + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_access: 'read-write' + pn_sudo: true + pn_shell: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the role command. + returned: always + type: list +stderr: + description: set of error responses from the role command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the role-show command. + If a role with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + role_name = module.params['pn_name'] + + cli += ' role-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if role_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='role-create', + absent='role-delete', + update='role-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_access=dict(required=False, type='str', + choices=['read-only', 'read-write']), + pn_shell=dict(required=False, type='bool'), + pn_sudo=dict(required=False, type='bool'), + pn_running_config=dict(required=False, type='bool'), + pn_name=dict(required=False, type='str'), + pn_delete_from_users=dict(required=False, type='bool'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + access = module.params['pn_access'] + shell = module.params['pn_shell'] + sudo = module.params['pn_sudo'] + running_config = module.params['pn_running_config'] + name = module.params['pn_name'] + delete_from_users = module.params['pn_delete_from_users'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + ROLE_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if shell is (False or '') and sudo is True: + module.fail_json( + failed=True, + msg='sudo access requires shell access' + ) + + if command == 'role-modify': + if ROLE_EXISTS is False: + module.fail_json( + failed=True, + msg='Role with name %s does not exist' % name + ) + + if command == 'role-delete': + if ROLE_EXISTS is False: + module.exit_json( + skipped=True, + msg='Role with name %s does not exist' % name + ) + + if command == 'role-create': + if ROLE_EXISTS is True: + module.exit_json( + skipped=True, + msg='Role with name %s already exists' % name + ) + + if scope: + cli += ' scope ' + scope + + if command != 'role-delete': + if access: + cli += ' access ' + access + + cli += booleanArgs(shell, 'shell', 'no-shell') + cli += booleanArgs(sudo, 'sudo', 'no-sudo') + cli += booleanArgs(running_config, 'running-config', 'no-running-config') + + if command == 'role-modify': + if delete_from_users: + cli += ' delete-from-users ' + delete_from_users + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_show.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_show.py new file mode 100644 index 00000000..6de9d250 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_show.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +""" PN CLI show commands """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_show +author: "Pluribus Networks (@amitsi)" +short_description: Run show commands on nvOS device. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute show command in the nodes and returns the results + read from the device. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + pn_command: + description: + - The C(pn_command) takes a CLI show command as value. + required: true + pn_parameters: + description: + - Display output using a specific parameter. Use 'all' to display + possible output. List of comma separated parameters. + default: 'all' + pn_options: + description: + - Specify formatting options. +''' + +EXAMPLES = """ +- name: Run the vlan-show command + community.network.pn_show: + pn_command: 'vlan-show' + pn_parameters: id,scope,ports + pn_options: 'layout vertical' + +- name: Run the vlag-show command + community.network.pn_show: + pn_command: 'vlag-show' + pn_parameters: 'id,name,cluster,mode' + pn_options: 'no-show-headers' + +- name: Run the cluster-show command + community.network.pn_show: + pn_command: 'cluster-show' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the show command. + returned: always + type: list +stderr: + description: The set of error responses from the show command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused any change on the target. + returned: always(False) + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch: + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + command = module.params['pn_command'] + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + msg='%s: ' % command, + stderr=err.strip(), + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + msg='%s: ' % command, + stdout=out.strip(), + changed=False + ) + + else: + module.exit_json( + command=cli, + msg='%s: Nothing to display!!!' % command, + changed=False + ) + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=True, type='str'), + pn_clipassword=dict(required=True, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str'), + pn_command=dict(required=True, type='str'), + pn_parameters=dict(default='all', type='str'), + pn_options=dict(type='str') + ) + ) + + # Accessing the arguments + command = module.params['pn_command'] + parameters = module.params['pn_parameters'] + options = module.params['pn_options'] + + # Building the CLI command string + cli = pn_cli(module) + + cli += ' %s format %s ' % (command, parameters) + + if options: + cli += options + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_community.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_community.py new file mode 100644 index 00000000..e42e925f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_community.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_community +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete snmp-community +description: + - This module can be used to create SNMP communities for SNMPv1 or + delete SNMP communities for SNMPv1 or modify SNMP communities for SNMPv1. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + state: + description: + - State the action to perform. Use C(present) to create snmp-community and + C(absent) to delete snmp-community C(update) to update snmp-community. + required: true + type: str + choices: ['present', 'absent', 'update'] + pn_community_type: + description: + - community type. + type: str + choices: ['read-only', 'read-write'] + pn_community_string: + description: + - community name. + type: str +''' + +EXAMPLES = """ +- name: Create snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "present" + pn_community_string: "foo" + pn_community_type: "read-write" + +- name: Delete snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "absent" + pn_community_string: "foo" + +- name: Modify snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "update" + pn_community_string: "foo" + pn_community_type: "read-only" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-community command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-community command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-community-show command. + If a user with given name exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + comm_str = module.params['pn_community_string'] + + cli += ' snmp-community-show format community-string no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if comm_str in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-community-create', + absent='snmp-community-delete', + update='snmp-community-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_community_type=dict(required=False, type='str', + choices=['read-only', 'read-write']), + pn_community_string=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_community_type", "pn_community_string"]], + ["state", "absent", ["pn_community_string"]], + ["state", "update", ["pn_community_type", "pn_community_string"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + community_type = module.params['pn_community_type'] + comm_str = module.params['pn_community_string'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + COMMUNITY_EXISTS = check_cli(module, cli) + + if command == 'snmp-community-modify': + if COMMUNITY_EXISTS is False: + module.fail_json( + failed=True, + msg='snmp community name %s does not exist' % comm_str + ) + + if command == 'snmp-community-delete': + if COMMUNITY_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp community name %s does not exist' % comm_str + ) + + if command == 'snmp-community-create': + if COMMUNITY_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp community with name %s already exists' % comm_str + ) + + cli += ' %s community-string %s ' % (command, comm_str) + + if command != 'snmp-community-delete' and community_type: + cli += ' community-type ' + community_type + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_trap_sink.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_trap_sink.py new file mode 100644 index 00000000..f60ff96b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_trap_sink.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_trap_sink +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete snmp-trap-sink +description: + - This module can be used to create a SNMP trap sink and delete a SNMP trap sink. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create snmp-trap-sink and + C(absent) to delete snmp-trap-sink. + required: true + type: str + choices: ['present', 'absent'] + pn_dest_host: + description: + - destination host. + type: str + pn_community: + description: + - community type. + type: str + pn_dest_port: + description: + - destination port. + type: str + default: '162' + pn_type: + description: + - trap type. + type: str + choices: ['TRAP_TYPE_V1_TRAP', 'TRAP_TYPE_V2C_TRAP', 'TRAP_TYPE_V2_INFORM'] + default: 'TRAP_TYPE_V2C_TRAP' +''' + +EXAMPLES = """ +- name: Snmp trap sink functionality + community.network.pn_snmp_trap_sink: + pn_cliswitch: "sw01" + state: "present" + pn_community: "foo" + pn_type: "TRAP_TYPE_V2_INFORM" + pn_dest_host: "192.168.67.8" + +- name: Snmp trap sink functionality + community.network.pn_snmp_trap_sink: + pn_cliswitch: "sw01" + state: "absent" + pn_community: "foo" + pn_type: "TRAP_TYPE_V2_INFORM" + pn_dest_host: "192.168.67.8" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-trap-sink command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-trap-sink command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-trap-sink-show command. + If a trap with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + community = module.params['pn_community'] + dest_host = module.params['pn_dest_host'] + + show = cli + cli += ' snmp-community-show format community-string no-show-headers' + rc, out, err = run_commands(module, cli) + + if out: + out = out.split() + + if community in out: + cli = show + cli += ' snmp-trap-sink-show community %s format type,dest-host no-show-headers' % community + rc, out, err = run_commands(module, cli) + + if out: + out = out.split() + + return True if dest_host in out else False + else: + return None + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-trap-sink-create', + absent='snmp-trap-sink-delete' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_dest_host=dict(required=False, type='str'), + pn_community=dict(required=False, type='str'), + pn_dest_port=dict(required=False, type='str', default='162'), + pn_type=dict(required=False, type='str', + choices=['TRAP_TYPE_V1_TRAP', + 'TRAP_TYPE_V2C_TRAP', + 'TRAP_TYPE_V2_INFORM'], + default='TRAP_TYPE_V2C_TRAP'), + ), + required_if=( + ["state", "present", ["pn_community", "pn_dest_host"]], + ["state", "absent", ["pn_community", "pn_dest_host"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + dest_host = module.params['pn_dest_host'] + community = module.params['pn_community'] + dest_port = module.params['pn_dest_port'] + pn_type = module.params['pn_type'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VALUE_EXISTS = check_cli(module, cli) + cli += ' %s ' % command + + if command == 'snmp-trap-sink-create': + if VALUE_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp trap sink already exists' + ) + if VALUE_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp community does not exists to create trap sink' + ) + if pn_type: + cli += ' type ' + pn_type + if dest_host: + cli += ' dest-host ' + dest_host + if community: + cli += ' community ' + community + if dest_port: + cli += ' dest-port ' + dest_port + + if command == 'snmp-trap-sink-delete': + if VALUE_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp community does not exists to delete trap sink' + ) + if VALUE_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp-trap-sink with community %s does not exist with dest-host %s ' % (community, dest_host) + ) + if community: + cli += ' community ' + community + if dest_host: + cli += ' dest-host ' + dest_host + if dest_port: + cli += ' dest-port ' + dest_port + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_vacm.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_vacm.py new file mode 100644 index 00000000..62c33a6c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_snmp_vacm.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_vacm +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete snmp-vacm +description: + - This module can be used to create View Access Control Models (VACM), + modify VACM and delete VACM. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(present) to create snmp-vacm and + C(absent) to delete snmp-vacm and C(update) to modify snmp-vacm. + type: str + required: true + choices: ['present', 'absent', 'update'] + pn_oid_restrict: + description: + - restrict OID. + type: str + pn_priv: + description: + - privileges. + type: bool + pn_auth: + description: + - authentication required. + type: bool + pn_user_type: + description: + - SNMP user type. + type: str + choices: ['rouser', 'rwuser'] + pn_user_name: + description: + - SNMP administrator name. + type: str +''' + +EXAMPLES = """ +- name: Create snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "present" + pn_user_name: "foo" + pn_user_type: "rouser" + +- name: Update snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "update" + pn_user_name: "foo" + pn_user_type: "rwuser" + +- name: Delete snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "absent" + pn_user_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-vacm command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-vacm command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-vacm-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + user_name = module.params['pn_user_name'] + show = cli + + cli += ' snmp-user-show format user-name no-show-headers' + rc, out, err = run_commands(module, cli) + + if out and user_name in out.split(): + pass + else: + return None + + cli = show + cli += ' snmp-vacm-show format user-name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if user_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-vacm-create', + absent='snmp-vacm-delete', + update='snmp-vacm-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_oid_restrict=dict(required=False, type='str'), + pn_priv=dict(required=False, type='bool'), + pn_auth=dict(required=False, type='bool'), + pn_user_type=dict(required=False, type='str', + choices=['rouser', 'rwuser']), + pn_user_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_user_name"]], + ["state", "absent", ["pn_user_name"]], + ["state", "update", ["pn_user_name"]] + ) + + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + oid_restrict = module.params['pn_oid_restrict'] + priv = module.params['pn_priv'] + auth = module.params['pn_auth'] + user_type = module.params['pn_user_type'] + user_name = module.params['pn_user_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s user-name %s ' % (command, user_name) + + if command == 'snmp-vacm-modify': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='snmp vacm with name %s does not exists' % user_name + ) + + if command == 'snmp-vacm-delete': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp vacm with name %s does not exist' % user_name + ) + + if command == 'snmp-vacm-create': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp vacm with name %s already exists' % user_name + ) + + if command != 'snmp-vacm-delete': + if oid_restrict: + cli += ' oid-restrict ' + oid_restrict + if user_type: + cli += ' user-type ' + user_type + + cli += booleanArgs(auth, 'auth', 'no-auth') + cli += booleanArgs(priv, 'priv', 'no-priv') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp.py new file mode 100644 index 00000000..9b7b8fd4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp.py @@ -0,0 +1,199 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_stp +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify stp +description: + - This module can be used to modify Spanning Tree Protocol parameters. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to stp. + type: str + required: true + choices: ['update'] + pn_hello_time: + description: + - STP hello time between 1 and 10 secs. + type: str + default: '2' + pn_enable: + description: + - enable or disable STP + type: bool + pn_root_guard_wait_time: + description: + - root guard wait time between 0 and 300 secs. 0 to disable wait. + type: str + default: '20' + pn_bpdus_bridge_ports: + description: + - BPDU packets to bridge specific port. + type: bool + pn_mst_max_hops: + description: + - maximum hop count for mstp bpdu. + type: str + default: '20' + pn_bridge_id: + description: + - STP bridge id. + type: str + pn_max_age: + description: + - maximum age time between 6 and 40 secs. + type: str + default: '20' + pn_stp_mode: + description: + - STP mode. + type: str + choices: ['rstp', 'mstp'] + pn_mst_config_name: + description: + - Name for MST Configuration Instance. + type: str + pn_forwarding_delay: + description: + - STP forwarding delay between 4 and 30 secs. + type: str + default: '15' + pn_bridge_priority: + description: + - STP bridge priority. + type: str + default: '32768' +''' + +EXAMPLES = """ +- name: Modify stp + community.network.pn_stp: + pn_cliswitch: "sw01" + state: "update" + pn_hello_time: "3" + pn_stp_mode: "rstp" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the stp command. + returned: always + type: list +stderr: + description: set of error responses from the stp command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='stp-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_hello_time=dict(required=False, type='str', default='2'), + pn_enable=dict(required=False, type='bool'), + pn_root_guard_wait_time=dict(required=False, type='str', default='20'), + pn_bpdus_bridge_ports=dict(required=False, type='bool'), + pn_mst_max_hops=dict(required=False, type='str', default='20'), + pn_bridge_id=dict(required=False, type='str'), + pn_max_age=dict(required=False, type='str', default='20'), + pn_stp_mode=dict(required=False, type='str', + choices=['rstp', 'mstp']), + pn_mst_config_name=dict(required=False, type='str'), + pn_forwarding_delay=dict(required=False, type='str', default='15'), + pn_bridge_priority=dict(required=False, type='str', default='32768'), + ), + required_one_of=[['pn_enable', 'pn_hello_time', + 'pn_root_guard_wait_time', + 'pn_bpdus_bridge_ports', + 'pn_mst_max_hops', + 'pn_bridge_id', + 'pn_max_age', + 'pn_stp_mode', + 'pn_mst_config_name', + 'pn_forwarding_delay', + 'pn_bridge_priority']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + hello_time = module.params['pn_hello_time'] + enable = module.params['pn_enable'] + root_guard_wait_time = module.params['pn_root_guard_wait_time'] + bpdus_bridge_ports = module.params['pn_bpdus_bridge_ports'] + mst_max_hops = module.params['pn_mst_max_hops'] + bridge_id = module.params['pn_bridge_id'] + max_age = module.params['pn_max_age'] + stp_mode = module.params['pn_stp_mode'] + mst_config_name = module.params['pn_mst_config_name'] + forwarding_delay = module.params['pn_forwarding_delay'] + bridge_priority = module.params['pn_bridge_priority'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'stp-modify': + cli += ' %s ' % command + if hello_time: + cli += ' hello-time ' + hello_time + if root_guard_wait_time: + cli += ' root-guard-wait-time ' + root_guard_wait_time + if mst_max_hops: + cli += ' mst-max-hops ' + mst_max_hops + if bridge_id: + cli += ' bridge-id ' + bridge_id + if max_age: + cli += ' max-age ' + max_age + if stp_mode: + cli += ' stp-mode ' + stp_mode + if mst_config_name: + cli += ' mst-config-name ' + mst_config_name + if forwarding_delay: + cli += ' forwarding-delay ' + forwarding_delay + if bridge_priority: + cli += ' bridge-priority ' + bridge_priority + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(bpdus_bridge_ports, 'bpdus-bridge-ports', 'bpdus-all-ports') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp_port.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp_port.py new file mode 100644 index 00000000..81b4dde8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_stp_port.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_stp_port +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify stp-port. +description: + - This module can be used modify Spanning Tree Protocol (STP) parameters on ports. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to update stp-port. + type: str + required: true + choices: ['update'] + pn_priority: + description: + - STP port priority from 0 to 240. + type: str + default: '128' + pn_cost: + description: + - STP port cost from 1 to 200000000. + type: str + default: '2000' + pn_root_guard: + description: + - STP port Root guard. + type: bool + pn_filter: + description: + - STP port filters BPDUs. + type: bool + pn_edge: + description: + - STP port is an edge port. + type: bool + pn_bpdu_guard: + description: + - STP port BPDU guard. + type: bool + pn_port: + description: + - STP port. + type: str + pn_block: + description: + - Specify if a STP port blocks BPDUs. + type: bool +''' + +EXAMPLES = """ +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_filter: True + pn_priority: '144' + +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_cost: "200" + +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_edge: True + pn_cost: "200" + +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the stp-port command. + returned: always + type: list +stderr: + description: set of error responses from the stp-port command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='stp-port-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_priority=dict(required=False, type='str', default='128'), + pn_cost=dict(required=False, type='str', default='2000'), + pn_root_guard=dict(required=False, type='bool'), + pn_filter=dict(required=False, type='bool'), + pn_edge=dict(required=False, type='bool'), + pn_bpdu_guard=dict(required=False, type='bool'), + pn_port=dict(required=False, type='str'), + pn_block=dict(required=False, type='bool'), + ), + required_if=( + ["state", "update", ["pn_port"]], + ), + required_one_of=( + ['pn_cost', 'pn_root_guard', 'pn_filter', + 'pn_edge', 'pn_bpdu_guard', 'pn_block'], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + priority = module.params['pn_priority'] + cost = module.params['pn_cost'] + root_guard = module.params['pn_root_guard'] + pn_filter = module.params['pn_filter'] + edge = module.params['pn_edge'] + bpdu_guard = module.params['pn_bpdu_guard'] + port = module.params['pn_port'] + block = module.params['pn_block'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'stp-port-modify': + cli += ' %s ' % command + if priority and (int(priority) % 16 == 0 and int(priority) < 240): + cli += ' priority ' + priority + else: + module.fail_json( + failed=True, + msg='Priority must be increment of 16 and should be less that 240' + ) + if cost and (int(cost) < 200000000): + cli += ' cost ' + cost + else: + module.fail_json( + failed=True, + msg='cost must be between 1 and 200000000' + ) + if port: + cli += ' port ' + port + + cli += booleanArgs(root_guard, 'root-guard', 'no-root-guard') + cli += booleanArgs(pn_filter, 'filter', 'no-filter') + cli += booleanArgs(edge, 'edge', 'no-edge') + cli += booleanArgs(bpdu_guard, 'bpdu-guard', 'no-bpdu-guard') + cli += booleanArgs(block, 'block', 'no-block') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_switch_setup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_switch_setup.py new file mode 100644 index 00000000..21f6313d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_switch_setup.py @@ -0,0 +1,407 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_switch_setup +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify switch-setup +description: + - This module can be used to modify switch setup. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify the switch-setup. + required: true + type: str + choices: ['update'] + pn_force: + description: + - Force analytics-store change even if it involves removing data. + required: false + type: bool + pn_dns_ip: + description: + - DNS IP address. + required: false + type: str + pn_mgmt_netmask: + description: + - Netmask. + required: false + type: str + pn_gateway_ip6: + description: + - Gateway IPv6 address. + required: false + type: str + pn_in_band_ip6_assign: + description: + - Data IPv6 address assignment. + required: false + type: str + choices: ['none', 'autoconf'] + pn_domain_name: + description: + - Domain name. + required: false + type: str + pn_timezone: + description: + - Timezone to be configured. + required: false + type: str + pn_in_band_netmask: + description: + - Data in-band netmask. + required: false + type: str + pn_in_band_ip6: + description: + - Data in-band IPv6 address. + required: false + type: str + pn_in_band_netmask_ip6: + description: + - Data in-band IPv6 netmask. + required: false + type: str + pn_motd: + description: + - Message of the Day. + required: false + type: str + pn_loopback_ip6: + description: + - loopback IPv6 address. + required: false + type: str + pn_mgmt_ip6_assignment: + description: + - IPv6 address assignment. + required: false + choices: ['none', 'autoconf'] + pn_ntp_secondary_server: + description: + - Secondary NTP server. + required: false + type: str + pn_in_band_ip: + description: + - data in-band IP address. + required: false + type: str + pn_eula_accepted: + description: + - Accept EULA. + required: false + type: str + choices: ['true', 'false'] + pn_mgmt_ip: + description: + - Management IP address. + required: false + type: str + pn_ntp_server: + description: + - NTP server. + required: false + type: str + pn_mgmt_ip_assignment: + description: + - IP address assignment. + required: false + type: str + choices: ['none', 'dhcp'] + pn_date: + description: + - Date. + required: false + type: str + pn_password: + description: + - plain text password. + required: false + type: str + pn_banner: + description: + - Banner to display on server-switch. + required: false + type: str + pn_loopback_ip: + description: + - loopback IPv4 address. + required: false + type: str + pn_dns_secondary_ip: + description: + - secondary DNS IP address. + required: false + type: str + pn_switch_name: + description: + - switch name. + required: false + type: str + pn_eula_timestamp: + description: + - EULA timestamp. + required: false + type: str + pn_mgmt_netmask_ip6: + description: + - IPv6 netmask. + required: false + type: str + pn_enable_host_ports: + description: + - Enable host ports by default. + required: false + type: bool + pn_mgmt_ip6: + description: + - IPv6 address. + required: false + type: str + pn_analytics_store: + description: + - type of disk storage for analytics. + required: false + type: str + choices: ['default', 'optimized'] + pn_gateway_ip: + description: + - gateway IPv4 address. + required: false + type: str +''' + +EXAMPLES = """ +- name: Modify switch + community.network.pn_switch_setup: + pn_cliswitch: "sw01" + state: "update" + pn_timezone: "America/New_York" + pn_in_band_ip: "20.20.1.1" + pn_in_band_netmask: "24" + +- name: Modify switch + community.network.pn_switch_setup: + pn_cliswitch: "sw01" + state: "update" + pn_in_band_ip6: "2001:0db8:85a3::8a2e:0370:7334" + pn_in_band_netmask_ip6: "127" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the switch-setup command. + returned: always + type: list +stderr: + description: set of error responses from the switch-setup command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, booleanArgs, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='switch-setup-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['update']), + pn_force=dict(required=False, type='bool'), + pn_dns_ip=dict(required=False, type='str'), + pn_mgmt_netmask=dict(required=False, type='str'), + pn_gateway_ip6=dict(required=False, type='str'), + pn_in_band_ip6_assign=dict(required=False, type='str', + choices=['none', 'autoconf']), + pn_domain_name=dict(required=False, type='str'), + pn_timezone=dict(required=False, type='str'), + pn_in_band_netmask=dict(required=False, type='str'), + pn_in_band_ip6=dict(required=False, type='str'), + pn_in_band_netmask_ip6=dict(required=False, type='str'), + pn_motd=dict(required=False, type='str'), + pn_loopback_ip6=dict(required=False, type='str'), + pn_mgmt_ip6_assignment=dict(required=False, type='str', + choices=['none', 'autoconf']), + pn_ntp_secondary_server=dict(required=False, type='str'), + pn_in_band_ip=dict(required=False, type='str'), + pn_eula_accepted=dict(required=False, type='str', + choices=['true', 'false']), + pn_mgmt_ip=dict(required=False, type='str'), + pn_ntp_server=dict(required=False, type='str'), + pn_mgmt_ip_assignment=dict(required=False, type='str', + choices=['none', 'dhcp']), + pn_date=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_banner=dict(required=False, type='str'), + pn_loopback_ip=dict(required=False, type='str'), + pn_dns_secondary_ip=dict(required=False, type='str'), + pn_switch_name=dict(required=False, type='str'), + pn_eula_timestamp=dict(required=False, type='str'), + pn_mgmt_netmask_ip6=dict(required=False, type='str'), + pn_enable_host_ports=dict(required=False, type='bool'), + pn_mgmt_ip6=dict(required=False, type='str'), + pn_analytics_store=dict(required=False, type='str', + choices=['default', 'optimized']), + pn_gateway_ip=dict(required=False, type='str'), + ), + required_one_of=[['pn_force', 'pn_dns_ip', 'pn_mgmt_netmask', + 'pn_gateway_ip6', 'pn_in_band_ip6_assign', + 'pn_domain_name', 'pn_timezone', + 'pn_in_band_netmask', 'pn_in_band_ip6', + 'pn_in_band_netmask_ip6', 'pn_motd', + 'pn_loopback_ip6', 'pn_mgmt_ip6_assignment', + 'pn_ntp_secondary_server', 'pn_in_band_ip', + 'pn_eula_accepted', 'pn_mgmt_ip', + 'pn_ntp_server', 'pn_mgmt_ip_assignment', + 'pn_date', 'pn_password', + 'pn_banner', 'pn_loopback_ip', + 'pn_dns_secondary_ip', 'pn_switch_name', + 'pn_eula_timestamp', 'pn_mgmt_netmask_ip6', + 'pn_enable_host_ports', 'pn_mgmt_ip6', + 'pn_analytics_store', 'pn_gateway_ip']], + required_together=[['pn_in_band_ip6', 'pn_in_band_netmask_ip6'], + ['pn_in_band_ip', 'pn_in_band_netmask'], + ['pn_mgmt_ip', 'pn_mgmt_netmask'], + ['pn_mgmt_ip6', 'pn_mgmt_netmask_ip6']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + force = module.params['pn_force'] + dns_ip = module.params['pn_dns_ip'] + mgmt_netmask = module.params['pn_mgmt_netmask'] + gateway_ip6 = module.params['pn_gateway_ip6'] + in_band_ip6_assign = module.params['pn_in_band_ip6_assign'] + domain_name = module.params['pn_domain_name'] + timezone = module.params['pn_timezone'] + in_band_netmask = module.params['pn_in_band_netmask'] + in_band_ip6 = module.params['pn_in_band_ip6'] + in_band_netmask_ip6 = module.params['pn_in_band_netmask_ip6'] + motd = module.params['pn_motd'] + loopback_ip6 = module.params['pn_loopback_ip6'] + mgmt_ip6_assignment = module.params['pn_mgmt_ip6_assignment'] + ntp_secondary_server = module.params['pn_ntp_secondary_server'] + in_band_ip = module.params['pn_in_band_ip'] + eula_accepted = module.params['pn_eula_accepted'] + mgmt_ip = module.params['pn_mgmt_ip'] + ntp_server = module.params['pn_ntp_server'] + mgmt_ip_assignment = module.params['pn_mgmt_ip_assignment'] + date = module.params['pn_date'] + password = module.params['pn_password'] + banner = module.params['pn_banner'] + loopback_ip = module.params['pn_loopback_ip'] + dns_secondary_ip = module.params['pn_dns_secondary_ip'] + switch_name = module.params['pn_switch_name'] + eula_timestamp = module.params['pn_eula_timestamp'] + mgmt_netmask_ip6 = module.params['pn_mgmt_netmask_ip6'] + enable_host_ports = module.params['pn_enable_host_ports'] + mgmt_ip6 = module.params['pn_mgmt_ip6'] + analytics_store = module.params['pn_analytics_store'] + gateway_ip = module.params['pn_gateway_ip'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'switch-setup-modify': + cli += ' %s ' % command + if dns_ip: + cli += ' dns-ip ' + dns_ip + if mgmt_netmask: + cli += ' mgmt-netmask ' + mgmt_netmask + if gateway_ip6: + cli += ' gateway-ip6 ' + gateway_ip6 + if in_band_ip6_assign: + cli += ' in-band-ip6-assign ' + in_band_ip6_assign + if domain_name: + cli += ' domain-name ' + domain_name + if timezone: + cli += ' timezone ' + timezone + if in_band_netmask: + cli += ' in-band-netmask ' + in_band_netmask + if in_band_ip6: + cli += ' in-band-ip6 ' + in_band_ip6 + if in_band_netmask_ip6: + cli += ' in-band-netmask-ip6 ' + in_band_netmask_ip6 + if motd: + cli += ' motd ' + motd + if loopback_ip6: + cli += ' loopback-ip6 ' + loopback_ip6 + if mgmt_ip6_assignment: + cli += ' mgmt-ip6-assignment ' + mgmt_ip6_assignment + if ntp_secondary_server: + cli += ' ntp-secondary-server ' + ntp_secondary_server + if in_band_ip: + cli += ' in-band-ip ' + in_band_ip + if eula_accepted: + cli += ' eula-accepted ' + eula_accepted + if mgmt_ip: + cli += ' mgmt-ip ' + mgmt_ip + if ntp_server: + cli += ' ntp-server ' + ntp_server + if mgmt_ip_assignment: + cli += ' mgmt-ip-assignment ' + mgmt_ip_assignment + if date: + cli += ' date ' + date + if password: + cli += ' password ' + password + if banner: + cli += ' banner ' + banner + if loopback_ip: + cli += ' loopback-ip ' + loopback_ip + if dns_secondary_ip: + cli += ' dns-secondary-ip ' + dns_secondary_ip + if switch_name: + cli += ' switch-name ' + switch_name + if eula_timestamp: + cli += ' eula_timestamp ' + eula_timestamp + if mgmt_netmask_ip6: + cli += ' mgmt-netmask-ip6 ' + mgmt_netmask_ip6 + if mgmt_ip6: + cli += ' mgmt-ip6 ' + mgmt_ip6 + if analytics_store: + cli += ' analytics-store ' + analytics_store + if gateway_ip: + cli += ' gateway-ip ' + gateway_ip + + cli += booleanArgs(force, 'force', 'no-force') + cli += booleanArgs(enable_host_ports, 'enable-host-ports', 'disable-host-ports') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_trunk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_trunk.py new file mode 100644 index 00000000..f7144347 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_trunk.py @@ -0,0 +1,462 @@ +#!/usr/bin/python +""" PN CLI trunk-create/trunk-delete/trunk-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_trunk +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify a trunk. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute trunk-create or trunk-delete command. + - Trunks can be used to aggregate network links at Layer 2 on the local + switch. Use this command to create a new trunk. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create trunk, + 'absent' to delete trunk and 'update' to modify trunk. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - Specify the name for the trunk configuration. + required: true + pn_ports: + description: + - Specify the port number(s) for the link(s) to aggregate into the trunk. + - Required for trunk-create. + pn_speed: + description: + - Specify the port speed or disable the port. + choices: ['disable', '10m', '100m', '1g', '2.5g', '10g', '40g'] + pn_egress_rate_limit: + description: + - Specify an egress port data rate limit for the configuration. + pn_jumbo: + description: + - Specify if the port can receive jumbo frames. + type: bool + pn_lacp_mode: + description: + - Specify the LACP mode for the configuration. + choices: ['off', 'passive', 'active'] + pn_lacp_priority: + description: + - Specify the LACP priority. This is a number between 1 and 65535 with a + default value of 32768. + pn_lacp_timeout: + description: + - Specify the LACP time out as slow (30 seconds) or fast (4seconds). + The default value is slow. + choices: ['slow', 'fast'] + pn_lacp_fallback: + description: + - Specify the LACP fallback mode as bundles or individual. + choices: ['bundle', 'individual'] + pn_lacp_fallback_timeout: + description: + - Specify the LACP fallback timeout in seconds. The range is between 30 + and 60 seconds with a default value of 50 seconds. + pn_edge_switch: + description: + - Specify if the switch is an edge switch. + type: bool + pn_pause: + description: + - Specify if pause frames are sent. + type: bool + pn_description: + description: + - Specify a description for the trunk configuration. + pn_loopback: + description: + - Specify loopback if you want to use loopback. + type: bool + pn_mirror_receive: + description: + - Specify if the configuration receives mirrored traffic. + type: bool + pn_unknown_ucast_level: + description: + - Specify an unknown unicast level in percent. The default value is 100%. + pn_unknown_mcast_level: + description: + - Specify an unknown multicast level in percent. The default value is 100%. + pn_broadcast_level: + description: + - Specify a broadcast level in percent. The default value is 100%. + pn_port_macaddr: + description: + - Specify the MAC address of the port. + pn_loopvlans: + description: + - Specify a list of looping vlans. + pn_routing: + description: + - Specify if the port participates in routing on the network. + type: bool + pn_host: + description: + - Host facing port control setting. + type: bool +''' + +EXAMPLES = """ +- name: Create trunk + community.network.pn_trunk: + state: 'present' + pn_name: 'spine-to-leaf' + pn_ports: '11,12,13,14' + +- name: Delete trunk + community.network.pn_trunk: + state: 'absent' + pn_name: 'spine-to-leaf' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the trunk command. + returned: always + type: list +stderr: + description: The set of error responses from the trunk command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +TRUNK_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the trunk-show command. + If a trunk with given name exists, return TRUNK_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: TRUNK_EXISTS + """ + name = module.params['pn_name'] + + show = cli + ' trunk-show format switch,name no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global TRUNK_EXISTS + if name in out: + TRUNK_EXISTS = True + else: + TRUNK_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'trunk-create' + if state == 'absent': + command = 'trunk-delete' + if state == 'update': + command = 'trunk-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_ports=dict(type='str'), + pn_speed=dict(type='str', + choices=['disable', '10m', '100m', '1g', '2.5g', + '10g', '40g']), + pn_egress_rate_limit=dict(type='str'), + pn_jumbo=dict(type='bool'), + pn_lacp_mode=dict(type='str', choices=[ + 'off', 'passive', 'active']), + pn_lacp_priority=dict(type='int'), + pn_lacp_timeout=dict(type='str', choices=['slow', 'fast']), + pn_lacp_fallback=dict(type='str', choices=[ + 'bundle', 'individual']), + pn_lacp_fallback_timeout=dict(type='str'), + pn_edge_switch=dict(type='bool'), + pn_pause=dict(type='bool'), + pn_description=dict(type='str'), + pn_loopback=dict(type='bool'), + pn_mirror_receive=dict(type='bool'), + pn_unknown_ucast_level=dict(type='str'), + pn_unknown_mcast_level=dict(type='str'), + pn_broadcast_level=dict(type='str'), + pn_port_macaddr=dict(type='str'), + pn_loopvlans=dict(type='str'), + pn_routing=dict(type='bool'), + pn_host=dict(type='bool') + ), + required_if=( + ["state", "present", ["pn_name", "pn_ports"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + name = module.params['pn_name'] + ports = module.params['pn_ports'] + speed = module.params['pn_speed'] + egress_rate_limit = module.params['pn_egress_rate_limit'] + jumbo = module.params['pn_jumbo'] + lacp_mode = module.params['pn_lacp_mode'] + lacp_priority = module.params['pn_lacp_priority'] + lacp_timeout = module.params['pn_lacp_timeout'] + lacp_fallback = module.params['pn_lacp_fallback'] + lacp_fallback_timeout = module.params['pn_lacp_fallback_timeout'] + edge_switch = module.params['pn_edge_switch'] + pause = module.params['pn_pause'] + description = module.params['pn_description'] + loopback = module.params['pn_loopback'] + mirror_receive = module.params['pn_mirror_receive'] + unknown_ucast_level = module.params['pn_unknown_ucast_level'] + unknown_mcast_level = module.params['pn_unknown_mcast_level'] + broadcast_level = module.params['pn_broadcast_level'] + port_macaddr = module.params['pn_port_macaddr'] + loopvlans = module.params['pn_loopvlans'] + routing = module.params['pn_routing'] + host = module.params['pn_host'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'trunk-delete': + + check_cli(module, cli) + if TRUNK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Trunk with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + if command == 'trunk-create': + check_cli(module, cli) + if TRUNK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Trunk with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + # Appending options + if ports: + cli += ' ports ' + ports + + if speed: + cli += ' speed ' + speed + + if egress_rate_limit: + cli += ' egress-rate-limit ' + egress_rate_limit + + if jumbo is True: + cli += ' jumbo ' + if jumbo is False: + cli += ' no-jumbo ' + + if lacp_mode: + cli += ' lacp-mode ' + lacp_mode + + if lacp_priority: + cli += ' lacp-priority ' + lacp_priority + + if lacp_timeout: + cli += ' lacp-timeout ' + lacp_timeout + + if lacp_fallback: + cli += ' lacp-fallback ' + lacp_fallback + + if lacp_fallback_timeout: + cli += ' lacp-fallback-timeout ' + lacp_fallback_timeout + + if edge_switch is True: + cli += ' edge-switch ' + if edge_switch is False: + cli += ' no-edge-switch ' + + if pause is True: + cli += ' pause ' + if pause is False: + cli += ' no-pause ' + + if description: + cli += ' description ' + description + + if loopback is True: + cli += ' loopback ' + if loopback is False: + cli += ' no-loopback ' + + if mirror_receive is True: + cli += ' mirror-receive-only ' + if mirror_receive is False: + cli += ' no-mirror-receive-only ' + + if unknown_ucast_level: + cli += ' unknown-ucast-level ' + unknown_ucast_level + + if unknown_mcast_level: + cli += ' unknown-mcast-level ' + unknown_mcast_level + + if broadcast_level: + cli += ' broadcast-level ' + broadcast_level + + if port_macaddr: + cli += ' port-mac-address ' + port_macaddr + + if loopvlans: + cli += ' loopvlans ' + loopvlans + + if routing is True: + cli += ' routing ' + if routing is False: + cli += ' no-routing ' + + if host is True: + cli += ' host-enable ' + if host is False: + cli += ' host-disable ' + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_user.py new file mode 100644 index 00000000..93673e03 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_user.py @@ -0,0 +1,195 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_user +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete user +description: + - This module can be used to create a user and apply a role, + update a user and delete a user. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(present) to create user and + C(absent) to delete user C(update) to update user. + type: str + required: true + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - local or fabric. + type: str + choices: ['local', 'fabric'] + pn_initial_role: + description: + - initial role for user. + type: str + pn_password: + description: + - plain text password. + type: str + pn_name: + description: + - username. + type: str +''' + +EXAMPLES = """ +- name: Create user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "present" + pn_scope: "fabric" + pn_password: "foo123" + pn_name: "foo" + +- name: Delete user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + +- name: Modify user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "update" + pn_password: "test1234" + pn_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the user command. + returned: always + type: list +stderr: + description: set of error responses from the user command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the user-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' user-show format name no-show-headers' + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='user-create', + absent='user-delete', + update='user-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_initial_role=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name", "pn_password"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + initial_role = module.params['pn_initial_role'] + password = module.params['pn_password'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'user-modify': + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='User with name %s does not exist' % name + ) + if initial_role or scope: + module.fail_json( + failed=True, + msg='Only password can be modified' + ) + + if command == 'user-delete': + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='user with name %s does not exist' % name + ) + + if command == 'user-create': + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='User with name %s already exists' % name + ) + if scope: + cli += ' scope ' + scope + + if initial_role: + cli += ' initial-role ' + initial_role + + if command != 'user-delete': + if password: + cli += ' password ' + password + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vflow_table_profile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vflow_table_profile.py new file mode 100644 index 00000000..6f0d14b5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vflow_table_profile.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vflow_table_profile +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify vflow-table-profile +description: + - This module can be used to modify a vFlow table profile. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the vflow-table-profile. + required: true + type: str + choices: ['update'] + pn_profile: + description: + - type of vFlow profile. + required: false + type: str + choices: ['application', 'ipv6', 'qos'] + pn_hw_tbl: + description: + - hardware table used by vFlow. + required: false + type: str + choices: ['switch-main', 'switch-hash', 'npu-main', 'npu-hash'] + pn_enable: + description: + - enable or disable vflow profile table. + required: false + type: bool +''' + +EXAMPLES = """ +- name: Modify vflow table profile + community.network.pn_vflow_table_profile: + pn_cliswitch: 'sw01' + state: 'update' + pn_profile: 'ipv6' + pn_hw_tbl: 'switch-main' + pn_enable: true + +- name: Modify vflow table profile + community.network.pn_vflow_table_profile: + state: 'update' + pn_profile: 'qos' + pn_hw_tbl: 'switch-main' + pn_enable: false +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vflow-table-profile command. + returned: always + type: list +stderr: + description: set of error responses from the vflow-table-profile command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='vflow-table-profile-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_profile=dict(required=False, type='str', + choices=['application', 'ipv6', 'qos']), + pn_hw_tbl=dict(required=False, type='str', + choices=['switch-main', 'switch-hash', + 'npu-main', 'npu-hash']), + pn_enable=dict(required=False, type='bool'), + ), + required_if=( + ['state', 'update', ['pn_profile', 'pn_hw_tbl']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + profile = module.params['pn_profile'] + hw_tbl = module.params['pn_hw_tbl'] + enable = module.params['pn_enable'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'vflow-table-profile-modify': + cli += ' %s ' % command + if profile: + cli += ' profile ' + profile + if hw_tbl: + cli += ' hw-tbl ' + hw_tbl + + cli += booleanArgs(enable, 'enable', 'disable') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlag.py new file mode 100644 index 00000000..6df7b52e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlag.py @@ -0,0 +1,350 @@ +#!/usr/bin/python +""" PN CLI vlag-create/vlag-delete/vlag-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vlag +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify vlag. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vlag-create/vlag-delete/vlag-modify command. + - A virtual link aggregation group (VLAG) allows links that are physically + connected to two different Pluribus Networks devices to appear as a single + trunk to a third device. The third device can be a switch, server, or any + Ethernet device. A VLAG can provide Layer 2 multipathing, which allows you + to create redundancy by increasing bandwidth, enabling multiple parallel + paths between nodes and loadbalancing traffic where alternative paths exist. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run this command on. + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vlag, + 'absent' to delete vlag and 'update' to modify vlag. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - The C(pn_name) takes a valid name for vlag configuration. + required: true + pn_port: + description: + - Specify the local VLAG port. + - Required for vlag-create. + pn_peer_port: + description: + - Specify the peer VLAG port. + - Required for vlag-create. + pn_mode: + description: + - Specify the mode for the VLAG. Active-standby indicates one side is + active and the other side is in standby mode. Active-active indicates + that both sides of the vlag are up by default. + choices: ['active-active', 'active-standby'] + pn_peer_switch: + description: + - Specify the fabric-name of the peer switch. + pn_failover_action: + description: + - Specify the failover action as move or ignore. + choices: ['move', 'ignore'] + pn_lacp_mode: + description: + - Specify the LACP mode. + choices: ['off', 'passive', 'active'] + pn_lacp_timeout: + description: + - Specify the LACP timeout as slow(30 seconds) or fast(4 seconds). + choices: ['slow', 'fast'] + pn_lacp_fallback: + description: + - Specify the LACP fallback mode as bundles or individual. + choices: ['bundle', 'individual'] + pn_lacp_fallback_timeout: + description: + - Specify the LACP fallback timeout in seconds. The range is between 30 + and 60 seconds with a default value of 50 seconds. +''' + +EXAMPLES = """ +- name: Create a VLAG + community.network.pn_vlag: + state: 'present' + pn_name: spine-to-leaf + pn_port: 'spine01-to-leaf' + pn_peer_port: 'spine02-to-leaf' + pn_peer_switch: spine02 + pn_mode: 'active-active' + +- name: Delete VLAGs + community.network.pn_vlag: + state: 'absent' + pn_name: spine-to-leaf +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vlag command. + returned: always + type: list +stderr: + description: The set of error responses from the vlag command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VLAG_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlag-show command. + If a vlag with given vlag exists, return VLAG_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VLAG_EXISTS + """ + name = module.params['pn_name'] + + show = cli + ' vlag-show format name no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global VLAG_EXISTS + if name in out: + VLAG_EXISTS = True + else: + VLAG_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vlag-create' + if state == 'absent': + command = 'vlag-delete' + if state == 'update': + command = 'vlag-modify' + return command + + +def main(): + """ This section is for argument parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_port=dict(type='str'), + pn_peer_port=dict(type='str'), + pn_mode=dict(type='str', choices=[ + 'active-standby', 'active-active']), + pn_peer_switch=dict(type='str'), + pn_failover_action=dict(type='str', choices=['move', 'ignore']), + pn_lacp_mode=dict(type='str', choices=[ + 'off', 'passive', 'active']), + pn_lacp_timeout=dict(type='str', choices=['slow', 'fast']), + pn_lacp_fallback=dict(type='str', choices=[ + 'bundle', 'individual']), + pn_lacp_fallback_timeout=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_name", "pn_port", "pn_peer_port", + "pn_peer_switch"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Argument accessing + state = module.params['state'] + name = module.params['pn_name'] + port = module.params['pn_port'] + peer_port = module.params['pn_peer_port'] + mode = module.params['pn_mode'] + peer_switch = module.params['pn_peer_switch'] + failover_action = module.params['pn_failover_action'] + lacp_mode = module.params['pn_lacp_mode'] + lacp_timeout = module.params['pn_lacp_timeout'] + lacp_fallback = module.params['pn_lacp_fallback'] + lacp_fallback_timeout = module.params['pn_lacp_fallback_timeout'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'vlag-delete': + + check_cli(module, cli) + if VLAG_EXISTS is False: + module.exit_json( + skipped=True, + msg='VLAG with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + + if command == 'vlag-create': + check_cli(module, cli) + if VLAG_EXISTS is True: + module.exit_json( + skipped=True, + msg='VLAG with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + if port: + cli += ' port %s peer-port %s ' % (port, peer_port) + + if mode: + cli += ' mode ' + mode + + if peer_switch: + cli += ' peer-switch ' + peer_switch + + if failover_action: + cli += ' failover-' + failover_action + '-L2 ' + + if lacp_mode: + cli += ' lacp-mode ' + lacp_mode + + if lacp_timeout: + cli += ' lacp-timeout ' + lacp_timeout + + if lacp_fallback: + cli += ' lacp-fallback ' + lacp_fallback + + if lacp_fallback_timeout: + cli += ' lacp-fallback-timeout ' + lacp_fallback_timeout + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlan.py new file mode 100644 index 00000000..ce0d55fb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vlan.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +""" PN CLI vlan-create/vlan-delete """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vlan +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete a VLAN. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vlan-create or vlan-delete command. + - VLANs are used to isolate network traffic at Layer 2.The VLAN identifiers + 0 and 4095 are reserved and cannot be used per the IEEE 802.1Q standard. + The range of configurable VLAN identifiers is 2 through 4092. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vlan and + 'absent' to delete vlan. + required: True + choices: ['present', 'absent'] + pn_vlanid: + description: + - Specify a VLAN identifier for the VLAN. This is a value between + 2 and 4092. + required: True + pn_scope: + description: + - Specify a scope for the VLAN. + - Required for vlan-create. + choices: ['fabric', 'local'] + pn_description: + description: + - Specify a description for the VLAN. + pn_stats: + description: + - Specify if you want to collect statistics for a VLAN. Statistic + collection is enabled by default. + type: bool + pn_ports: + description: + - Specifies the switch network data port number, list of ports, or range + of ports. Port numbers must ne in the range of 1 to 64. + pn_untagged_ports: + description: + - Specifies the ports that should have untagged packets mapped to the + VLAN. Untagged packets are packets that do not contain IEEE 802.1Q VLAN + tags. +''' + +EXAMPLES = """ +- name: Create a VLAN + community.network.pn_vlan: + state: 'present' + pn_vlanid: 1854 + pn_scope: fabric + +- name: Delete VLANs + community.network.pn_vlan: + state: 'absent' + pn_vlanid: 1854 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vlan command. + returned: always + type: list +stderr: + description: The set of error responses from the vlan command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VLAN_EXISTS = None +MAX_VLAN_ID = 4092 +MIN_VLAN_ID = 2 + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlan-show command. + If a vlan with given vlan id exists, return VLAN_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VLAN_EXISTS + """ + vlanid = module.params['pn_vlanid'] + + show = cli + \ + ' vlan-show id %s format id,scope no-show-headers' % str(vlanid) + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global VLAN_EXISTS + if str(vlanid) in out: + VLAN_EXISTS = True + else: + VLAN_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vlan-create' + if state == 'absent': + command = 'vlan-delete' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_vlanid=dict(required=True, type='int'), + pn_scope=dict(type='str', choices=['fabric', 'local']), + pn_description=dict(type='str'), + pn_stats=dict(type='bool'), + pn_ports=dict(type='str'), + pn_untagged_ports=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_vlanid", "pn_scope"]], + ["state", "absent", ["pn_vlanid"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vlanid = module.params['pn_vlanid'] + scope = module.params['pn_scope'] + description = module.params['pn_description'] + stats = module.params['pn_stats'] + ports = module.params['pn_ports'] + untagged_ports = module.params['pn_untagged_ports'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if not MIN_VLAN_ID <= vlanid <= MAX_VLAN_ID: + module.exit_json( + msg="VLAN id must be between 2 and 4092", + changed=False + ) + + if command == 'vlan-create': + + check_cli(module, cli) + if VLAN_EXISTS is True: + module.exit_json( + skipped=True, + msg='VLAN with id %s already exists' % str(vlanid) + ) + + cli += ' %s id %s scope %s ' % (command, str(vlanid), scope) + + if description: + cli += ' description ' + description + + if stats is True: + cli += ' stats ' + if stats is False: + cli += ' no-stats ' + + if ports: + cli += ' ports ' + ports + + if untagged_ports: + cli += ' untagged-ports ' + untagged_ports + + if command == 'vlan-delete': + + check_cli(module, cli) + if VLAN_EXISTS is False: + module.exit_json( + skipped=True, + msg='VLAN with id %s does not exist' % str(vlanid) + ) + + cli += ' %s id %s ' % (command, str(vlanid)) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter.py new file mode 100644 index 00000000..d640d3cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +""" PN CLI vrouter-create/vrouter-delete/vrouter-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouter +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify a vrouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-create, vrouter-delete, vrouter-modify command. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a virtual router (vRouter) service that forwards + traffic between networks and implements Layer 3 protocols. + - C(vrouter-create) creates a new vRouter service. + - C(vrouter-delete) deletes a vRouter service. + - C(vrouter-modify) modifies a vRouter service. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the CLI on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vrouter, + 'absent' to delete vrouter and 'update' to modify vrouter. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - Specify the name of the vRouter. + required: true + pn_vnet: + description: + - Specify the name of the VNET. + - Required for vrouter-create. + pn_service_type: + description: + - Specify if the vRouter is a dedicated or shared VNET service. + choices: ['dedicated', 'shared'] + pn_service_state: + description: + - Specify to enable or disable vRouter service. + choices: ['enable', 'disable'] + pn_router_type: + description: + - Specify if the vRouter uses software or hardware. + - Note that if you specify hardware as router type, you cannot assign IP + addresses using DHCP. You must specify a static IP address. + choices: ['hardware', 'software'] + pn_hw_vrrp_id: + description: + - Specifies the VRRP ID for a hardware vrouter. + pn_router_id: + description: + - Specify the vRouter IP address. + pn_bgp_as: + description: + - Specify the Autonomous System Number(ASN) if the vRouter runs Border + Gateway Protocol(BGP). + pn_bgp_redistribute: + description: + - Specify how BGP routes are redistributed. + choices: ['static', 'connected', 'rip', 'ospf'] + pn_bgp_max_paths: + description: + - Specify the maximum number of paths for BGP. This is a number between + 1 and 255 or 0 to unset. + pn_bgp_options: + description: + - Specify other BGP options as a whitespaces separated string within + single quotes ''. + pn_rip_redistribute: + description: + - Specify how RIP routes are redistributed. + choices: ['static', 'connected', 'ospf', 'bgp'] + pn_ospf_redistribute: + description: + - Specify how OSPF routes are redistributed. + choices: ['static', 'connected', 'bgp', 'rip'] + pn_ospf_options: + description: + - Specify other OSPF options as a whitespaces separated string within + single quotes ''. + pn_vrrp_track_port: + description: + - Specify list of ports and port ranges. +''' + +EXAMPLES = """ +- name: Create vrouter + community.network.pn_vrouter: + state: 'present' + pn_name: 'ansible-vrouter' + pn_vnet: 'ansible-fab-global' + pn_router_id: 208.74.182.1 + +- name: Delete vrouter + community.network.pn_vrouter: + state: 'absent' + pn_name: 'ansible-vrouter' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouter command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouter command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +VROUTER_NAME_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlan-show command. + A switch can have only one vRouter configuration. + If a vRouter already exists on the given switch, return VROUTER_EXISTS as + True else False. + If a vRouter with the given name exists(on a different switch), return + VROUTER_NAME_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, VROUTER_NAME_EXISTS + """ + name = module.params['pn_name'] + # Global flags + global VROUTER_EXISTS, VROUTER_NAME_EXISTS + + # Get the name of the local switch + location = cli + ' switch-setup-show format switch-name' + location = shlex.split(location) + out = module.run_command(location)[1] + location = out.split()[1] + + # Check for any vRouters on the switch + check_vrouter = cli + ' vrouter-show location %s ' % location + check_vrouter += 'format name no-show-headers' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + + if out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for any vRouters with the given name + show = cli + ' vrouter-show format name no-show-headers ' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if name in out: + VROUTER_NAME_EXISTS = True + else: + VROUTER_NAME_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-create' + if state == 'absent': + command = 'vrouter-delete' + if state == 'update': + command = 'vrouter-modify' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_vnet=dict(type='str'), + pn_service_type=dict(type='str', choices=['dedicated', 'shared']), + pn_service_state=dict(type='str', choices=['enable', 'disable']), + pn_router_type=dict(type='str', choices=['hardware', 'software']), + pn_hw_vrrp_id=dict(type='int'), + pn_router_id=dict(type='str'), + pn_bgp_as=dict(type='int'), + pn_bgp_redistribute=dict(type='str', choices=['static', 'connected', + 'rip', 'ospf']), + pn_bgp_max_paths=dict(type='int'), + pn_bgp_options=dict(type='str'), + pn_rip_redistribute=dict(type='str', choices=['static', 'connected', + 'bgp', 'ospf']), + pn_ospf_redistribute=dict(type='str', choices=['static', 'connected', + 'bgp', 'rip']), + pn_ospf_options=dict(type='str'), + pn_vrrp_track_port=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_name", "pn_vnet"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + name = module.params['pn_name'] + vnet = module.params['pn_vnet'] + service_type = module.params['pn_service_type'] + service_state = module.params['pn_service_state'] + router_type = module.params['pn_router_type'] + hw_vrrp_id = module.params['pn_hw_vrrp_id'] + router_id = module.params['pn_router_id'] + bgp_as = module.params['pn_bgp_as'] + bgp_redistribute = module.params['pn_bgp_redistribute'] + bgp_max_paths = module.params['pn_bgp_max_paths'] + bgp_options = module.params['pn_bgp_options'] + rip_redistribute = module.params['pn_rip_redistribute'] + ospf_redistribute = module.params['pn_ospf_redistribute'] + ospf_options = module.params['pn_ospf_options'] + vrrp_track_port = module.params['pn_vrrp_track_port'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'vrouter-delete': + check_cli(module, cli) + if VROUTER_NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + + if command == 'vrouter-create': + check_cli(module, cli) + if VROUTER_EXISTS is True: + module.exit_json( + skipped=True, + msg='Maximum number of vRouters has been reached on this ' + 'switch' + ) + if VROUTER_NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + if vnet: + cli += ' vnet ' + vnet + + if service_type: + cli += ' %s-vnet-service ' % service_type + + if service_state: + cli += ' ' + service_state + + if router_type: + cli += ' router-type ' + router_type + + if hw_vrrp_id: + cli += ' hw-vrrp-id ' + str(hw_vrrp_id) + + if router_id: + cli += ' router-id ' + router_id + + if bgp_as: + cli += ' bgp-as ' + str(bgp_as) + + if bgp_redistribute: + cli += ' bgp-redistribute ' + bgp_redistribute + + if bgp_max_paths: + cli += ' bgp-max-paths ' + str(bgp_max_paths) + + if bgp_options: + cli += ' %s ' % bgp_options + + if rip_redistribute: + cli += ' rip-redistribute ' + rip_redistribute + + if ospf_redistribute: + cli += ' ospf-redistribute ' + ospf_redistribute + + if ospf_options: + cli += ' %s ' % ospf_options + + if vrrp_track_port: + cli += ' vrrp-track-port ' + vrrp_track_port + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp.py new file mode 100644 index 00000000..8351351b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp.py @@ -0,0 +1,467 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_bgp +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/modify/remove vrouter-bgp +description: + - This module can be used to add Border Gateway Protocol neighbor to a vRouter + modify Border Gateway Protocol neighbor to a vRouter and remove Border Gateway Protocol + neighbor from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-bgp configuration command. + required: false + type: str + choices: ['present', 'absent', 'update'] + default: 'present' + pn_neighbor: + description: + - IP address for BGP neighbor. + required: true + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str + pn_send_community: + description: + - send any community attribute to neighbor. + required: false + type: bool + pn_weight: + description: + - default weight value between 0 and 65535 for the neighbor's routes. + required: false + pn_multi_protocol: + description: + - Multi-protocol features. + required: false + choices: ['ipv4-unicast', 'ipv6-unicast'] + pn_prefix_list_in: + description: + - prefixes used for filtering. + required: false + type: str + pn_route_reflector_client: + description: + - set as route reflector client. + required: false + type: bool + pn_default_originate: + description: + - announce default routes to the neighbor or not. + required: false + type: bool + pn_neighbor_holdtime: + description: + - BGP Holdtime (seconds). + required: false + type: str + pn_connect_retry_interval: + description: + - BGP Connect retry interval (seconds). + required: false + type: str + pn_advertisement_interval: + description: + - Minimum interval between sending BGP routing updates. + required: false + type: str + pn_route_map_out: + description: + - route map out for nbr. + required: false + type: str + pn_update_source: + description: + - IP address of BGP packets required for peering over loopback interface. + required: false + type: str + pn_bfd: + description: + - BFD protocol support for fault detection. + required: false + type: bool + default: False + pn_next_hop_self: + description: + - BGP next hop is self or not. + required: false + type: bool + pn_allowas_in: + description: + - Allow/reject routes with local AS in AS_PATH. + required: false + type: bool + pn_neighbor_keepalive_interval: + description: + - BGP Keepalive interval (seconds). + required: false + type: str + pn_max_prefix: + description: + - maximum number of prefixes. + required: false + type: str + pn_bfd_multihop: + description: + - always use BFD multi-hop port for fault detection. + required: false + type: bool + pn_interface: + description: + - Interface to reach the neighbor. + required: false + type: str + pn_password: + description: + - password for MD5 BGP. + required: false + type: str + pn_route_map_in: + description: + - route map in for nbr. + required: false + type: str + pn_soft_reconfig_inbound: + description: + - soft reset to reconfigure inbound traffic. + required: false + type: bool + pn_override_capability: + description: + - override capability. + required: false + type: bool + pn_max_prefix_warn_only: + description: + - warn if the maximum number of prefixes is exceeded. + required: false + type: bool + pn_ebgp_multihop: + description: + - value for external BGP from 1 to 255. + required: false + type: str + pn_remote_as: + description: + - BGP remote AS from 1 to 4294967295. + required: false + type: str + pn_prefix_list_out: + description: + - prefixes used for filtering outgoing packets. + required: false + type: str + pn_no_route_map_out: + description: + - Remove egress route-map from BGP neighbor. + required: false + type: str + pn_no_route_map_in: + description: + - Remove ingress route-map from BGP neighbor. + required: false + type: str +''' + +EXAMPLES = """ +- name: "Add BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'present' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + pn_remote_as: 65000 + pn_bfd: true + +- name: "Remove BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'absent' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + +- name: "Modify BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'update' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + pn_remote_as: 65000 + pn_bfd: false + pn_allowas_in: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-bgp command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-bgp command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def is_valid(module, param_name, param_val, min_val, max_val): + if int(param_val) < min_val or int(param_val) > max_val: + module.fail_json( + failed=True, + msg='Valid %s range is %s to %s' % (param_name, min_val, max_val) + ) + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-bgp-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If the given neighbor exists on the given vRouter, return NEIGHBOR_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NEIGHBOR_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if neighbor: + # Check for BGP neighbor + show = cli + ' vrouter-bgp-show vrouter-name %s ' % vrouter_name + show += 'format neighbor no-show-headers' + out = run_commands(module, show)[1] + + if out and neighbor in out.split(): + NEIGHBOR_EXISTS = True + else: + NEIGHBOR_EXISTS = False + + return VROUTER_EXISTS, NEIGHBOR_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-bgp-add', + absent='vrouter-bgp-remove', + update='vrouter-bgp-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_neighbor=dict(required=True, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + pn_send_community=dict(required=False, type='bool'), + pn_weight=dict(required=False, type='str'), + pn_multi_protocol=dict(required=False, type='str', choices=['ipv4-unicast', 'ipv6-unicast']), + pn_prefix_list_in=dict(required=False, type='str'), + pn_route_reflector_client=dict(required=False, type='bool'), + pn_default_originate=dict(required=False, type='bool'), + pn_neighbor_holdtime=dict(required=False, type='str'), + pn_connect_retry_interval=dict(required=False, type='str'), + pn_advertisement_interval=dict(required=False, type='str'), + pn_route_map_out=dict(required=False, type='str'), + pn_update_source=dict(required=False, type='str'), + pn_bfd=dict(required=False, type='bool', default=False), + pn_next_hop_self=dict(required=False, type='bool'), + pn_allowas_in=dict(required=False, type='bool'), + pn_neighbor_keepalive_interval=dict(required=False, type='str'), + pn_max_prefix=dict(required=False, type='str'), + pn_bfd_multihop=dict(required=False, type='bool'), + pn_interface=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_route_map_in=dict(required=False, type='str'), + pn_soft_reconfig_inbound=dict(required=False, type='bool'), + pn_override_capability=dict(required=False, type='bool'), + pn_max_prefix_warn_only=dict(required=False, type='bool'), + pn_ebgp_multihop=dict(required=False, type='str'), + pn_remote_as=dict(required=False, type='str'), + pn_prefix_list_out=dict(required=False, type='str'), + pn_no_route_map_out=dict(required=False, type='str'), + pn_no_route_map_in=dict(required=False, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_neighbor", "pn_remote_as"]], + ["state", "absent", ["pn_vrouter_name", "pn_neighbor"]], + ["state", "update", ["pn_vrouter_name", "pn_neighbor"]] + ), + required_one_of=[['pn_send_community', 'pn_weight', 'pn_multi_protocol', + 'pn_prefix_list_in', 'pn_route_reflector_client', 'pn_default_originate', + 'pn_neighbor_holdtime', 'pn_connect_retry_interval', 'pn_advertisement_interval', + 'pn_route_map_out', 'pn_update_source', 'pn_bfd', + 'pn_next_hop_self', 'pn_allowas_in', 'pn_neighbor_keepalive_interval', + 'pn_max_prefix', 'pn_bfd_multihop', 'pn_interface', + 'pn_password', 'pn_route_map_in', 'pn_soft_reconfig_inbound', + 'pn_override_capability', 'pn_max_prefix_warn_only', 'pn_ebgp_multihop', + 'pn_remote_as', 'pn_prefix_list_out', 'pn_no_route_map_out', + 'pn_no_route_map_in']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + neighbor = module.params['pn_neighbor'] + vrouter_name = module.params['pn_vrouter_name'] + send_community = module.params['pn_send_community'] + weight = module.params['pn_weight'] + multi_protocol = module.params['pn_multi_protocol'] + prefix_list_in = module.params['pn_prefix_list_in'] + route_reflector_client = module.params['pn_route_reflector_client'] + default_originate = module.params['pn_default_originate'] + neighbor_holdtime = module.params['pn_neighbor_holdtime'] + connect_retry_interval = module.params['pn_connect_retry_interval'] + advertisement_interval = module.params['pn_advertisement_interval'] + route_map_out = module.params['pn_route_map_out'] + update_source = module.params['pn_update_source'] + bfd = module.params['pn_bfd'] + next_hop_self = module.params['pn_next_hop_self'] + allowas_in = module.params['pn_allowas_in'] + neighbor_keepalive_interval = module.params['pn_neighbor_keepalive_interval'] + max_prefix = module.params['pn_max_prefix'] + bfd_multihop = module.params['pn_bfd_multihop'] + interface = module.params['pn_interface'] + password = module.params['pn_password'] + route_map_in = module.params['pn_route_map_in'] + soft_reconfig_inbound = module.params['pn_soft_reconfig_inbound'] + override_capability = module.params['pn_override_capability'] + max_prefix_warn_only = module.params['pn_max_prefix_warn_only'] + ebgp_multihop = module.params['pn_ebgp_multihop'] + remote_as = module.params['pn_remote_as'] + prefix_list_out = module.params['pn_prefix_list_out'] + no_route_map_out = module.params['pn_no_route_map_out'] + no_route_map_in = module.params['pn_no_route_map_in'] + + command = state_map[state] + + if weight and weight != 'none': + if int(weight) < 1 or int(weight) > 65535: + module.fail_json( + failed=True, + msg='Valid weight range is 1 to 65535' + ) + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + VROUTER_EXISTS, NEIGHBOR_EXISTS = check_cli(module, cli) + + if state: + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if command == 'vrouter-bgp-remove' or command == 'vrouter-bgp-modify': + if NEIGHBOR_EXISTS is False: + module.exit_json( + skipped=True, + msg='BGP neighbor with IP %s does not exist on %s' % (neighbor, vrouter_name) + ) + + if command == 'vrouter-bgp-add': + if NEIGHBOR_EXISTS is True: + module.exit_json( + skipped=True, + msg='BGP neighbor with IP %s already exists on %s' % (neighbor, vrouter_name) + ) + + cli += ' %s vrouter-name %s neighbor %s ' % (command, vrouter_name, neighbor) + + if command == 'vrouter-bgp-add' or command == 'vrouter-bgp-modify': + if weight: + cli += ' weight ' + weight + if multi_protocol: + cli += ' multi-protocol ' + multi_protocol + if prefix_list_in: + cli += ' prefix-list-in ' + prefix_list_in + if neighbor_holdtime: + is_valid(module, 'neighbor holdtime', neighbor_holdtime, '0', '65535') + cli += ' neighbor-holdtime ' + neighbor_holdtime + if connect_retry_interval: + is_valid(module, 'connect retry interval', connect_retry_interval, '0', '65535') + cli += ' connect-retry-interval ' + connect_retry_interval + if advertisement_interval: + is_valid(module, 'advertisement interval', advertisement_interval, '0', '65535') + cli += ' advertisement-interval ' + advertisement_interval + if route_map_out: + cli += ' route-map-out ' + route_map_out + if update_source: + cli += ' update-source ' + update_source + if neighbor_keepalive_interval: + is_valid(module, 'neighbor keepalive interval', neighbor_keepalive_interval, '0', '65535') + cli += ' neighbor-keepalive-interval ' + neighbor_keepalive_interval + if max_prefix: + cli += ' max-prefix ' + max_prefix + if interface: + cli += ' interface ' + interface + if password: + cli += ' password ' + password + if route_map_in: + cli += ' route-map-in ' + route_map_in + if ebgp_multihop: + is_valid(module, 'ebgp_multihop', ebgp_multihop, '1', '255') + cli += ' ebgp-multihop ' + ebgp_multihop + if remote_as: + cli += ' remote-as ' + remote_as + if prefix_list_out: + cli += ' prefix-list-out ' + prefix_list_out + cli += booleanArgs(send_community, 'send-community', 'no-send-community') + cli += booleanArgs(route_reflector_client, 'route-reflector-client', 'no-route-reflector-client') + cli += booleanArgs(default_originate, 'default-originate', 'no-default-originate') + cli += booleanArgs(bfd, 'bfd', 'no-bfd') + cli += booleanArgs(next_hop_self, 'next-hop-self', 'no-next-hop-self') + cli += booleanArgs(allowas_in, 'allowas-in', 'no-allowas-in') + cli += booleanArgs(bfd_multihop, 'bfd-multihop', 'no-bfd-multihop') + cli += booleanArgs(soft_reconfig_inbound, 'soft-reconfig-inbound', 'no-soft-reconfig-inbound') + cli += booleanArgs(override_capability, 'override-capability', 'no-override-capability') + cli += booleanArgs(max_prefix_warn_only, 'max-prefix-warn-only', 'no-max-prefix-warn-only') + + if command == 'vrouter-bgp-modify': + if no_route_map_out: + cli += ' no-route-map-out ' + no_route_map_out + if no_route_map_in: + cli += ' no-route-map-in ' + no_route_map_in + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp_network.py new file mode 100644 index 00000000..7ccb62a3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_bgp_network.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_bgp_network +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-bgp-network +description: + - This module can be used to add Border Gateway Protocol network to a vRouter + and remove Border Gateway Protocol network from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add bgp network and + C(absent) to remove bgp network. + required: true + type: str + choices: ['present', 'absent'] + pn_netmask: + description: + - BGP network mask. + required: false + type: str + pn_network: + description: + - IP address for BGP network. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add network to bgp + community.network.pn_vrouter_bgp_network: + pn_cliswitch: "sw01" + state: "present" + pn_vrouter_name: "foo-vrouter" + pn_network: '10.10.10.10' + pn_netmask: '31' + +- name: Remove network from bgp + community.network.pn_vrouter_bgp_network: + pn_cliswitch: "sw01" + state: "absent" + pn_vrouter_name: "foo-vrouter" + pn_network: '10.10.10.10' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-bgp-network command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-bgp-network command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for pim ssm config using the vrouter-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_vrouter_name'] + network = module.params['pn_network'] + + show = cli + cli += ' vrouter-show name %s format name no-show-headers' % name + rc, out, err = run_commands(module, cli) + VROUTER_EXISTS = '' if out else None + + cli = show + cli += ' vrouter-bgp-network-show vrouter-name %s network %s format network no-show-headers' % (name, network) + out = run_commands(module, cli)[1] + out = out.split() + NETWORK_EXISTS = True if network in out[-1] else False + + return NETWORK_EXISTS, VROUTER_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-bgp-network-add', + absent='vrouter-bgp-network-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_netmask=dict(required=False, type='str'), + pn_network=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_vrouter_name', 'pn_netmask', 'pn_network']], + ['state', 'absent', ['pn_vrouter_name', 'pn_network']], + ), + ) + + # Accessing the arguments + state = module.params['state'] + cliswitch = module.params['pn_cliswitch'] + netmask = module.params['pn_netmask'] + network = module.params['pn_network'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NETWORK_EXISTS, VROUTER_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is None: + module.fail_json( + failed=True, + msg='vRouter %s does not exists' % vrouter_name + ) + + if command == 'vrouter-bgp-network-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Network %s already added to bgp' % network + ) + + if command == 'vrouter-bgp-network-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Network %s does not exists' % network + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if netmask: + cli += ' netmask ' + netmask + if network: + cli += ' network ' + network + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_interface_ip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_interface_ip.py new file mode 100644 index 00000000..1de6e507 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_interface_ip.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_interface_ip +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-interface-ip +description: + - This module can be used to add an IP address on interface from a vRouter + or remove an IP address on interface from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to addvrouter-interface-ip + and C(absent) to remove vrouter-interface-ip. + required: true + type: str + choices: ['present', 'absent'] + pn_bd: + description: + - interface Bridge Domain. + required: false + type: str + pn_netmask: + description: + - netmask. + required: false + type: str + pn_vnet: + description: + - interface VLAN VNET. + required: false + type: str + pn_ip: + description: + - IP address. + required: false + type: str + pn_nic: + description: + - virtual NIC assigned to interface. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add vrouter interface to nic + community.network.pn_vrouter_interface_ip: + state: "present" + pn_cliswitch: "sw01" + pn_vrouter_name: "foo-vrouter" + pn_ip: "2620:0:1651:1::30" + pn_netmask: "127" + pn_nic: "eth0.4092" + +- name: Remove vrouter interface to nic + community.network.pn_vrouter_interface_ip: + state: "absent" + pn_cliswitch: "sw01" + pn_vrouter_name: "foo-vrouter" + pn_ip: "2620:0:1651:1::30" + pn_nic: "eth0.4092" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-interface-ip command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-interface-ip command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If an interface with the given ip exists on the given vRouter, + return INTERFACE_EXISTS as True else False. This is required for + vrouter-interface-add. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-interface-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_ip'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip2 %s format ip2,nic no-show-headers' % interface_ip + out = run_commands(module, show)[1] + + if out and interface_ip in out.split(' ')[-2]: + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + if nic_str: + # Check for nic + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'format nic no-show-headers' + out = run_commands(module, show)[1] + + if out: + out = out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-interface-ip-add', + absent='vrouter-interface-ip-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_bd=dict(required=False, type='str'), + pn_netmask=dict(required=False, type='str'), + pn_vnet=dict(required=False, type='str'), + pn_ip=dict(required=False, type='str'), + pn_nic=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_nic", "pn_ip", "pn_netmask"]], + ["state", "absent", ["pn_vrouter_name", "pn_nic", "pn_ip"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + bd = module.params['pn_bd'] + netmask = module.params['pn_netmask'] + vnet = module.params['pn_vnet'] + ip = module.params['pn_ip'] + nic = module.params['pn_nic'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if NIC_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter with nic %s does not exist' % nic + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if command == 'vrouter-interface-ip-add': + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with interface ip %s exist' % ip + ) + cli += ' nic %s ip %s ' % (nic, ip) + + if bd: + cli += ' bd ' + bd + if netmask: + cli += ' netmask ' + netmask + if vnet: + cli += ' vnet ' + vnet + + if command == 'vrouter-interface-ip-remove': + if INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with interface ip %s does not exist' % ip + ) + if nic: + cli += ' nic %s ' % nic + if ip: + cli += ' ip %s ' % ip.split('/')[0] + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_loopback_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_loopback_interface.py new file mode 100644 index 00000000..1f806c00 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_loopback_interface.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_loopback_interface +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-loopback-interface +description: + - This module can be used to add loopback interface to a vRouter or + remove loopback interface from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add vrouter-loopback-interface + and C(absent) to remove vrouter-loopback-interface. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_ip: + description: + - loopback IP address. + required: true + type: str + pn_index: + description: + - loopback index from 1 to 255. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: Add vrouter loopback interface + community.network.pn_vrouter_loopback_interface: + state: "present" + pn_cliswitch: "sw01" + pn_vrouter_name: "sw01-vrouter" + pn_ip: "192.168.10.1" + +- name: Remove vrouter loopback interface + community.network.pn_vrouter_loopback_interface: + state: "absent" + pn_cliswitch: "sw01" + pn_vrouter_name: "sw01-vrouter" + pn_ip: "192.168.10.1" + pn_index: "2" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-loopback-interface command. + returned: always + type: list +stderr: + description: set of error response from the vrouter-loopback-interface + command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, INTERFACE_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_ip'] + + # Check for vRouter + check_vrouter = 'vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-loopback-interface-show ' + show += 'vrouter-name %s ' % vrouter_name + show += 'format ip no-show-headers' + out = run_commands(module, show)[1] + + if out and interface_ip in out.split(): + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + return VROUTER_EXISTS, INTERFACE_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-loopback-interface-add', + absent='vrouter-loopback-interface-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_ip=dict(required=True, type='str'), + pn_index=dict(required=False, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_ip"]], + ["state", "absent", ["pn_vrouter_name", "pn_ip", "pn_index"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ip = module.params['pn_ip'] + index = module.params['pn_index'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, INTERFACE_EXISTS = check_cli(module, cli) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if index and (int(index) < 1 or int(index) > 255): + module.fail_json( + failed=True, + msg='index should be in range 1 to 255' + ) + + if index and state == 'present': + show = 'vrouter-loopback-interface-show format index parsable-delim ,' + out = run_commands(module, show)[1] + if out: + out = out.split() + for res in out: + res = res.strip().split(',') + if index in res: + module.fail_json( + failed=True, + msg='index with value %s exist' % index + ) + + if command == 'vrouter-loopback-interface-add': + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with loopback ip %s exist' % ip + ) + if ip: + cli += ' ip ' + ip + if index: + cli += ' index ' + index + + if command == 'vrouter-loopback-interface-remove': + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with loopback ip %s doesnt exist' % ip + ) + + if index: + cli += ' index ' + index + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf.py new file mode 100644 index 00000000..f32b02ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_ospf +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-ospf +description: + - This module can be used to add OSPF protocol to vRouter + and remove OSPF protocol from a vRouter +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-ospf configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_netmask: + description: + - OSPF network IP address netmask. + required: false + type: str + pn_ospf_area: + description: + - stub area number for the configuration. + required: false + type: str + pn_network: + description: + - OSPF network IP address. + required: true + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: Add OSPF to vRouter + community.network.pn_vrouter_ospf: + state: 'present' + pn_vrouter_name: 'sw01-vrouter' + pn_network: '105.104.104.1' + pn_netmask: '24' + pn_ospf_area: '0' +- name: "Remove OSPF to vRouter" + community.network.pn_vrouter_ospf: + state: 'absent' + pn_vrouter_name: 'sw01-vrouter' + pn_network: '105.104.104.1' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-ospf command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-ospf command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If an OSPF network with the given ip exists on the given vRouter, + return NETWORK_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :return Booleans: VROUTER_EXISTS, NETWORK_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + network = module.params['pn_network'] + show_cli = pn_cli(module) + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + # Check for OSPF networks + check_network = cli + ' vrouter-ospf-show vrouter-name %s ' % vrouter_name + check_network += 'format network no-show-headers' + out = run_commands(module, check_network)[1] + + if out and network in out: + NETWORK_EXISTS = True + else: + NETWORK_EXISTS = False + + return VROUTER_EXISTS, NETWORK_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-ospf-add', + absent='vrouter-ospf-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_netmask=dict(required=False, type='str'), + pn_ospf_area=dict(required=False, type='str'), + pn_network=dict(required=True, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ['pn_vrouter_name', 'pn_network', 'pn_netmask', 'pn_ospf_area']], + ["state", "absent", ['pn_vrouter_name', 'pn_network']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + netmask = module.params['pn_netmask'] + ospf_area = module.params['pn_ospf_area'] + network = module.params['pn_network'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + VROUTER_EXISTS, NETWORK_EXISTS = check_cli(module, cli) + + if state: + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if command == 'vrouter-ospf-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='OSPF with network %s dose not exists' % network + ) + cli += ' %s vrouter-name %s network %s' % (command, vrouter_name, network) + + if command == 'vrouter-ospf-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='OSPF with network %s already exists' % network + ) + if netmask: + cli += ' netmask ' + netmask + if ospf_area: + cli += ' ospf-area ' + ospf_area + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf6.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf6.py new file mode 100644 index 00000000..9d2e0041 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_ospf6.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_ospf6 +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-ospf6 +description: + - This module can be used to add interface ip to OSPF6 protocol + or remove interface ip from OSPF6 protocol on vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add vrouter-ospf6 and + C(absent) to remove interface from vrouter-ospf6. + required: true + type: str + choices: ['present', 'absent'] + pn_ospf6_area: + description: + - area id for this interface in IPv4 address format. + required: false + type: str + pn_nic: + description: + - OSPF6 control for this interface. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add vrouter interface nic to ospf6 + community.network.pn_vrouter_ospf6: + pn_cliswitch: "sw01" + state: "present" + pn_vrouter_name: "foo-vrouter" + pn_nic: "eth0.4092" + pn_ospf6_area: "0.0.0.0" + +- name: Remove vrouter interface nic to ospf6 + community.network.pn_vrouter_ospf6: + pn_cliswitch: "sw01" + state: "absent" + pn_vrouter_name: "foo-vrouter" + pn_nic: "eth0.4092" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-ospf6 command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-ospf6 command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-ospf6-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if nic_str: + # Check for nic + show = cli + ' vrouter-ospf6-show vrouter-name %s format nic no-show-headers' % vrouter_name + out = run_commands(module, show)[1] + + if out: + out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-ospf6-add', + absent='vrouter-ospf6-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_ospf6_area=dict(required=False, type='str'), + pn_nic=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_nic", + "pn_ospf6_area"]], + ["state", "absent", ["pn_vrouter_name", "pn_nic"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ospf6_area = module.params['pn_ospf6_area'] + nic = module.params['pn_nic'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if command == 'vrouter-ospf6-add': + if NIC_EXISTS is True: + module.exit_json( + skipped=True, + msg='OSPF6 with nic %s already exist' % nic + ) + if nic: + cli += ' nic %s' % nic + if ospf6_area: + cli += ' ospf6-area %s ' % ospf6_area + + if command == 'vrouter-ospf6-remove': + if NIC_EXISTS is False: + module.exit_json( + skipped=True, + msg='OSPF6 with nic %s does not exist' % nic + ) + if nic: + cli += ' nic %s' % nic + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_packet_relay.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_packet_relay.py new file mode 100644 index 00000000..64256d84 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_packet_relay.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_packet_relay +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-packet-relay +description: + - This module can be used to add packet relay configuration for DHCP on vrouter + and remove packet relay configuration for DHCP on vrouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-packet-relay configuration command. + required: false + choices: ['present', 'absent'] + type: str + default: 'present' + pn_forward_ip: + description: + - forwarding IP address. + required: true + type: str + pn_nic: + description: + - NIC. + required: true + type: str + pn_forward_proto: + description: + - protocol type to forward packets. + required: false + type: str + choices: ['dhcp'] + default: 'dhcp' + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: VRouter packet relay add + community.network.pn_vrouter_packet_relay: + pn_cliswitch: "sw01" + pn_forward_ip: "192.168.10.1" + pn_nic: "eth0.4092" + pn_vrouter_name: "sw01-vrouter" + +- name: VRouter packet relay remove + community.network.pn_vrouter_packet_relay: + pn_cliswitch: "sw01" + state: "absent" + pn_forward_ip: "192.168.10.1" + pn_nic: "eth0.4092" + pn_vrouter_name: "sw01-vrouter" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-packet-relay command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-packet-relay command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = 'vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if nic_str: + # Check for nic + show = 'vrouter-interface-show vrouter-name %s format nic no-show-headers' % vrouter_name + out = run_commands(module, show)[1] + if out: + out = out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-packet-relay-add', + absent='vrouter-packet-relay-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_forward_ip=dict(required=True, type='str'), + pn_nic=dict(required=True, type='str'), + pn_forward_proto=dict(required=False, type='str', choices=['dhcp'], default='dhcp'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_forward_ip", "pn_nic", "pn_forward_proto"]], + ["state", "absent", ["pn_vrouter_name", "pn_forward_ip", "pn_nic", "pn_forward_proto"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + forward_ip = module.params['pn_forward_ip'] + nic = module.params['pn_nic'] + forward_proto = module.params['pn_forward_proto'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if NIC_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter with nic %s does not exist' % nic + ) + + if command == 'vrouter-packet-relay-add' or command == 'vrouter-packet-relay-remove': + cli += ' %s' % command + cli += ' vrouter-name %s nic %s' % (vrouter_name, nic) + cli += ' forward-proto %s forward-ip %s' % (forward_proto, forward_ip) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_pim_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_pim_config.py new file mode 100644 index 00000000..a6cd6acd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouter_pim_config.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_pim_config +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify vrouter-pim-config +description: + - This module can be used to modify pim parameters. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify the vrouter-pim-config. + required: true + type: str + choices: ['update'] + pn_query_interval: + description: + - igmp query interval in seconds. + required: false + type: str + pn_querier_timeout: + description: + - igmp querier timeout in seconds. + required: false + type: str + pn_hello_interval: + description: + - hello interval in seconds. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Pim config modify + community.network.pn_vrouter_pim_config: + pn_cliswitch: '192.168.1.1' + pn_query_interval: '10' + pn_querier_timeout: '30' + state: 'update' + pn_vrouter_name: 'ansible-spine1-vrouter' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-pim-config command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-pim-config command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for pim ssm config using the vrouter-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_vrouter_name'] + + show = cli + cli += ' vrouter-show format name no-show-headers ' + out = run_commands(module, cli)[1] + if out: + out = out.split() + if name in out: + pass + else: + return False + + cli = show + cli += ' vrouter-show name %s format proto-multi no-show-headers' % name + out = run_commands(module, cli)[1] + if out: + out = out.split() + + return True if 'none' not in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='vrouter-pim-config-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_query_interval=dict(required=False, type='str'), + pn_querier_timeout=dict(required=False, type='str'), + pn_hello_interval=dict(required=False, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ), + required_if=( + ['state', 'update', ['pn_vrouter_name']], + ), + required_one_of=[['pn_query_interval', + 'pn_querier_timeout', + 'pn_hello_interval']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + query_interval = module.params['pn_query_interval'] + querier_timeout = module.params['pn_querier_timeout'] + hello_interval = module.params['pn_hello_interval'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'vrouter-pim-config-modify': + PIM_SSM_CONFIG = check_cli(module, cli) + if PIM_SSM_CONFIG is False: + module.exit_json( + skipped=True, + msg='vrouter proto-multi is not configured/vrouter is not created' + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + if querier_timeout: + cli += ' querier-timeout ' + querier_timeout + if hello_interval: + cli += ' hello-interval ' + hello_interval + if query_interval: + cli += ' query-interval ' + query_interval + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterbgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterbgp.py new file mode 100644 index 00000000..5e1d4ffa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterbgp.py @@ -0,0 +1,485 @@ +#!/usr/bin/python +""" PN-CLI vrouter-bgp-add/vrouter-bgp-remove/vrouter-bgp-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterbgp +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove/modify vrouter-bgp. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-bgp-add, vrouter-bgp-remove, vrouter-bgp-modify command. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a vRouter service that forwards traffic between + networks and implements Layer 4 protocols. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add bgp, + 'absent' to remove bgp and 'update' to modify bgp. + required: True + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify a name for the vRouter service. + required: True + pn_neighbor: + description: + - Specify a neighbor IP address to use for BGP. + - Required for vrouter-bgp-add. + pn_remote_as: + description: + - Specify the remote Autonomous System(AS) number. This value is between + 1 and 4294967295. + - Required for vrouter-bgp-add. + pn_next_hop_self: + description: + - Specify if the next-hop is the same router or not. + type: bool + pn_password: + description: + - Specify a password, if desired. + pn_ebgp: + description: + - Specify a value for external BGP to accept or attempt BGP connections + to external peers, not directly connected, on the network. This is a + value between 1 and 255. + pn_prefix_listin: + description: + - Specify the prefix list to filter traffic inbound. + pn_prefix_listout: + description: + - Specify the prefix list to filter traffic outbound. + pn_route_reflector: + description: + - Specify if a route reflector client is used. + type: bool + pn_override_capability: + description: + - Specify if you want to override capability. + type: bool + pn_soft_reconfig: + description: + - Specify if you want a soft reconfiguration of inbound traffic. + type: bool + pn_max_prefix: + description: + - Specify the maximum number of prefixes. + pn_max_prefix_warn: + description: + - Specify if you want a warning message when the maximum number of + prefixes is exceeded. + type: bool + pn_bfd: + description: + - Specify if you want BFD protocol support for fault detection. + type: bool + pn_multiprotocol: + description: + - Specify a multi-protocol for BGP. + choices: ['ipv4-unicast', 'ipv6-unicast'] + pn_weight: + description: + - Specify a default weight value between 0 and 65535 for the neighbor + routes. + pn_default_originate: + description: + - Specify if you want announce default routes to the neighbor or not. + type: bool + pn_keepalive: + description: + - Specify BGP neighbor keepalive interval in seconds. + pn_holdtime: + description: + - Specify BGP neighbor holdtime in seconds. + pn_route_mapin: + description: + - Specify inbound route map for neighbor. + pn_route_mapout: + description: + - Specify outbound route map for neighbor. +''' + +EXAMPLES = """ +- name: Add vrouter-bgp + community.network.pn_vrouterbgp: + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_neighbor: 104.104.104.1 + pn_remote_as: 1800 + +- name: Remove vrouter-bgp + community.network.pn_vrouterbgp: + state: 'absent' + pn_name: 'ansible-vrouter' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterbpg command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouterbgp command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +NEIGHBOR_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-bgp-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If a BGP neighbor with the given ip exists on the given vRouter, + return NEIGHBOR_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, NEIGHBOR_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + # Global flags + global VROUTER_EXISTS, NEIGHBOR_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for BGP neighbors + show = cli + ' vrouter-bgp-show vrouter-name %s ' % vrouter_name + show += 'format neighbor no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if neighbor in out: + NEIGHBOR_EXISTS = True + else: + NEIGHBOR_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-bgp-add' + if state == 'absent': + command = 'vrouter-bgp-remove' + if state == 'update': + command = 'vrouter-bgp-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_neighbor=dict(type='str'), + pn_remote_as=dict(type='str'), + pn_next_hop_self=dict(type='bool'), + pn_password=dict(type='str', no_log=True), + pn_ebgp=dict(type='int'), + pn_prefix_listin=dict(type='str'), + pn_prefix_listout=dict(type='str'), + pn_route_reflector=dict(type='bool'), + pn_override_capability=dict(type='bool'), + pn_soft_reconfig=dict(type='bool'), + pn_max_prefix=dict(type='int'), + pn_max_prefix_warn=dict(type='bool'), + pn_bfd=dict(type='bool'), + pn_multiprotocol=dict(type='str', + choices=['ipv4-unicast', 'ipv6-unicast']), + pn_weight=dict(type='int'), + pn_default_originate=dict(type='bool'), + pn_keepalive=dict(type='str'), + pn_holdtime=dict(type='str'), + pn_route_mapin=dict(type='str'), + pn_route_mapout=dict(type='str') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_neighbor", "pn_remote_as"]], + ["state", "absent", + ["pn_vrouter_name", "pn_neighbor"]], + ["state", "update", + ["pn_vrouter_name", "pn_neighbor"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + remote_as = module.params['pn_remote_as'] + next_hop_self = module.params['pn_next_hop_self'] + password = module.params['pn_password'] + ebgp = module.params['pn_ebgp'] + prefix_listin = module.params['pn_prefix_listin'] + prefix_listout = module.params['pn_prefix_listout'] + route_reflector = module.params['pn_route_reflector'] + override_capability = module.params['pn_override_capability'] + soft_reconfig = module.params['pn_soft_reconfig'] + max_prefix = module.params['pn_max_prefix'] + max_prefix_warn = module.params['pn_max_prefix_warn'] + bfd = module.params['pn_bfd'] + multiprotocol = module.params['pn_multiprotocol'] + weight = module.params['pn_weight'] + default_originate = module.params['pn_default_originate'] + keepalive = module.params['pn_keepalive'] + holdtime = module.params['pn_holdtime'] + route_mapin = module.params['pn_route_mapin'] + route_mapout = module.params['pn_route_mapout'] + + # Building the CLI command string + cli = pn_cli(module) + + command = get_command_from_state(state) + if command == 'vrouter-bgp-remove': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NEIGHBOR_EXISTS is False: + module.exit_json( + skipped=True, + msg=('BGP neighbor with IP %s does not exist on %s' + % (neighbor, vrouter_name)) + ) + cli += (' %s vrouter-name %s neighbor %s ' + % (command, vrouter_name, neighbor)) + + else: + + if command == 'vrouter-bgp-add': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NEIGHBOR_EXISTS is True: + module.exit_json( + skipped=True, + msg=('BGP neighbor with IP %s already exists on %s' + % (neighbor, vrouter_name)) + ) + + cli += (' %s vrouter-name %s neighbor %s ' + % (command, vrouter_name, neighbor)) + + if remote_as: + cli += ' remote-as ' + str(remote_as) + + if next_hop_self is True: + cli += ' next-hop-self ' + if next_hop_self is False: + cli += ' no-next-hop-self ' + + if password: + cli += ' password ' + password + + if ebgp: + cli += ' ebgp-multihop ' + str(ebgp) + + if prefix_listin: + cli += ' prefix-list-in ' + prefix_listin + + if prefix_listout: + cli += ' prefix-list-out ' + prefix_listout + + if route_reflector is True: + cli += ' route-reflector-client ' + if route_reflector is False: + cli += ' no-route-reflector-client ' + + if override_capability is True: + cli += ' override-capability ' + if override_capability is False: + cli += ' no-override-capability ' + + if soft_reconfig is True: + cli += ' soft-reconfig-inbound ' + if soft_reconfig is False: + cli += ' no-soft-reconfig-inbound ' + + if max_prefix: + cli += ' max-prefix ' + str(max_prefix) + + if max_prefix_warn is True: + cli += ' max-prefix-warn-only ' + if max_prefix_warn is False: + cli += ' no-max-prefix-warn-only ' + + if bfd is True: + cli += ' bfd ' + if bfd is False: + cli += ' no-bfd ' + + if multiprotocol: + cli += ' multi-protocol ' + multiprotocol + + if weight: + cli += ' weight ' + str(weight) + + if default_originate is True: + cli += ' default-originate ' + if default_originate is False: + cli += ' no-default-originate ' + + if keepalive: + cli += ' neighbor-keepalive-interval ' + keepalive + + if holdtime: + cli += ' neighbor-holdtime ' + holdtime + + if route_mapin: + cli += ' route-map-in ' + route_mapin + + if route_mapout: + cli += ' route-map-out ' + route_mapout + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterif.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterif.py new file mode 100644 index 00000000..173565a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterif.py @@ -0,0 +1,490 @@ +#!/usr/bin/python +""" PN-CLI vrouter-interface-add/remove/modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterif +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove/modify vrouter-interface. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-interface-add, vrouter-interface-remove, + vrouter-interface-modify command. + - You configure interfaces to vRouter services on a fabric, cluster, + standalone switch or virtual network(VNET). +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add vrouter interface, + 'absent' to remove vrouter interface and 'update' to modify vrouter + interface. + required: True + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify the name of the vRouter interface. + required: True + pn_vlan: + description: + - Specify the VLAN identifier. This is a value between 1 and 4092. + pn_interface_ip: + description: + - Specify the IP address of the interface in x.x.x.x/n format. + pn_assignment: + description: + - Specify the DHCP method for IP address assignment. + choices: ['none', 'dhcp', 'dhcpv6', 'autov6'] + pn_vxlan: + description: + - Specify the VXLAN identifier. This is a value between 1 and 16777215. + pn_interface: + description: + - Specify if the interface is management, data or span interface. + choices: ['mgmt', 'data', 'span'] + pn_alias: + description: + - Specify an alias for the interface. + pn_exclusive: + description: + - Specify if the interface is exclusive to the configuration. Exclusive + means that other configurations cannot use the interface. Exclusive is + specified when you configure the interface as span interface and allows + higher throughput through the interface. + type: bool + required: False + pn_nic_enable: + description: + - Specify if the NIC is enabled or not + type: bool + pn_vrrp_id: + description: + - Specify the ID for the VRRP interface. The IDs on both vRouters must be + the same IS number. + pn_vrrp_priority: + description: + - Specify the priority for the VRRP interface. This is a value between + 1 (lowest) and 255 (highest). + pn_vrrp_adv_int: + description: + - Specify a VRRP advertisement interval in milliseconds. The range is + from 30 to 40950 with a default value of 1000. + pn_l3port: + description: + - Specify a Layer 3 port for the interface. + pn_secondary_macs: + description: + - Specify a secondary MAC address for the interface. + pn_nic_str: + description: + - Specify the type of NIC. Used for vrouter-interface remove/modify. +''' + +EXAMPLES = """ +- name: Add vrouter-interface + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 + pn_vlan: 101 + +- name: Add VRRP.. + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 + pn_vrrp_ip: 101.101.101.1/24 + pn_vrrp_priority: 100 + pn_vlan: 101 + +- name: Remove vrouter-interface + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'absent' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterif command. + returned: on success + type: list +stderr: + description: The set of error responses from the vrouterif command. + returned: on error + type: str +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +INTERFACE_EXISTS = None +NIC_EXISTS = None +VRRP_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If an interface with the given ip exists on the given vRouter, + return INTERFACE_EXISTS as True else False. This is required for + vrouter-interface-add. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-interface-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + nic_str = module.params['pn_nic_str'] + + # Global flags + global VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip %s format ip,nic no-show-headers' % interface_ip + show = shlex.split(show) + out = module.run_command(show)[1] + if out: + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + if nic_str: + # Check for nic + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += ' format nic no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + if nic_str in out: + NIC_EXISTS = True + else: + NIC_EXISTS = False + + +def get_nic(module, cli): + """ + This module checks if VRRP interface can be added. If No, return VRRP_EXISTS + as True. + If Yes, fetch the nic string from the primary interface and return nic and + VRRP_EXISTS as False. + :param module: + :param cli: + :return: nic, Global Boolean: VRRP_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + + global VRRP_EXISTS + + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip %s format ip,nic no-show-headers' % interface_ip + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if len(out) > 3: + VRRP_EXISTS = True + return None + else: + nic = out[2] + VRRP_EXISTS = False + return nic + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-interface-add' + if state == 'absent': + command = 'vrouter-interface-remove' + if state == 'update': + command = 'vrouter-interface-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_vlan=dict(type='int'), + pn_interface_ip=dict(required=True, type='str'), + pn_assignment=dict(type='str', + choices=['none', 'dhcp', 'dhcpv6', 'autov6']), + pn_vxlan=dict(type='int'), + pn_interface=dict(type='str', choices=['mgmt', 'data', 'span']), + pn_alias=dict(type='str'), + pn_exclusive=dict(type='bool'), + pn_nic_enable=dict(type='bool'), + pn_vrrp_id=dict(type='int'), + pn_vrrp_priority=dict(type='int'), + pn_vrrp_adv_int=dict(type='str'), + pn_l3port=dict(type='str'), + pn_secondary_macs=dict(type='str'), + pn_nic_str=dict(type='str') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_interface_ip"]], + ["state", "absent", + ["pn_vrouter_name", "pn_nic_str"]] + ), + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + vlan = module.params['pn_vlan'] + interface_ip = module.params['pn_interface_ip'] + assignment = module.params['pn_assignment'] + vxlan = module.params['pn_vxlan'] + interface = module.params['pn_interface'] + alias = module.params['pn_alias'] + exclusive = module.params['pn_exclusive'] + nic_enable = module.params['pn_nic_enable'] + vrrp_id = module.params['pn_vrrp_id'] + vrrp_priority = module.params['pn_vrrp_priority'] + vrrp_adv_int = module.params['pn_vrrp_adv_int'] + l3port = module.params['pn_l3port'] + secondary_macs = module.params['pn_secondary_macs'] + nic_str = module.params['pn_nic_str'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + check_cli(module, cli) + if command == 'vrouter-interface-add': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if vrrp_id: + vrrp_primary = get_nic(module, cli) + if VRRP_EXISTS is True: + module.exit_json( + skipped=True, + msg=('VRRP interface on %s already exists. Check ' + 'the IP addresses' % vrouter_name) + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + cli += (' ip %s vrrp-primary %s vrrp-id %s ' + % (interface_ip, vrrp_primary, str(vrrp_id))) + if vrrp_priority: + cli += ' vrrp-priority %s ' % str(vrrp_priority) + if vrrp_adv_int: + cli += ' vrrp-adv-int %s ' % vrrp_adv_int + + else: + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg=('vRouter interface on %s already exists. Check the ' + 'IP addresses' % vrouter_name) + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + cli += ' ip %s ' % interface_ip + + if vlan: + cli += ' vlan ' + str(vlan) + + if l3port: + cli += ' l3-port ' + l3port + + if assignment: + cli += ' assignment ' + assignment + + if vxlan: + cli += ' vxlan ' + str(vxlan) + + if interface: + cli += ' if ' + interface + + if alias: + cli += ' alias-on ' + alias + + if exclusive is True: + cli += ' exclusive ' + if exclusive is False: + cli += ' no-exclusive ' + + if nic_enable is True: + cli += ' nic-enable ' + if nic_enable is False: + cli += ' nic-disable ' + + if secondary_macs: + cli += ' secondary-macs ' + secondary_macs + + if command == 'vrouter-interface-remove': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NIC_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter interface with nic %s does not exist' % nic_str + ) + cli += ' %s vrouter-name %s nic %s ' % (command, vrouter_name, nic_str) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterlbif.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterlbif.py new file mode 100644 index 00000000..379f3142 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vrouterlbif.py @@ -0,0 +1,331 @@ +#!/usr/bin/python +""" PN CLI vrouter-loopback-interface-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterlbif +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove vrouter-loopback-interface. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-loopback-interface-add, vrouter-loopback-interface-remove + commands. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a virtual router (vRouter) service that forwards + traffic between networks and implements Layer 3 protocols. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add vrouter loopback + interface and 'absent' to remove vrouter loopback interface. + required: True + choices: ['present', 'absent'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: True + pn_index: + description: + - Specify the interface index from 1 to 255. + pn_interface_ip: + description: + - Specify the IP address. + required: True +''' + +EXAMPLES = """ +- name: Add vrouter-loopback-interface + community.network.pn_vrouterlbif: + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: '104.104.104.1' + +- name: Remove vrouter-loopback-interface + community.network.pn_vrouterlbif: + state: 'absent' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: '104.104.104.1' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterlb command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouterlb command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +LB_INTERFACE_EXISTS = None +# Index range +MIN_INDEX = 1 +MAX_INDEX = 255 + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the + vrouter-loopback-interface-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If a loopback interface with the given ip exists on the given vRouter, + return LB_INTERFACE_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, LB_INTERFACE_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + + # Global flags + global VROUTER_EXISTS, LB_INTERFACE_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for loopback interface + show = (cli + ' vrouter-loopback-interface-show vrouter-name %s format ip ' + 'no-show-headers' % vrouter_name) + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if interface_ip in out: + LB_INTERFACE_EXISTS = True + else: + LB_INTERFACE_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-loopback-interface-add' + if state == 'absent': + command = 'vrouter-loopback-interface-remove' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_vrouter_name=dict(required=True, type='str'), + pn_interface_ip=dict(type='str'), + pn_index=dict(type='int') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_interface_ip"]], + ["state", "absent", + ["pn_vrouter_name", "pn_interface_ip"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + index = module.params['pn_index'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if index: + if not MIN_INDEX <= index <= MAX_INDEX: + module.exit_json( + msg="Index must be between 1 and 255", + changed=False + ) + index = str(index) + + if command == 'vrouter-loopback-interface-remove': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if LB_INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg=('Loopback interface with IP %s does not exist on %s' + % (interface_ip, vrouter_name)) + ) + if not index: + # To remove loopback interface, we need the index. + # If index is not specified, get the Loopback interface index + # using the given interface ip. + get_index = cli + get_index += (' vrouter-loopback-interface-show vrouter-name %s ip ' + '%s ' % (vrouter_name, interface_ip)) + get_index += 'format index no-show-headers' + + get_index = shlex.split(get_index) + out = module.run_command(get_index)[1] + index = out.split()[1] + + cli += ' %s vrouter-name %s index %s' % (command, vrouter_name, index) + + if command == 'vrouter-loopback-interface-add': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg=('vRouter %s does not exist' % vrouter_name) + ) + if LB_INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg=('Loopback interface with IP %s already exists on %s' + % (interface_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s ip %s' + % (command, vrouter_name, interface_ip)) + if index: + cli += ' index %s ' % index + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vtep.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vtep.py new file mode 100644 index 00000000..cc0a172c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/netvisor/pn_vtep.py @@ -0,0 +1,198 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vtep +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete vtep +description: + - This module can be used to create a vtep and delete a vtep. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vtep configuration command. + required: false + choices: ['present', 'absent'] + type: str + default: 'present' + pn_name: + description: + - vtep name. + required: false + type: str + pn_ip: + description: + - Primary IP address. + required: false + type: str + pn_vrouter_name: + description: + - name of the vrouter service. + required: false + type: str + pn_virtual_ip: + description: + - Virtual/Secondary IP address. + required: false + type: str + pn_location: + description: + - switch name. + required: false + type: str + pn_switch_in_cluster: + description: + - Tells whether switch in cluster or not. + required: false + type: bool + default: True +''' + +EXAMPLES = """ +- name: Create vtep + community.network.pn_vtep: + pn_cliswitch: 'sw01' + pn_name: 'foo' + pn_vrouter_name: 'foo-vrouter' + pn_ip: '22.22.22.2' + pn_location: 'sw01' + pn_virtual_ip: "22.22.22.1" + +- name: Delete vtep + community.network.pn_vtep: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'foo' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vtep command. + returned: always + type: list +stderr: + description: set of error responses from the vtep command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vtep-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' vtep-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vtep-create', + absent='vtep-delete' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_name=dict(required=False, type='str'), + pn_ip=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + pn_virtual_ip=dict(required=False, type='str'), + pn_location=dict(required=False, type='str'), + pn_switch_in_cluster=dict(required=False, type='bool', default='True') + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_name", "pn_ip", "pn_vrouter_name", "pn_location"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + ip = module.params['pn_ip'] + vrouter_name = module.params['pn_vrouter_name'] + virtual_ip = module.params['pn_virtual_ip'] + location = module.params['pn_location'] + switch_in_cluster = module.params['pn_switch_in_cluster'] + + if switch_in_cluster and not virtual_ip and state == 'present': + module.exit_json( + failed=True, + msg='virtual ip is required when switch is in cluster' + ) + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command == 'vtep-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='vtep with name %s does not exist' % name + ) + + if command == 'vtep-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='vtpe with name %s already exists' % name + ) + + cli += 'vrouter-name %s ' % vrouter_name + cli += 'ip %s ' % ip + cli += 'location %s ' % location + + if virtual_ip: + cli += 'virtual-ip %s ' % virtual_ip + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_command.py new file mode 100644 index 00000000..b47e2493 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_command.py @@ -0,0 +1,219 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: nos_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme Networks NOS +description: + - Sends arbitrary commands to a NOS device and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.nos_config) to configure NOS devices. +notes: + - Tested against NOS 7.2.0 + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote NOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.nos_command: + commands: show version + + - name: Run show version and check to see if output contains NOS + community.network.nos_command: + commands: show version + wait_for: result[0] contains NOS + + - name: Run multiple commands on remote nodes + community.network.nos_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.nos_command: + commands: + - show version + - show interface status + wait_for: + - result[0] contains NOS + - result[1] contains Te + - name: Run command that requires answering a prompt + community.network.nos_command: + commands: + - command: 'clear sessions' + prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' + answer: y +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +__metaclass__ = type + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='nos_command does not support running config mode ' + 'commands. Please use nos_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_config.py new file mode 100644 index 00000000..38e4fb7e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_config.py @@ -0,0 +1,389 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: nos_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme Networks NOS configuration sections +description: + - Extreme NOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with NOS configuration sections in + a deterministic way. +notes: + - Tested against NOS 7.2.0 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the NOS device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + default: "@" + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.nos_config: + lines: logging raslog console INFO + +- name: Configure interface settings + community.network.nos_config: + lines: + - description test interface + - ip address 172.31.1.1/24 + parents: + - interface TenGigabitEthernet 104/0/1 + +- name: Configure multiple interfaces + community.network.nos_config: + lines: + - lacp timeout long + parents: "{{ item }}" + with_items: + - interface TenGigabitEthernet 104/0/1 + - interface TenGigabitEthernet 104/0/2 + +- name: Load new acl into device + community.network.nos_config: + lines: + - seq 10 permit ip host 1.1.1.1 any log + - seq 20 permit ip host 2.2.2.2 any log + - seq 30 permit ip host 3.3.3.3 any log + - seq 40 permit ip host 4.4.4.4 any log + - seq 50 permit ip host 5.5.5.5 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.nos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Configurable backup path + community.network.nos_config: + lines: logging raslog console INFO + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/nos_config.2018-02-12@18:26:34 +""" + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands, get_config, load_config +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + +__metaclass__ = type + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def get_running_config(module, current_config=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + src = module.params['src'] + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + diff_against=dict(choices=['intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + configobjs = candidate.difference(config, path=path, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_facts.py new file mode 100644 index 00000000..efa991e5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nos/nos_facts.py @@ -0,0 +1,453 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nos_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from devices running Extreme NOS +description: + - Collects a base set of device facts from a remote device that + is running NOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against NOS 7.2.0 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +# Collect all facts from the device +- community.network.nos_facts: + gather_subset: all + +# Collect only the config and default facts +- community.network.nos_facts: + gather_subset: + - config + +# Do not collect hardware facts +- community.network.nos_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS) + + def run(self, cmd): + return run_commands(self.module, cmd) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show inventory chassis', + r'show running-config | include host\-name' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[2] + if data: + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'Network Operating System Version: (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'SID:(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'SN:(\S+)', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show process memory summary' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) + + def parse_memtotal(self, data): + match = re.search(r'TotalMemory: (\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'Total Free: (\d+)\s', data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interface', + 'show ipv6 interface brief', + r'show lldp nei detail | inc ^Local\ Interface|^Remote\ Interface|^System\ Name' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + + data = self.responses[1] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + else: + self.facts['neighbors'] = dict() + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = list() + primary_address = addresses = [] + primary_address = re.findall(r'Primary Internet Address is (\S+)', value, re.M) + addresses = re.findall(r'Secondary Internet Address is (\S+)', value, re.M) + if not primary_address: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + # Only gets primary IPv6 addresses + def populate_ipv6_interfaces(self, data): + interfaces = re.split('=+', data)[1].strip() + matches = re.findall(r'(\S+ \S+) +[\w-]+.+\s+([\w:/]+/\d+)', interfaces, re.M) + for match in matches: + interface = match[0] + self.facts['interfaces'][interface]['ipv6'] = list() + address, masklen = match[1].split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][interface]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Local Interface: ') + if not lines: + return facts + for line in lines: + match = re.search(r'(\w+ \S+)\s+\(Local Int.+?\)[\s\S]+Remote Interface: (\S+.+?) \(Remote Int.+?\)[\s\S]+System Name: (\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for interface in data.split('\n\n'): + match = re.match(r'^(\S+ \S+)', interface, re.M) + if not match: + continue + else: + parsed[match.group(1)] = interface + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is Ethernet, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Primary Internet Address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+) bytes', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'LineSpeed Actual\s+:\s(.+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'Duplex: (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.match(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config"], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_action.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_action.py new file mode 100644 index 00000000..74ca48e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_action.py @@ -0,0 +1,184 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_action +extends_documentation_fragment: +- community.network.nso + +short_description: Executes Cisco NSO actions and verifies output. +description: + - This module provides support for executing Cisco NSO actions and then + verifying that the output is as expected. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO action. + required: true + input: + description: > + NSO action parameters. + output_required: + description: > + Required output parameters. + output_invalid: + description: > + List of result parameter names that will cause the task to fail if they + are present. + validate_strict: + description: > + If set to true, the task will fail if any output parameters not in + output_required is present in the output. + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Sync NSO device + community.network.nso_action: + url: http://localhost:8080/jsonrpc + username: username + password: password + path: /ncs:devices/device{ce0}/sync-from + input: {} +''' + +RETURN = ''' +output: + description: Action output + returned: success + type: dict + sample: + result: true +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import normalize_value +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoAction(object): + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, + path, input, + output_required, output_invalid, validate_strict): + self._check_mode = check_mode + self._client = client + self._path = path + self._input = input + self._output_required = output_required + self._output_invalid = output_invalid + self._validate_strict = validate_strict + + def main(self): + schema = self._client.get_schema(path=self._path) + if schema['data']['kind'] != 'action': + raise ModuleFailException('{0} is not an action'.format(self._path)) + + input_schema = [c for c in schema['data']['children'] + if c.get('is_action_input', False)] + + for key, value in self._input.items(): + child = next((c for c in input_schema if c['name'] == key), None) + if child is None: + raise ModuleFailException('no parameter {0}'.format(key)) + + # implement type validation in the future + + if self._check_mode: + return {} + else: + return self._run_and_verify() + + def _run_and_verify(self): + output = self._client.run_action(None, self._path, self._input) + for key, value in self._output_required.items(): + if key not in output: + raise ModuleFailException('{0} not in result'.format(key)) + + n_value = normalize_value(value, output[key], key) + if value != n_value: + msg = '{0} value mismatch. expected {1} got {2}'.format( + key, value, n_value) + raise ModuleFailException(msg) + + for key in self._output_invalid.keys(): + if key in output: + raise ModuleFailException('{0} not allowed in result'.format(key)) + + if self._validate_strict: + for name in output.keys(): + if name not in self._output_required: + raise ModuleFailException('{0} not allowed in result'.format(name)) + + return output + + +def main(): + argument_spec = dict( + path=dict(required=True), + input=dict(required=False, type='dict', default={}), + output_required=dict(required=False, type='dict', default={}), + output_invalid=dict(required=False, type='dict', default={}), + validate_strict=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_action = NsoAction( + module.check_mode, client, + p['path'], + p['input'], + p['output_required'], + p['output_invalid'], + p['validate_strict']) + try: + verify_version(client, NsoAction.REQUIRED_VERSIONS) + + output = nso_action.main() + client.logout() + module.exit_json(changed=True, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_config.py new file mode 100644 index 00000000..eb938251 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_config.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_config +extends_documentation_fragment: +- community.network.nso + +short_description: Manage Cisco NSO configuration and service synchronization. +description: + - This module provides support for managing configuration in Cisco NSO and + can also ensure services are in sync. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as | display json converted to YAML. List entries can + be annotated with a __state entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true +''' + +EXAMPLES = ''' +- name: Create L3VPN + community.network.nso_config: + url: http://localhost:8080/jsonrpc + username: username + password: password + data: + l3vpn:vpn: + l3vpn: + - name: company + route-distinguisher: 999 + endpoint: + - id: branch-office1 + ce-device: ce6 + ce-interface: GigabitEthernet0/12 + ip-network: 10.10.1.0/24 + bandwidth: 12000000 + as-number: 65101 + - id: branch-office2 + ce-device: ce1 + ce-interface: GigabitEthernet0/11 + ip-network: 10.7.7.0/24 + bandwidth: 6000000 + as-number: 65102 + - id: branch-office3 + __state: absent + __state: in-sync +''' + +RETURN = ''' +changes: + description: List of changes + returned: always + type: complex + sample: + - path: "/l3vpn:vpn/l3vpn{example}/endpoint{office}/bandwidth" + from: '6000000' + to: '12000000' + type: set + contains: + path: + description: Path to value changed + returned: always + type: str + from: + description: Previous value if any, else null + returned: When previous value is present on value change + type: str + to: + description: Current value if any, else null. + returned: When new value is present on value change + type: + description: Type of change. create|delete|set|re-deploy +diffs: + description: List of sync changes + returned: always + type: complex + sample: + - path: "/l3vpn:vpn/l3vpn{example}" + diff: |2 + devices { + device pe3 { + config { + alu:service { + vprn 65101 { + bgp { + group example-ce6 { + - peer-as 65102; + + peer-as 65101; + } + } + } + } + } + } + } + contains: + path: + description: keypath to service changed + returned: always + type: str + diff: + description: configuration difference triggered the re-deploy + returned: always + type: str +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import State, ValueBuilder +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoConfig(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, data): + self._check_mode = check_mode + self._client = client + self._data = data + + self._changes = [] + self._diffs = [] + + def main(self): + # build list of values from configured data + value_builder = ValueBuilder(self._client) + for key, value in self._data.items(): + value_builder.build('', key, value) + + self._data_write(value_builder.values) + + # check sync AFTER configuration is written + sync_values = self._sync_check(value_builder.values) + self._sync_ensure(sync_values) + + return self._changes, self._diffs + + def _data_write(self, values): + th = self._client.get_trans(mode='read_write') + + for value in values: + if value.state == State.SET: + self._client.set_value(th, value.path, value.value) + elif value.state == State.PRESENT: + self._client.create(th, value.path) + elif value.state == State.ABSENT: + self._client.delete(th, value.path) + + changes = self._client.get_trans_changes(th) + for change in changes: + if change['op'] == 'value_set': + self._changes.append({ + 'path': change['path'], + 'from': change['old'] or None, + 'to': change['value'], + 'type': 'set' + }) + elif change['op'] in ('created', 'deleted'): + self._changes.append({ + 'path': change['path'], + 'type': change['op'][:-1] + }) + + if len(changes) > 0: + warnings = self._client.validate_commit(th) + if len(warnings) > 0: + raise NsoException( + 'failed to validate transaction with warnings: {0}'.format( + ', '.join((str(warning) for warning in warnings))), {}) + + if self._check_mode or len(changes) == 0: + self._client.delete_trans(th) + else: + self._client.commit(th) + + def _sync_check(self, values): + sync_values = [] + + for value in values: + if value.state in (State.CHECK_SYNC, State.IN_SYNC): + action = 'check-sync' + elif value.state in (State.DEEP_CHECK_SYNC, State.DEEP_IN_SYNC): + action = 'deep-check-sync' + else: + action = None + + if action is not None: + action_path = '{0}/{1}'.format(value.path, action) + action_params = {'outformat': 'cli'} + resp = self._client.run_action(None, action_path, action_params) + if len(resp) > 0: + sync_values.append( + ValueBuilder.Value(value.path, value.state, resp[0]['value'])) + + return sync_values + + def _sync_ensure(self, sync_values): + for value in sync_values: + if value.state in (State.CHECK_SYNC, State.DEEP_CHECK_SYNC): + raise NsoException( + '{0} out of sync, diff {1}'.format(value.path, value.value), {}) + + action_path = '{0}/{1}'.format(value.path, 're-deploy') + if not self._check_mode: + result = self._client.run_action(None, action_path) + if not result: + raise NsoException( + 'failed to re-deploy {0}'.format(value.path), {}) + + self._changes.append({'path': value.path, 'type': 're-deploy'}) + self._diffs.append({'path': value.path, 'diff': value.value}) + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_config = NsoConfig(module.check_mode, client, p['data']) + try: + verify_version(client, NsoConfig.REQUIRED_VERSIONS) + + changes, diffs = nso_config.main() + client.logout() + + changed = len(changes) > 0 + module.exit_json( + changed=changed, changes=changes, diffs=diffs) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_query.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_query.py new file mode 100644 index 00000000..2f7b53ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_query.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_query +extends_documentation_fragment: +- community.network.nso + +short_description: Query data from Cisco NSO. +description: + - This module provides support for querying data from Cisco NSO using XPath. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + xpath: + description: XPath selection relative to the root. + required: true + fields: + description: > + List of fields to select from matching nodes. + required: true +''' + +EXAMPLES = ''' +- name: Select device name and description + community.network.nso_query: + url: http://localhost:8080/jsonrpc + username: username + password: password + xpath: /ncs:devices/device + fields: + - name + - description +''' + +RETURN = ''' +output: + description: Value of matching nodes + returned: success + type: list +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoQuery(object): + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, xpath, fields): + self._check_mode = check_mode + self._client = client + self._xpath = xpath + self._fields = fields + + def main(self): + if self._check_mode: + return [] + else: + return self._client.query(self._xpath, self._fields) + + +def main(): + argument_spec = dict( + xpath=dict(required=True, type='str'), + fields=dict(required=True, type='list') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_query = NsoQuery( + module.check_mode, client, + p['xpath'], p['fields']) + try: + verify_version(client, NsoQuery.REQUIRED_VERSIONS) + + output = nso_query.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_show.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_show.py new file mode 100644 index 00000000..c7c05fb6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_show.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_show +extends_documentation_fragment: +- community.network.nso + +short_description: Displays data from Cisco NSO. +description: + - This module provides support for displaying data from Cisco NSO. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.1.9 or higher, 4.2.6 or higher, + 4.3.7 or higher, 4.4.5 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO data. + required: true + operational: + description: > + Controls whether or not operational data is included in the result. + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Show devices including operational data + community.network.nso_show: + url: http://localhost:8080/jsonrpc + username: username + password: password + path: /ncs:devices/device + operational: true +''' + +RETURN = ''' +output: + description: Configuration + returned: success + type: dict +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoShow(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 5), + (4, 3, 7), + (4, 2, 6), + (4, 1, 9), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, path, operational): + self._check_mode = check_mode + self._client = client + self._path = path + self._operational = operational + + def main(self): + if self._check_mode: + return {} + else: + return self._client.show_config(self._path, self._operational) + + +def main(): + argument_spec = dict( + path=dict(required=True, type='str'), + operational=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_show = NsoShow( + module.check_mode, client, + p['path'], p['operational']) + try: + verify_version(client, NsoShow.REQUIRED_VERSIONS) + + output = nso_show.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_verify.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_verify.py new file mode 100644 index 00000000..637aa6ed --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nso/nso_verify.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_verify +extends_documentation_fragment: +- community.network.nso + +short_description: Verifies Cisco NSO configuration. +description: + - This module provides support for verifying Cisco NSO configuration is in + compliance with specified values. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as C(| display json) converted to YAML. List entries can + be annotated with a C(__state) entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true +''' + +EXAMPLES = ''' +- name: Verify interface is up + nso_config: + url: http://localhost:8080/jsonrpc + username: username + password: password + data: + ncs:devices: + device: + - name: ce0 + live-status: + interfaces: + interface: + - name: GigabitEthernet0/12 + - state: Up +''' + +RETURN = ''' +violations: + description: List of value violations + returned: failed + type: complex + sample: + - path: /ncs:devices/device{ce0}/description + expected-value: CE0 example + value: null + contains: + path: + description: Path to the value in violation + returned: always + type: str + expected-value: + description: Expected value of path + returned: always + type: str + value: + description: Current value of path + returned: always + type: str +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import normalize_value +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import State, ValueBuilder +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoVerify(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, client, data): + self._client = client + self._data = data + + def main(self): + violations = [] + + # build list of values from configured data + value_builder = ValueBuilder(self._client, 'verify') + for key, value in self._data.items(): + value_builder.build('', key, value) + + for expected_value in value_builder.values: + if expected_value.state == State.PRESENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'present', + 'value': 'absent' + }) + elif expected_value.state == State.ABSENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'absent', + 'value': 'present' + }) + elif expected_value.state == State.SET: + try: + value = self._client.get_value(expected_value.path)['value'] + except NsoException as ex: + if ex.error.get('type', '') == 'data.not_found': + value = None + else: + raise + + # handle different types properly + n_value = normalize_value( + expected_value.value, value, expected_value.path) + if n_value != expected_value.value: + # if the value comparison 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( + expected_value.state, expected_value.path)) + + return violations + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_verify = NsoVerify(client, p['data']) + try: + verify_version(client, NsoVerify.REQUIRED_VERSIONS) + + violations = nso_verify.main() + client.logout() + + num_violations = len(violations) + if num_violations > 0: + msg = '{0} value{1} differ'.format( + num_violations, num_violations > 1 and 's' or '') + module.fail_json(msg=msg, violations=violations) + else: + module.exit_json(changed=False) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nuage/nuage_vspk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nuage/nuage_vspk.py new file mode 100644 index 00000000..39fdae7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/nuage/nuage_vspk.py @@ -0,0 +1,1016 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Nokia +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nuage_vspk +short_description: Manage Nuage VSP environments +description: + - Manage or find Nuage VSP entities, this includes create, update, delete, assign, unassign and find, with all supported properties. +author: Philippe Dellaert (@pdellaert) +options: + auth: + description: + - Dict with the authentication information required to connect to a Nuage VSP environment. + - Requires a I(api_username) parameter (example csproot). + - Requires either a I(api_password) parameter (example csproot) or a I(api_certificate) and I(api_key) parameters, + which point to the certificate and key files for certificate based authentication. + - Requires a I(api_enterprise) parameter (example csp). + - Requires a I(api_url) parameter (example https://10.0.0.10:8443). + - Requires a I(api_version) parameter (example v4_0). + required: true + type: + description: + - The type of entity you want to work on (example Enterprise). + - This should match the objects CamelCase class name in VSPK-Python. + - This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html). + required: true + id: + description: + - The ID of the entity you want to work on. + - In combination with I(command=find), it will only return the single entity. + - In combination with I(state), it will either update or delete this entity. + - Will take precedence over I(match_filter) and I(properties) whenever an entity needs to be found. + parent_id: + description: + - The ID of the parent of the entity you want to work on. + - When I(state) is specified, the entity will be gathered from this parent, if it exists, unless an I(id) is specified. + - When I(command=find) is specified, the entity will be searched for in this parent, unless an I(id) is specified. + - If specified, I(parent_type) also needs to be specified. + parent_type: + description: + - The type of parent the ID is specified for (example Enterprise). + - This should match the objects CamelCase class name in VSPK-Python. + - This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html). + - If specified, I(parent_id) also needs to be specified. + state: + description: + - Specifies the desired state of the entity. + - If I(state=present), in case the entity already exists, will update the entity if it is needed. + - If I(state=present), in case the relationship with the parent is a member relationship, will assign the entity as a member of the parent. + - If I(state=absent), in case the relationship with the parent is a member relationship, will unassign the entity as a member of the parent. + - Either I(state) or I(command) needs to be defined, both can not be defined at the same time. + choices: + - present + - absent + command: + description: + - Specifies a command to be executed. + - With I(command=find), if I(parent_id) and I(parent_type) are defined, it will only search within the parent. Otherwise, if allowed, + will search in the root object. + - With I(command=find), if I(id) is specified, it will only return the single entity matching the id. + - With I(command=find), otherwise, if I(match_filter) is define, it will use that filter to search. + - With I(command=find), otherwise, if I(properties) are defined, it will do an AND search using all properties. + - With I(command=change_password), a password of a user can be changed. Warning - In case the password is the same as the existing, + it will throw an error. + - With I(command=wait_for_job), the module will wait for a job to either have a status of SUCCESS or ERROR. In case an ERROR status is found, + the module will exit with an error. + - With I(command=wait_for_job), the job will always be returned, even if the state is ERROR situation. + - Either I(state) or I(command) needs to be defined, both can not be defined at the same time. + choices: + - find + - change_password + - wait_for_job + - get_csp_enterprise + match_filter: + description: + - A filter used when looking (both in I(command) and I(state) for entities, in the format the Nuage VSP API expects. + - If I(match_filter) is defined, it will take precedence over the I(properties), but not on the I(id) + properties: + description: + - Properties are the key, value pairs of the different properties an entity has. + - If no I(id) and no I(match_filter) is specified, these are used to find or determine if the entity exists. + children: + description: + - Can be used to specify a set of child entities. + - A mandatory property of each child is the I(type). + - Supported optional properties of each child are I(id), I(properties) and I(match_filter). + - The function of each of these properties is the same as in the general task definition. + - This can be used recursively + - Only useable in case I(state=present). +notes: + - Check mode is supported, but with some caveats. It will not do any changes, and if possible try to determine if it is able do what is requested. + - In case a parent id is provided from a previous task, it might be empty and if a search is possible on root, it will do so, which can impact performance. +requirements: + - Python 2.7 + - Supports Nuage VSP 4.0Rx & 5.x.y + - Proper VSPK-Python installed for your Nuage version + - Tested with NuageX U(https://nuagex.io) +''' + +EXAMPLES = ''' +# This can be executed as a single role, with the following vars +# vars: +# auth: +# api_username: csproot +# api_password: csproot +# api_enterprise: csp +# api_url: https://10.0.0.10:8443 +# api_version: v5_0 +# enterprise_name: Ansible-Enterprise +# enterprise_new_name: Ansible-Updated-Enterprise +# +# or, for certificate based authentication +# vars: +# auth: +# api_username: csproot +# api_certificate: /path/to/user-certificate.pem +# api_key: /path/to/user-Key.pem +# api_enterprise: csp +# api_url: https://10.0.0.10:8443 +# api_version: v5_0 +# enterprise_name: Ansible-Enterprise +# enterprise_new_name: Ansible-Updated-Enterprise + +# Creating a new enterprise +- name: Create Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + state: present + properties: + name: "{{ enterprise_name }}-basic" + register: nuage_enterprise + +# Checking if an Enterprise with the new name already exists +- name: Check if an Enterprise exists with the new name + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + command: find + properties: + name: "{{ enterprise_new_name }}-basic" + ignore_errors: yes + register: nuage_check_enterprise + +# Updating an enterprise's name +- name: Update Enterprise name + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + id: "{{ nuage_enterprise.id }}" + state: present + properties: + name: "{{ enterprise_new_name }}-basic" + when: nuage_check_enterprise is failed + +# Creating a User in an Enterprise +- name: Create admin user + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + state: present + match_filter: "userName == 'ansible-admin'" + properties: + email: "ansible@localhost.local" + first_name: "Ansible" + last_name: "Admin" + password: "ansible-password" + user_name: "ansible-admin" + register: nuage_user + +# Updating password for User +- name: Update admin password + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + command: change_password + properties: + password: "ansible-new-password" + ignore_errors: yes + +# Finding a group in an enterprise +- name: Find Administrators group in Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Group + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + command: find + properties: + name: "Administrators" + register: nuage_group + +# Assign the user to the group +- name: Assign admin user to administrators + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + parent_id: "{{ nuage_group.id }}" + parent_type: Group + state: present + +# Creating multiple DomainTemplates +- name: Create multiple DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + state: present + properties: + name: "{{ item }}" + description: "Created by Ansible" + with_items: + - "Template-1" + - "Template-2" + +# Finding all DomainTemplates +- name: Fetching all DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + command: find + register: nuage_domain_templates + +# Deleting all DomainTemplates +- name: Deleting all found DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + state: absent + id: "{{ item.ID }}" + with_items: "{{ nuage_domain_templates.entities }}" + when: nuage_domain_templates.entities is defined + +# Unassign user from group +- name: Unassign admin user to administrators + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + parent_id: "{{ nuage_group.id }}" + parent_type: Group + state: absent + +# Deleting an enterprise +- name: Delete Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + id: "{{ nuage_enterprise.id }}" + state: absent + +# Setup an enterprise with Children +- name: Setup Enterprise and domain structure + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + state: present + properties: + name: "Child-based-Enterprise" + children: + - type: L2DomainTemplate + properties: + name: "Unmanaged-Template" + children: + - type: EgressACLTemplate + match_filter: "name == 'Allow All'" + properties: + name: "Allow All" + active: true + default_allow_ip: true + default_allow_non_ip: true + default_install_acl_implicit_rules: true + description: "Created by Ansible" + priority_type: "TOP" + - type: IngressACLTemplate + match_filter: "name == 'Allow All'" + properties: + name: "Allow All" + active: true + default_allow_ip: true + default_allow_non_ip: true + description: "Created by Ansible" + priority_type: "TOP" +''' + +RETURN = ''' +id: + description: The id of the entity that was found, created, updated or assigned. + returned: On state=present and command=find in case one entity was found. + type: str + sample: bae07d8d-d29c-4e2b-b6ba-621b4807a333 +entities: + description: A list of entities handled. Each element is the to_dict() of the entity. + returned: On state=present and find, with only one element in case of state=present or find=one. + type: list + sample: [{ + "ID": acabc435-3946-4117-a719-b8895a335830", + "assocEntityType": "DOMAIN", + "command": "BEGIN_POLICY_CHANGES", + "creationDate": 1487515656000, + "entityScope": "ENTERPRISE", + "externalID": null, + "lastUpdatedBy": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e", + "lastUpdatedDate": 1487515656000, + "owner": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e", + "parameters": null, + "parentID": "a22fddb9-3da4-4945-bd2e-9d27fe3d62e0", + "parentType": "domain", + "progress": 0.0, + "result": null, + "status": "RUNNING" + }] +''' + +import time + +try: + import importlib + HAS_IMPORTLIB = True +except ImportError: + HAS_IMPORTLIB = False + +try: + from bambou.exceptions import BambouHTTPError + HAS_BAMBOU = True +except ImportError: + HAS_BAMBOU = False + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_COMMANDS = ['find', 'change_password', 'wait_for_job', 'get_csp_enterprise'] +VSPK = None + + +class NuageEntityManager(object): + """ + This module is meant to manage an entity in a Nuage VSP Platform + """ + + def __init__(self, module): + self.module = module + self.auth = module.params['auth'] + self.api_username = None + self.api_password = None + self.api_enterprise = None + self.api_url = None + self.api_version = None + self.api_certificate = None + self.api_key = None + self.type = module.params['type'] + + self.state = module.params['state'] + self.command = module.params['command'] + self.match_filter = module.params['match_filter'] + self.entity_id = module.params['id'] + self.parent_id = module.params['parent_id'] + self.parent_type = module.params['parent_type'] + self.properties = module.params['properties'] + self.children = module.params['children'] + + self.entity = None + self.entity_class = None + self.parent = None + self.parent_class = None + self.entity_fetcher = None + + self.result = { + 'state': self.state, + 'id': self.entity_id, + 'entities': [] + } + self.nuage_connection = None + + self._verify_api() + self._verify_input() + self._connect_vspk() + self._find_parent() + + def _connect_vspk(self): + """ + Connects to a Nuage API endpoint + """ + try: + # Connecting to Nuage + if self.api_certificate and self.api_key: + self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, enterprise=self.api_enterprise, api_url=self.api_url, + certificate=(self.api_certificate, self.api_key)) + else: + self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, password=self.api_password, enterprise=self.api_enterprise, + api_url=self.api_url) + self.nuage_connection.start() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to connect to the API URL with given username, password and enterprise: {0}'.format(error)) + + def _verify_api(self): + """ + Verifies the API and loads the proper VSPK version + """ + # Checking auth parameters + if ('api_password' not in list(self.auth.keys()) or not self.auth['api_password']) and ('api_certificate' not in list(self.auth.keys()) or + 'api_key' not in list(self.auth.keys()) or + not self.auth['api_certificate'] or not self.auth['api_key']): + self.module.fail_json(msg='Missing api_password or api_certificate and api_key parameter in auth') + + self.api_username = self.auth['api_username'] + if 'api_password' in list(self.auth.keys()) and self.auth['api_password']: + self.api_password = self.auth['api_password'] + if 'api_certificate' in list(self.auth.keys()) and 'api_key' in list(self.auth.keys()) and self.auth['api_certificate'] and self.auth['api_key']: + self.api_certificate = self.auth['api_certificate'] + self.api_key = self.auth['api_key'] + self.api_enterprise = self.auth['api_enterprise'] + self.api_url = self.auth['api_url'] + self.api_version = self.auth['api_version'] + + try: + global VSPK + VSPK = importlib.import_module('vspk.{0:s}'.format(self.api_version)) + except ImportError: + self.module.fail_json(msg='vspk is required for this module, or the API version specified does not exist.') + + def _verify_input(self): + """ + Verifies the parameter input for types and parent correctness and necessary parameters + """ + + # Checking if type exists + try: + self.entity_class = getattr(VSPK, 'NU{0:s}'.format(self.type)) + except AttributeError: + self.module.fail_json(msg='Unrecognised type specified') + + if self.module.check_mode: + return + + if self.parent_type: + # Checking if parent type exists + try: + self.parent_class = getattr(VSPK, 'NU{0:s}'.format(self.parent_type)) + except AttributeError: + # The parent type does not exist, fail + self.module.fail_json(msg='Unrecognised parent type specified') + + fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name) + if fetcher is None: + # The parent has no fetcher, fail + self.module.fail_json(msg='Specified parent is not a valid parent for the specified type') + elif not self.entity_id: + # If there is an id, we do not need a parent because we'll interact directly with the entity + # If an assign needs to happen, a parent will have to be provided + # Root object is the parent + self.parent_class = VSPK.NUMe + fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name) + if fetcher is None: + self.module.fail_json(msg='No parent specified and root object is not a parent for the type') + + # Verifying if a password is provided in case of the change_password command: + if self.command and self.command == 'change_password' and 'password' not in self.properties.keys(): + self.module.fail_json(msg='command is change_password but the following are missing: password property') + + def _find_parent(self): + """ + Fetches the parent if needed, otherwise configures the root object as parent. Also configures the entity fetcher + Important notes: + - If the parent is not set, the parent is automatically set to the root object + - It the root object does not hold a fetcher for the entity, you have to provide an ID + - If you want to assign/unassign, you have to provide a valid parent + """ + self.parent = self.nuage_connection.user + + if self.parent_id: + self.parent = self.parent_class(id=self.parent_id) + try: + self.parent.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified parent: {0}'.format(error)) + + self.entity_fetcher = self.parent.fetcher_for_rest_name(self.entity_class.rest_name) + + def _find_entities(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None): + """ + Will return a set of entities matching a filter or set of properties if the match_filter is unset. If the + entity_id is set, it will return only the entity matching that ID as the single element of the list. + :param entity_id: Optional ID of the entity which should be returned + :param entity_class: Optional class of the entity which needs to be found + :param match_filter: Optional search filter + :param properties: Optional set of properties the entities should contain + :param entity_fetcher: The fetcher for the entity type + :return: List of matching entities + """ + search_filter = '' + + if entity_id: + found_entity = entity_class(id=entity_id) + try: + found_entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error)) + + return [found_entity] + + elif match_filter: + search_filter = match_filter + elif properties: + # Building filter + for num, property_name in enumerate(properties): + if num > 0: + search_filter += ' and ' + search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name]) + + if entity_fetcher is not None: + try: + return entity_fetcher.get(filter=search_filter) + except BambouHTTPError: + pass + return [] + + def _find_entity(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None): + """ + Finds a single matching entity that matches all the provided properties, unless an ID is specified, in which + case it just fetches the one item + :param entity_id: Optional ID of the entity which should be returned + :param entity_class: Optional class of the entity which needs to be found + :param match_filter: Optional search filter + :param properties: Optional set of properties the entities should contain + :param entity_fetcher: The fetcher for the entity type + :return: The first entity matching the criteria, or None if none was found + """ + search_filter = '' + if entity_id: + found_entity = entity_class(id=entity_id) + try: + found_entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error)) + + return found_entity + + elif match_filter: + search_filter = match_filter + elif properties: + # Building filter + for num, property_name in enumerate(properties): + if num > 0: + search_filter += ' and ' + search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name]) + + if entity_fetcher is not None: + try: + return entity_fetcher.get_first(filter=search_filter) + except BambouHTTPError: + pass + return None + + def handle_main_entity(self): + """ + Handles the Ansible task + """ + if self.command and self.command == 'find': + self._handle_find() + elif self.command and self.command == 'change_password': + self._handle_change_password() + elif self.command and self.command == 'wait_for_job': + self._handle_wait_for_job() + elif self.command and self.command == 'get_csp_enterprise': + self._handle_get_csp_enterprise() + elif self.state == 'present': + self._handle_present() + elif self.state == 'absent': + self._handle_absent() + self.module.exit_json(**self.result) + + def _handle_absent(self): + """ + Handles the Ansible task when the state is set to absent + """ + # Absent state + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.entity and (self.entity_fetcher is None or self.entity_fetcher.relationship in ['child', 'root']): + # Entity is present, deleting + if self.module.check_mode: + self.result['changed'] = True + else: + self._delete_entity(self.entity) + self.result['id'] = None + elif self.entity and self.entity_fetcher.relationship == 'member': + # Entity is a member, need to check if already present + if self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._unassign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent, + set_output=True) + + def _handle_present(self): + """ + Handles the Ansible task when the state is set to present + """ + # Present state + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + # Determining action to take + if self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and not self.entity: + self.module.fail_json(msg='Trying to assign an entity that does not exist') + elif self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and self.entity: + # Entity is a member, need to check if already present + if not self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._assign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent, + set_output=True) + elif self.entity_fetcher is not None and self.entity_fetcher.relationship in ['child', 'root'] and not self.entity: + # Entity is not present as a child, creating + if self.module.check_mode: + self.result['changed'] = True + else: + self.entity = self._create_entity(entity_class=self.entity_class, parent=self.parent, properties=self.properties) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + # Checking children + if self.children: + for child in self.children: + self._handle_child(child=child, parent=self.entity) + elif self.entity: + # Need to compare properties in entity and found entity + changed = self._has_changed(entity=self.entity, properties=self.properties) + + if self.module.check_mode: + self.result['changed'] = changed + elif changed: + self.entity = self._save_entity(entity=self.entity) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + else: + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + # Checking children + if self.children: + for child in self.children: + self._handle_child(child=child, parent=self.entity) + elif not self.module.check_mode: + self.module.fail_json(msg='Invalid situation, verify parameters') + + def _handle_get_csp_enterprise(self): + """ + Handles the Ansible task when the command is to get the csp enterprise + """ + self.entity_id = self.parent.enterprise_id + self.entity = VSPK.NUEnterprise(id=self.entity_id) + try: + self.entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to fetch CSP enterprise: {0}'.format(error)) + self.result['id'] = self.entity_id + self.result['entities'].append(self.entity.to_dict()) + + def _handle_wait_for_job(self): + """ + Handles the Ansible task when the command is to wait for a job + """ + # Command wait_for_job + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.module.check_mode: + self.result['changed'] = True + else: + self._wait_for_job(self.entity) + + def _handle_change_password(self): + """ + Handles the Ansible task when the command is to change a password + """ + # Command change_password + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.module.check_mode: + self.result['changed'] = True + else: + try: + getattr(self.entity, 'password') + except AttributeError: + self.module.fail_json(msg='Entity does not have a password property') + + try: + setattr(self.entity, 'password', self.properties['password']) + except AttributeError: + self.module.fail_json(msg='Password can not be changed for entity') + + self.entity = self._save_entity(entity=self.entity) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + def _handle_find(self): + """ + Handles the Ansible task when the command is to find an entity + """ + # Command find + entities = self._find_entities(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + self.result['changed'] = False + if entities: + if len(entities) == 1: + self.result['id'] = entities[0].id + for entity in entities: + self.result['entities'].append(entity.to_dict()) + elif not self.module.check_mode: + self.module.fail_json(msg='Unable to find matching entries') + + def _handle_child(self, child, parent): + """ + Handles children of a main entity. Fields are similar to the normal fields + Currently only supported state: present + """ + if 'type' not in list(child.keys()): + self.module.fail_json(msg='Child type unspecified') + elif 'id' not in list(child.keys()) and 'properties' not in list(child.keys()): + self.module.fail_json(msg='Child ID or properties unspecified') + + # Setting intern variables + child_id = None + if 'id' in list(child.keys()): + child_id = child['id'] + child_properties = None + if 'properties' in list(child.keys()): + child_properties = child['properties'] + child_filter = None + if 'match_filter' in list(child.keys()): + child_filter = child['match_filter'] + + # Checking if type exists + entity_class = None + try: + entity_class = getattr(VSPK, 'NU{0:s}'.format(child['type'])) + except AttributeError: + self.module.fail_json(msg='Unrecognised child type specified') + + entity_fetcher = parent.fetcher_for_rest_name(entity_class.rest_name) + if entity_fetcher is None and not child_id and not self.module.check_mode: + self.module.fail_json(msg='Unable to find a fetcher for child, and no ID specified.') + + # Try and find the child + entity = self._find_entity(entity_id=child_id, entity_class=entity_class, match_filter=child_filter, properties=child_properties, + entity_fetcher=entity_fetcher) + + # Determining action to take + if entity_fetcher.relationship == 'member' and not entity: + self.module.fail_json(msg='Trying to assign a child that does not exist') + elif entity_fetcher.relationship == 'member' and entity: + # Entity is a member, need to check if already present + if not self._is_member(entity_fetcher=entity_fetcher, entity=entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._assign_member(entity_fetcher=entity_fetcher, entity=entity, entity_class=entity_class, parent=parent, set_output=False) + elif entity_fetcher.relationship in ['child', 'root'] and not entity: + # Entity is not present as a child, creating + if self.module.check_mode: + self.result['changed'] = True + else: + entity = self._create_entity(entity_class=entity_class, parent=parent, properties=child_properties) + elif entity_fetcher.relationship in ['child', 'root'] and entity: + changed = self._has_changed(entity=entity, properties=child_properties) + + if self.module.check_mode: + self.result['changed'] = changed + elif changed: + entity = self._save_entity(entity=entity) + + if entity: + self.result['entities'].append(entity.to_dict()) + + # Checking children + if 'children' in list(child.keys()) and not self.module.check_mode: + for subchild in child['children']: + self._handle_child(child=subchild, parent=entity) + + def _has_changed(self, entity, properties): + """ + Compares a set of properties with a given entity, returns True in case the properties are different from the + values in the entity + :param entity: The entity to check + :param properties: The properties to check + :return: boolean + """ + # Need to compare properties in entity and found entity + changed = False + if properties: + for property_name in list(properties.keys()): + if property_name == 'password': + continue + entity_value = '' + try: + entity_value = getattr(entity, property_name) + except AttributeError: + self.module.fail_json(msg='Property {0:s} is not valid for this type of entity'.format(property_name)) + + if entity_value != properties[property_name]: + # Difference in values changing property + changed = True + try: + setattr(entity, property_name, properties[property_name]) + except AttributeError: + self.module.fail_json(msg='Property {0:s} can not be changed for this type of entity'.format(property_name)) + return changed + + def _is_member(self, entity_fetcher, entity): + """ + Verifies if the entity is a member of the parent in the fetcher + :param entity_fetcher: The fetcher for the entity type + :param entity: The entity to look for as a member in the entity fetcher + :return: boolean + """ + members = entity_fetcher.get() + for member in members: + if member.id == entity.id: + return True + return False + + def _assign_member(self, entity_fetcher, entity, entity_class, parent, set_output): + """ + Adds the entity as a member to a parent + :param entity_fetcher: The fetcher of the entity type + :param entity: The entity to add as a member + :param entity_class: The class of the entity + :param parent: The parent on which to add the entity as a member + :param set_output: If set to True, sets the Ansible result variables + """ + members = entity_fetcher.get() + members.append(entity) + try: + parent.assign(members, entity_class) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to assign entity as a member: {0}'.format(error)) + self.result['changed'] = True + if set_output: + self.result['id'] = entity.id + self.result['entities'].append(entity.to_dict()) + + def _unassign_member(self, entity_fetcher, entity, entity_class, parent, set_output): + """ + Removes the entity as a member of a parent + :param entity_fetcher: The fetcher of the entity type + :param entity: The entity to remove as a member + :param entity_class: The class of the entity + :param parent: The parent on which to add the entity as a member + :param set_output: If set to True, sets the Ansible result variables + """ + members = [] + for member in entity_fetcher.get(): + if member.id != entity.id: + members.append(member) + try: + parent.assign(members, entity_class) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to remove entity as a member: {0}'.format(error)) + self.result['changed'] = True + if set_output: + self.result['id'] = entity.id + self.result['entities'].append(entity.to_dict()) + + def _create_entity(self, entity_class, parent, properties): + """ + Creates a new entity in the parent, with all properties configured as in the file + :param entity_class: The class of the entity + :param parent: The parent of the entity + :param properties: The set of properties of the entity + :return: The entity + """ + entity = entity_class(**properties) + try: + parent.create_child(entity) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to create entity: {0}'.format(error)) + self.result['changed'] = True + return entity + + def _save_entity(self, entity): + """ + Updates an existing entity + :param entity: The entity to save + :return: The updated entity + """ + try: + entity.save() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to update entity: {0}'.format(error)) + self.result['changed'] = True + return entity + + def _delete_entity(self, entity): + """ + Deletes an entity + :param entity: The entity to delete + """ + try: + entity.delete() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to delete entity: {0}'.format(error)) + self.result['changed'] = True + + def _wait_for_job(self, entity): + """ + Waits for a job to finish + :param entity: The job to wait for + """ + running = False + if entity.status == 'RUNNING': + self.result['changed'] = True + running = True + + while running: + time.sleep(1) + entity.fetch() + + if entity.status != 'RUNNING': + running = False + + self.result['entities'].append(entity.to_dict()) + if entity.status == 'ERROR': + self.module.fail_json(msg='Job ended in an error') + + +def main(): + """ + Main method + """ + module = AnsibleModule( + argument_spec=dict( + auth=dict( + required=True, + type='dict', + options=dict( + api_username=dict(required=True, type='str'), + api_enterprise=dict(required=True, type='str'), + api_url=dict(required=True, type='str'), + api_version=dict(required=True, type='str'), + api_password=dict(default=None, required=False, type='str', no_log=True), + api_certificate=dict(default=None, required=False, type='str', no_log=True), + api_key=dict(default=None, required=False, type='str', no_log=True) + ) + ), + type=dict(required=True, type='str'), + id=dict(default=None, required=False, type='str'), + parent_id=dict(default=None, required=False, type='str'), + parent_type=dict(default=None, required=False, type='str'), + state=dict(default=None, choices=['present', 'absent'], type='str'), + command=dict(default=None, choices=SUPPORTED_COMMANDS, type='str'), + match_filter=dict(default=None, required=False, type='str'), + properties=dict(default=None, required=False, type='dict'), + children=dict(default=None, required=False, type='list') + ), + mutually_exclusive=[ + ['command', 'state'] + ], + required_together=[ + ['parent_id', 'parent_type'] + ], + required_one_of=[ + ['command', 'state'] + ], + required_if=[ + ['state', 'present', ['id', 'properties', 'match_filter'], True], + ['state', 'absent', ['id', 'properties', 'match_filter'], True], + ['command', 'change_password', ['id', 'properties']], + ['command', 'wait_for_job', ['id']] + ], + supports_check_mode=True + ) + + if not HAS_BAMBOU: + module.fail_json(msg='bambou is required for this module') + + if not HAS_IMPORTLIB: + module.fail_json(msg='importlib (python 2.7) is required for this module') + + entity_manager = NuageEntityManager(module) + entity_manager.handle_main_entity() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/opx/opx_cps.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/opx/opx_cps.py new file mode 100644 index 00000000..ec5cb6db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/opx/opx_cps.py @@ -0,0 +1,389 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# This file is part of Ansible by Red Hat +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: opx_cps +author: "Senthil Kumar Ganesan (@skg-net)" +short_description: CPS operations on networking device running Openswitch (OPX) +description: + - Executes the given operation on the YANG object, using CPS API in the + networking device running OpenSwitch (OPX). It uses the YANG models + provided in https://github.com/open-switch/opx-base-model. +options: + module_name: + description: + - Yang path to be configured. + attr_type: + description: + - Attribute Yang type. + attr_data: + description: + - Attribute Yang path and their corresponding data. + operation: + description: + - Operation to be performed on the object. + default: create + choices: ['delete', 'create', 'set', 'action', 'get'] + db: + description: + - Queries/Writes the specified yang path from/to the db. + type: bool + default: 'no' + qualifier: + description: + - A qualifier provides the type of object data to retrieve or act on. + default: target + choices: ['target', 'observed', 'proposed', 'realtime', 'registration', 'running', 'startup'] + commit_event: + description: + - Attempts to force the auto-commit event to the specified yang object. + type: bool + default: 'no' +requirements: + - "cps" + - "cps_object" + - "cps_utils" +''' + +EXAMPLES = """ +- name: Create VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "base-if-vlan/if/interfaces/interface/id": 230, + "if/interfaces/interface/name": "br230", + "if/interfaces/interface/type": "ianaift:l2vlan" + } + operation: "create" +- name: Get VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "if/interfaces/interface/name": "br230", + } + operation: "get" +- name: Modify some attributes in VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "cps/key_data": + { "if/interfaces/interface/name": "br230" }, + "dell-if/if/interfaces/interface/untagged-ports": ["e101-008-0"], + } + operation: "set" +- name: Delete VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "if/interfaces/interface/name": "br230", + } + operation: "delete" +""" + +RETURN = """ +response: + description: Output from the CPS transaction. + Output of CPS Get operation if CPS set/create/delete not done. + returned: when a CPS transaction is successfully performed. + type: list + sample: + [{ + "data": { + "base-if-vlan/if/interfaces/interface/id": 230, + "cps/object-group/return-code": 0, + "dell-base-if-cmn/if/interfaces/interface/if-index": 46, + "if/interfaces/interface/name": "br230", + "if/interfaces/interface/type": "ianaift:l2vlan" + }, + "key": "target/dell-base-if-cmn/if/interfaces/interface" + }] +cps_curr_config: + description: Returns the CPS Get output i.e. the running configuration + before CPS operation of set/delete is performed + returned: when CPS operations set, delete + type: dict + sample: + [{ + "data": { + "base-if-vlan/if/interfaces/interface/id": 230, + "cps/key_data": { + "if/interfaces/interface/name": "br230" + }, + "dell-base-if-cmn/if/interfaces/interface/if-index": 44, + "dell-if/if/interfaces/interface/learning-mode": 1, + "dell-if/if/interfaces/interface/mtu": 1532, + "dell-if/if/interfaces/interface/phys-address": "", + "dell-if/if/interfaces/interface/vlan-type": 1, + "if/interfaces/interface/enabled": 0, + "if/interfaces/interface/type": "ianaift:l2vlan" + }, + "key": "target/dell-base-if-cmn/if/interfaces/interface" + }] +diff: + description: The actual configuration that will be pushed comparing + the running configuration and input attributes + returned: when CPS operations set, delete + type: dict + sample: + { + "cps/key_data": { + "if/interfaces/interface/name": "br230" + }, + "dell-if/if/interfaces/interface/untagged-ports": [ + "e101-007-0" + ] + } +db: + description: Denotes if CPS DB transaction was performed + returned: when db is set to True in module options + type: bool + sample: True +commit_event: + description: Denotes if auto-commit event is set + returned: when commit_event is set to True in module options + type: bool + sample: True +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import dict_diff + +try: + import cps + import cps_object + import cps_utils + HAS_CPS = True +except ImportError: + HAS_CPS = False + + +def convert_cps_raw_list(raw_list): + resp_list = [] + if raw_list: + for raw_elem in raw_list: + processed_element = convert_cps_raw_data(raw_elem) + if processed_element: + raw_key = raw_elem['key'] + individual_element = {} + individual_element['data'] = processed_element + individual_element['key'] = (cps.qual_from_key(raw_key) + "/" + + cps.name_from_key(raw_key, 1)) + resp_list.append(individual_element) + return resp_list + + +def convert_cps_raw_data(raw_elem): + d = {} + obj = cps_object.CPSObject(obj=raw_elem) + for attr in raw_elem['data']: + d[attr] = obj.get_attr_data(attr) + return d + + +def parse_cps_parameters(module_name, qualifier, attr_type, + attr_data, operation=None, db=None, + commit_event=None): + + obj = cps_object.CPSObject(module=module_name, qual=qualifier) + + if operation: + obj.set_property('oper', operation) + + if attr_type: + for key, val in iteritems(attr_type): + cps_utils.cps_attr_types_map.add_type(key, val) + + for key, val in iteritems(attr_data): + + embed_attrs = key.split(',') + embed_attrs_len = len(embed_attrs) + if embed_attrs_len >= 3: + obj.add_embed_attr(embed_attrs, val, embed_attrs_len - 2) + else: + if isinstance(val, str): + val_list = val.split(',') + # Treat as list if value contains ',' but is not + # enclosed within {} + if len(val_list) == 1 or val.startswith('{'): + obj.add_attr(key, val) + else: + obj.add_attr(key, val_list) + else: + obj.add_attr(key, val) + + if db: + cps.set_ownership_type(obj.get_key(), 'db') + obj.set_property('db', True) + else: + obj.set_property('db', False) + + if commit_event: + cps.set_auto_commit_event(obj.get_key(), True) + obj.set_property('commit-event', True) + return obj + + +def cps_get(obj): + + RESULT = dict() + key = obj.get() + l = [] + cps.get([key], l) + + resp_list = convert_cps_raw_list(l) + + RESULT["response"] = resp_list + return RESULT + + +def cps_transaction(obj): + + RESULT = dict() + ch = {'operation': obj.get_property('oper'), 'change': obj.get()} + if cps.transaction([ch]): + RESULT["response"] = convert_cps_raw_list([ch['change']]) + RESULT["changed"] = True + else: + error_msg = "Transaction error while " + obj.get_property('oper') + raise RuntimeError(error_msg) + return RESULT + + +def parse_key_data(attrs): + + res = dict() + for key, val in iteritems(attrs): + if key == 'cps/key_data': + res.update(val) + else: + res[key] = val + return res + + +def main(): + """ + main entry point for module execution + """ + argument_spec = dict( + qualifier=dict(required=False, + default="target", + type='str', + choices=['target', 'observed', 'proposed', 'realtime', + 'registration', 'running', 'startup']), + module_name=dict(required=True, type='str'), + attr_type=dict(required=False, type='dict'), + attr_data=dict(required=True, type='dict'), + operation=dict(required=False, + default="create", + type='str', + choices=['delete', 'create', 'set', 'action', 'get']), + db=dict(required=False, default=False, type='bool'), + commit_event=dict(required=False, default=False, type='bool') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + + if not HAS_CPS: + module.fail_json(msg='CPS library required for this module') + + qualifier = module.params['qualifier'] + module_name = module.params['module_name'] + attr_type = module.params["attr_type"] + attr_data = module.params["attr_data"] + operation = module.params['operation'] + db = module.params["db"] + commit_event = module.params["commit_event"] + RESULT = dict(changed=False, db=False, commit_event=False) + + if db: + RESULT['db'] = True + if commit_event: + RESULT['commit_event'] = True + + try: + # First do a CPS get operation + get_obj = parse_cps_parameters(module_name, qualifier, attr_type, + attr_data, 'get', db, commit_event) + curr_config = cps_get(get_obj) + + if operation == 'get': + RESULT.update(curr_config) + else: + diff = attr_data + + # Evaluate the changes in the attributes + cfg = dict() + if curr_config and curr_config['response']: + cfg = curr_config['response'][0]['data'] + key_d = 'cps/key_data' + + # diff computation is not needed for delete + if operation != 'delete': + configs = parse_key_data(cfg) + attributes = parse_key_data(attr_data) + diff = dict_diff(configs, attributes) + # Append diff with any 'cps/key_data' from attr_data + if diff and key_d in attr_data: + diff[key_d] = attr_data[key_d] + + # Append diff with any 'cps/key_data' from curr_config + # Needed for all operations including delete + if diff and key_d in cfg: + if key_d in diff: + diff[key_d].update(cfg[key_d]) + else: + diff[key_d] = cfg[key_d] + + RESULT.update({"diff": diff}) + + # Create object for cps operation + obj = parse_cps_parameters(module_name, qualifier, attr_type, + diff, operation, db, commit_event) + + res = dict() + if operation == "delete": + if cfg: + res = cps_transaction(obj) + else: + if diff: + res = cps_transaction(obj) + + if not res and cfg: + res.update({"response": curr_config['response']}) + else: + res.update({"cps_curr_config": curr_config['response']}) + RESULT.update(res) + + except Exception as e: + module.fail_json(msg=str(type(e).__name__) + ": " + str(e)) + + module.exit_json(**RESULT) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_config.py new file mode 100644 index 00000000..1cc72195 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_config.py @@ -0,0 +1,356 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ordnance_config +author: "Alexander Turner (@alexanderturner) " +short_description: Manage Ordnance configuration sections +description: + - Ordnance router configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with these configuration sections in + a deterministic way. +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the Ordnance router. It specifies the character to use + as the delimiting character. This only applies to the + configuration action + default: "@" + backup: + description: + - 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. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + type: bool + default: 'no' +''' + +EXAMPLES = """ +--- +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: RouterName + password: password + transport: cli + +--- +- name: Configure top level configuration + community.network.ordnance_config: + lines: hostname {{ inventory_hostname }} + provider: "{{ cli }}" + +- name: Configure interface settings + community.network.ordnance_config: + lines: + - description test interface + - ip address 172.31.1.1 255.255.255.0 + parents: interface Ethernet1 + provider: "{{ cli }}" + +- name: Configure bgp router + community.network.ordnance_config: + lines: + - neighbor 1.1.1.1 remote-as 1234 + - network 10.0.0.0/24 + parents: router bgp 65001 + provider: "{{ cli }}" + +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when commands is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ordnance_config.2016-07-16@22:28:34 +""" +import re +import time +import traceback + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import NetworkModule, NetworkError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Command +from ansible_collections.community.network.plugins.module_utils.network.ordnance.ordnance import get_config +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_native + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + if module.params['force']: + warnings.append('The force argument is deprecated, please use ' + 'match=none instead. This argument will be ' + 'removed in the future') + + +def extract_banners(config): + banners = {} + banner_cmds = re.findall(r'^banner (\w+)', config, re.M) + for cmd in banner_cmds: + regex = r'banner %s \^C(.+?)(?=\^C)' % cmd + match = re.search(regex, config, re.S) + if match: + key = 'banner %s' % cmd + banners[key] = match.group(1).strip() + + for cmd in banner_cmds: + regex = r'banner %s \^C(.+?)(?=\^C)' % cmd + match = re.search(regex, config, re.S) + if match: + config = config.replace(str(match.group(1)), '') + + config = re.sub(r'banner \w+ \^C\^C', '!! banner removed', config) + return (config, banners) + + +def diff_banners(want, have): + candidate = {} + for key, value in iteritems(want): + if value != have.get(key): + candidate[key] = value + return candidate + + +def load_banners(module, banners): + delimiter = module.params['multiline_delimiter'] + for key, value in iteritems(banners): + key += ' %s' % delimiter + for cmd in ['config terminal', key, value, delimiter, 'end']: + cmd += '\r' + module.connection.shell.shell.sendall(cmd) + time.sleep(1) + module.connection.shell.receive() + + +def get_config(module, result): + contents = module.params['config'] + if not contents: + defaults = module.params['defaults'] + contents = module.config.get_config(include_defaults=defaults) + + contents, banners = extract_banners(contents) + return NetworkConfig(indent=1, contents=contents), banners + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + banners = {} + + if module.params['src']: + src, banners = extract_banners(module.params['src']) + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate, banners + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate, want_banners = get_candidate(module) + + if match != 'none': + config, have_banners = get_config(module, result) + path = module.params['parents'] + configobjs = candidate.difference(config, path=path, match=match, + replace=replace) + else: + configobjs = candidate.items + have_banners = {} + + banners = diff_banners(want_banners, have_banners) + + if configobjs or banners: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['updates'] = commands + result['banners'] = banners + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + module.config(commands) + if banners: + load_banners(module, banners) + + result['changed'] = True + + if module.params['save']: + if not module.check_mode: + module.config.save_config() + result['changed'] = True + + +def main(): + """ main entry point for module execution + """ + + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + config=dict(), + defaults=dict(type='bool', default=False), + + backup=dict(type='bool', default=False), + save=dict(default=False, type='bool'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = NetworkModule(argument_spec=argument_spec, + connect_on_load=False, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + if module.params['force'] is True: + module.params['match'] = 'none' + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = module.config.get_config() + + try: + run(module, result) + except NetworkError as e: + module.disconnect() + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + module.disconnect() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_facts.py new file mode 100644 index 00000000..04d0776b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/ordnance/ordnance_facts.py @@ -0,0 +1,288 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ordnance_facts +author: "Alexander Turner (@alexanderturner) " +short_description: Collect facts from Ordnance Virtual Routers over SSH +description: + - Collects a base set of device facts from an Ordnance Virtual + router over SSH. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +--- +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: RouterName + password: ordnance + transport: cli + +--- +- name: Collect all facts from the device + community.network.ordnance_facts: + gather_subset: all + provider: "{{ cli }}" + +- name: Collect only the config and default facts + community.network.ordnance_facts: + gather_subset: + - config + provider: "{{ cli }}" + +- name: Do not collect hardware facts + community.network.ordnance_facts: + gather_subset: + - "!hardware" + provider: "{{ cli }}" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the virtual router + returned: always + type: list + +# config +ansible_net_config: + description: The current active config from the virtual router + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the virtual router + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the virtual router + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the virtual router + returned: when interfaces is configured + type: dict +""" +import re +import traceback + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import NetworkModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip +from ansible.module_utils._text import to_native + + +class FactsBase(object): + + def __init__(self, module): + self.module = module + self.facts = dict() + self.failed_commands = list() + + def run(self, cmd): + try: + return self.module.cli(cmd)[0] + except Exception: + self.failed_commands.append(cmd) + + +class Config(FactsBase): + + def populate(self): + data = self.run('show running-config') + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + def populate(self): + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.run('show interfaces') + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.run('show ipv6 interface') + if data: + data = self.parse_interfaces(data) + self.populate_ipv6_interfaces(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['duplex'] = self.parse_duplex(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s+(.+), subnet', value, re.M) + subnets = re.findall(r', subnet is (.+)$', value, re.M) + for addr, subnet in zip(addresses, subnets): + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_duplex(self, data): + match = re.search(r'(\w+) Duplex', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = NetworkModule(argument_spec=spec, supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + failed_commands = list() + + try: + for inst in instances: + inst.populate() + failed_commands.extend(inst.failed_commands) + facts.update(inst.facts) + except Exception as exc: + module.fail_json(msg=to_native(exc), exception=traceback.format_exc()) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, failed_commands=failed_commands) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admin.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admin.py new file mode 100644 index 00000000..cd0e958b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admin.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_admin +short_description: Add or modify PAN-OS user accounts password. +description: + - PanOS module that allows changes to the user account passwords by doing + API calls to the Firewall using pan-api as the protocol. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + admin_username: + description: + - username for admin user + default: "admin" + admin_password: + description: + - password for admin user + required: true + role: + description: + - role for admin user + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# Set the password of user admin to "badpassword" +# Doesn't commit the candidate config + - name: Set admin password + community.network.panos_admin: + ip_address: "192.168.1.1" + password: "admin" + admin_username: admin + admin_password: "badpassword" + commit: False +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "okey dokey" +''' +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_ADMIN_XPATH = "/config/mgt-config/users/entry[@name='%s']" + + +def admin_exists(xapi, admin_username): + xapi.get(_ADMIN_XPATH % admin_username) + e = xapi.element_root.find('.//entry') + return e + + +def admin_set(xapi, module, admin_username, admin_password, role): + if admin_password is not None: + xapi.op(cmd='request password-hash password "%s"' % admin_password, + cmd_xml=True) + r = xapi.element_root + phash = r.find('.//phash').text + if role is not None: + rbval = "yes" + if role != "superuser" and role != 'superreader': + rbval = "" + + ea = admin_exists(xapi, admin_username) + if ea is not None: + # user exists + changed = False + + if role is not None: + rb = ea.find('.//role-based') + if rb is not None: + if rb[0].tag != role: + changed = True + xpath = _ADMIN_XPATH % admin_username + xpath += '/permissions/role-based/%s' % rb[0].tag + xapi.delete(xpath=xpath) + + xpath = _ADMIN_XPATH % admin_username + xpath += '/permissions/role-based' + xapi.set(xpath=xpath, + element='<%s>%s' % (role, rbval, role)) + + if admin_password is not None: + xapi.edit(xpath=_ADMIN_XPATH % admin_username + '/phash', + element='%s' % phash) + changed = True + + return changed + + # setup the non encrypted part of the monitor + exml = [] + + exml.append('%s' % phash) + exml.append('<%s>%s' + '' % (role, rbval, role)) + + exml = ''.join(exml) + # module.fail_json(msg=exml) + + xapi.set(xpath=_ADMIN_XPATH % admin_username, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(), + password=dict(no_log=True), + username=dict(default='admin'), + admin_username=dict(default='admin'), + admin_password=dict(no_log=True), + role=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + admin_username = module.params['admin_username'] + if admin_username is None: + module.fail_json(msg="admin_username is required") + admin_password = module.params['admin_password'] + role = module.params['role'] + commit = module.params['commit'] + + changed = admin_set(xapi, module, admin_username, admin_password, role) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admpwd.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admpwd.py new file mode 100644 index 00000000..c9c0ea74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_admpwd.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_admpwd +short_description: change admin password of PAN-OS device using SSH with SSH key +description: + - Change the admin password of PAN-OS via SSH using a SSH key for authentication. + - Useful for AWS instances where the first login should be done via SSH. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - paramiko +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device + required: true + username: + description: + - username for initial authentication + required: false + default: "admin" + key_filename: + description: + - filename of the SSH Key to use for authentication + required: true + newpassword: + description: + - password to configure for admin on the PAN-OS device + required: true +''' + +EXAMPLES = ''' +# Tries for 10 times to set the admin password of 192.168.1.1 to "badpassword" +# via SSH, authenticating using key /tmp/ssh.key +- name: Set admin password + community.network.panos_admpwd: + ip_address: "192.168.1.1" + username: "admin" + key_filename: "/tmp/ssh.key" + newpassword: "badpassword" + register: result + until: result is not failed + retries: 10 + delay: 30 +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "Last login: Fri Sep 16 11:09:20 2016 from 10.35.34.56.....Configuration committed successfully" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.paramiko import paramiko +import time +import sys + +_PROMPTBUFF = 4096 + + +def wait_with_timeout(module, shell, prompt, timeout=60): + now = time.time() + result = "" + while True: + if shell.recv_ready(): + result += shell.recv(_PROMPTBUFF) + endresult = result.strip() + if len(endresult) != 0 and endresult[-1] == prompt: + break + + if time.time() - now > timeout: + module.fail_json(msg="Timeout waiting for prompt") + + return result + + +def set_panwfw_password(module, ip_address, key_filename, newpassword, username): + stdout = "" + + ssh = paramiko.SSHClient() + + # add policy to accept all host keys, I haven't found + # a way to retrieve the instance SSH key fingerprint from AWS + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + ssh.connect(ip_address, username=username, key_filename=key_filename) + shell = ssh.invoke_shell() + + # wait for the shell to start + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # step into config mode + shell.send('configure\n') + # wait for the config prompt + buff = wait_with_timeout(module, shell, "#") + stdout += buff + + if module.check_mode: + # exit and close connection + shell.send('exit\n') + ssh.close() + return False, 'Connection test successful. Password left intact.' + + # set admin password + shell.send('set mgt-config users ' + username + ' password\n') + + # wait for the password prompt + buff = wait_with_timeout(module, shell, ":") + stdout += buff + + # enter password for the first time + shell.send(newpassword + '\n') + + # wait for the password prompt + buff = wait_with_timeout(module, shell, ":") + stdout += buff + + # enter password for the second time + shell.send(newpassword + '\n') + + # wait for the config mode prompt + buff = wait_with_timeout(module, shell, "#") + stdout += buff + + # commit ! + shell.send('commit\n') + + # wait for the prompt + buff = wait_with_timeout(module, shell, "#", 120) + stdout += buff + + if 'success' not in buff: + module.fail_json(msg="Error setting " + username + " password: " + stdout) + + # exit + shell.send('exit\n') + + ssh.close() + + return True, stdout + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + username=dict(default='admin'), + key_filename=dict(required=True), + newpassword=dict(no_log=True, required=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + if paramiko is None: + module.fail_json(msg='paramiko is required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + key_filename = module.params["key_filename"] + if not key_filename: + module.fail_json(msg="key_filename should be specified") + newpassword = module.params["newpassword"] + if not newpassword: + module.fail_json(msg="newpassword is required") + username = module.params['username'] + + try: + changed, stdout = set_panwfw_password(module, ip_address, key_filename, newpassword, username) + module.exit_json(changed=changed, stdout=stdout) + except Exception: + x = sys.exc_info()[1] + module.fail_json(msg=x) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_cert_gen_ssh.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_cert_gen_ssh.py new file mode 100644 index 00000000..61326154 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_cert_gen_ssh.py @@ -0,0 +1,192 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_cert_gen_ssh +short_description: generates a self-signed certificate using SSH protocol with SSH key +description: + - This module generates a self-signed certificate that can be used by GlobalProtect client, SSL connector, or + - otherwise. Root certificate must be preset on the system first. This module depends on paramiko for ssh. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - paramiko +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + key_filename: + description: + - Location of the filename that is used for the auth. Either I(key_filename) or I(password) is required. + required: true + password: + description: + - Password credentials to use for auth. Either I(key_filename) or I(password) is required. + required: true + cert_friendly_name: + description: + - Human friendly certificate name (not CN but just a friendly name). + required: true + cert_cn: + description: + - Certificate CN (common name) embedded in the certificate signature. + required: true + signed_by: + description: + - Undersigning authority (CA) that MUST already be presents on the device. + required: true + rsa_nbits: + description: + - Number of bits used by the RSA algorithm for the certificate generation. + default: "2048" +''' + +EXAMPLES = ''' +# Generates a new self-signed certificate using ssh +- name: Generate self signed certificate + community.network.panos_cert_gen_ssh: + ip_address: "192.168.1.1" + password: "paloalto" + cert_cn: "1.1.1.1" + cert_friendly_name: "test123" + signed_by: "root-ca" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.paramiko import paramiko +import time + +_PROMPTBUFF = 4096 + + +def wait_with_timeout(module, shell, prompt, timeout=60): + now = time.time() + result = "" + while True: + if shell.recv_ready(): + result += shell.recv(_PROMPTBUFF) + endresult = result.strip() + if len(endresult) != 0 and endresult[-1] == prompt: + break + + if time.time() - now > timeout: + module.fail_json(msg="Timeout waiting for prompt") + + return result + + +def generate_cert(module, ip_address, key_filename, password, + cert_cn, cert_friendly_name, signed_by, rsa_nbits): + stdout = "" + + client = paramiko.SSHClient() + + # add policy to accept all host keys, I haven't found + # a way to retrieve the instance SSH key fingerprint from AWS + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + if not key_filename: + client.connect(ip_address, username="admin", password=password) + else: + client.connect(ip_address, username="admin", key_filename=key_filename) + + shell = client.invoke_shell() + # wait for the shell to start + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # generate self-signed certificate + if isinstance(cert_cn, list): + cert_cn = cert_cn[0] + cmd = 'request certificate generate signed-by {0} certificate-name {1} name {2} algorithm RSA rsa-nbits {3}\n'.format( + signed_by, cert_friendly_name, cert_cn, rsa_nbits) + shell.send(cmd) + + # wait for the shell to complete + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # exit + shell.send('exit\n') + + if 'Success' not in buff: + module.fail_json(msg="Error generating self signed certificate: " + stdout) + + client.close() + return stdout + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + key_filename=dict(), + password=dict(no_log=True), + cert_cn=dict(required=True), + cert_friendly_name=dict(required=True), + rsa_nbits=dict(default='2048'), + signed_by=dict(required=True) + + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['key_filename', 'password']]) + if paramiko is None: + module.fail_json(msg='paramiko is required for this module') + + ip_address = module.params["ip_address"] + key_filename = module.params["key_filename"] + password = module.params["password"] + cert_cn = module.params["cert_cn"] + cert_friendly_name = module.params["cert_friendly_name"] + signed_by = module.params["signed_by"] + rsa_nbits = module.params["rsa_nbits"] + + try: + stdout = generate_cert(module, + ip_address, + key_filename, + password, + cert_cn, + cert_friendly_name, + signed_by, + rsa_nbits) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_check.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_check.py new file mode 100644 index 00000000..0d007f3c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_check.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_check +short_description: check if PAN-OS device is ready for configuration +description: + - Check if PAN-OS device is ready for being configured (no pending jobs). + - The check could be done once or multiple times until the device is ready. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + timeout: + description: + - timeout of API calls + required: false + default: 0 + interval: + description: + - time waited between checks + required: false + default: 0 +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# single check on 192.168.1.1 with credentials admin/admin +- name: Check if ready + community.network.panos_check: + ip_address: "192.168.1.1" + password: "admin" + +# check for 10 times, every 30 seconds, if device 192.168.1.1 +# is ready, using credentials admin/admin +- name: Wait for reboot + community.network.panos_check: + ip_address: "192.168.1.1" + password: "admin" + register: result + until: result is not failed + retries: 10 + delay: 30 +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +import time + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def check_jobs(jobs, module): + job_check = False + for j in jobs: + status = j.find('.//status') + if status is None: + return False + if status.text != 'FIN': + return False + job_check = True + return job_check + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + timeout=dict(default=0, type='int'), + interval=dict(default=0, type='int') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + timeout = module.params['timeout'] + interval = module.params['interval'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password, + timeout=60 + ) + + checkpnt = time.time() + timeout + while True: + try: + xapi.op(cmd="show jobs all", cmd_xml=True) + except Exception: + pass + else: + jobs = xapi.element_root.findall('.//job') + if check_jobs(jobs, module): + module.exit_json(changed=True, msg="okey dokey") + + if time.time() > checkpnt: + break + + time.sleep(interval) + + module.fail_json(msg="Timeout") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_commit.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_commit.py new file mode 100644 index 00000000..e049d7e3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_commit.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2019, Tomi Raittinen +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_commit +short_description: commit firewall's candidate configuration +description: + - PanOS module that will commit firewall's candidate configuration on + - the device. The new configuration will become active immediately. +author: + - Luigi Mori (@jtschichold) + - Ivan Bojer (@ivanbojer) + - Tomi Raittinen (@traittinen) +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device. + required: true + password: + description: + - Password for authentication. If the value is not specified in the + task, the value of environment variable C(ANSIBLE_NET_PASSWORD) + will be used instead. + required: true + username: + description: + - Username for authentication. If the value is not specified in the + task, the value of environment variable C(ANSIBLE_NET_USERNAME) + will be used instead if defined. C(admin) will be used if nothing + above is defined. + default: admin + interval: + description: + - interval for checking commit job + default: 0.5 + timeout: + description: + - timeout for commit job + sync: + description: + - if commit should be synchronous + type: bool + default: 'yes' + description: + description: + - Commit description/comment + type: str + commit_changes_by: + description: + - Commit changes made by specified admin + type: list + commit_vsys: + description: + - Commit changes for specified VSYS + type: list +''' + +EXAMPLES = ''' +- name: Commit candidate config on 192.168.1.1 in sync mode + community.network.panos_commit: + ip_address: "192.168.1.1" + username: "admin" + password: "admin" +''' + +RETURN = ''' +panos_commit: + description: Information about commit job. + returned: always + type: complex + contains: + job_id: + description: Palo Alto job ID for the commit operation. Only returned if commit job is launched on device. + returned: always + type: str + sample: "139" + status_code: + description: Palo Alto API status code. Null if commit is successful. + returned: always + type: str + sample: 19 + status_detail: + description: Palo Alto API detailed status message. + returned: always + type: str + sample: Configuration committed successfully + status_text: + description: Palo Alto API status text. + returned: always + type: str + sample: success +''' + +from ansible.module_utils.basic import AnsibleModule, env_fallback +import xml.etree.ElementTree as etree + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True, type='str'), + password=dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), default="admin"), + interval=dict(default=0.5), + timeout=dict(), + sync=dict(type='bool', default=True), + description=dict(type='str'), + commit_changes_by=dict(type='list'), + commit_vsys=dict(type='list') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + + username = module.params['username'] + if not username: + module.fail_json(msg="username is required") + + interval = module.params['interval'] + timeout = module.params['timeout'] + sync = module.params['sync'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + cmd = "" + + description = module.params["description"] + if description: + cmd += "" + description + "" + + commit_changes_by = module.params["commit_changes_by"] + commit_vsys = module.params["commit_vsys"] + + if commit_changes_by or commit_vsys: + + cmd += "" + + if commit_changes_by: + cmd += "" + for admin in commit_changes_by: + cmd += "" + admin + "" + cmd += "" + + if commit_vsys: + cmd += "" + for vsys in commit_vsys: + cmd += "" + vsys + "" + cmd += "" + + cmd += "" + + cmd += "" + + xapi.commit( + cmd=cmd, + sync=sync, + interval=interval, + timeout=timeout + ) + + try: + result = xapi.xml_root().encode('utf-8') + root = etree.fromstring(result) + job_id = root.find('./result/job/id').text + except AttributeError: + job_id = None + + panos_commit_details = dict( + status_text=xapi.status, + status_code=xapi.status_code, + status_detail=xapi.status_detail, + job_id=job_id + ) + + if "Commit failed" in xapi.status_detail: + module.fail_json(msg=xapi.status_detail, panos_commit=panos_commit_details) + + if job_id: + module.exit_json(changed=True, msg="Commit successful.", panos_commit=panos_commit_details) + else: + module.exit_json(changed=False, msg="No changes to commit.", panos_commit=panos_commit_details) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag.py new file mode 100644 index 00000000..c824d69b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_dag +short_description: create a dynamic address group +description: + - Create a dynamic address group object in the firewall used for policy rules +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + dag_name: + description: + - name of the dynamic address group + required: true + dag_filter: + description: + - dynamic filter user by the dynamic address group + required: true + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Dag + community.network.panos_dag: + ip_address: "192.168.1.1" + password: "admin" + dag_name: "dag-1" + dag_filter: "'aws-tag.aws:cloudformation:logical-id.ServerInstance' and 'instanceState.running'" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_ADDRGROUP_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry[@name='vsys1']/address-group/entry[@name='%s']" + + +def addressgroup_exists(xapi, group_name): + xapi.get(_ADDRGROUP_XPATH % group_name) + e = xapi.element_root.find('.//entry') + if e is None: + return False + return True + + +def add_dag(xapi, dag_name, dag_filter): + if addressgroup_exists(xapi, dag_name): + return False + + # setup the non encrypted part of the monitor + exml = [] + + exml.append('') + exml.append('%s' % dag_filter) + exml.append('') + + exml = ''.join(exml) + xapi.set(xpath=_ADDRGROUP_XPATH % dag_name, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + dag_name=dict(required=True), + dag_filter=dict(required=True), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + dag_name = module.params['dag_name'] + dag_filter = module.params['dag_filter'] + commit = module.params['commit'] + + changed = add_dag(xapi, dag_name, dag_filter) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag_tags.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag_tags.py new file mode 100644 index 00000000..5e2d1223 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_dag_tags.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_dag_tags +short_description: Create tags for DAG's on PAN-OS devices. +description: + - Create the ip address to tag associations. Tags will in turn be used to create DAG's +author: "Vinay Venkataraghavan (@vinayvenkat)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is not supported. +options: + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + description: + description: + - The purpose / objective of the static Address Group + commit: + description: + - commit if changed + default: true + type: bool + devicegroup: + description: > + - Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama. + If device group is not define we assume that we are contacting Firewall. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + tag_names: + description: + - The list of the tags that will be added or removed from the IP address. + ip_to_register: + description: + - IP that will be registered with the given tag names. +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Create the tags to map IP addresses + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + ip_to_register: "{{ ip_to_register }}" + tag_names: "{{ tag_names }}" + description: "Tags to allow certain IP's to access various SaaS Applications" + operation: 'add' + tags: "adddagip" + +- name: List the IP address to tag mapping + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + tag_names: "{{ tag_names }}" + description: "List the IP address to tag mapping" + operation: 'list' + tags: "listdagip" + +- name: Unregister an IP address from a tag mapping + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + ip_to_register: "{{ ip_to_register }}" + tag_names: "{{ tag_names }}" + description: "Unregister IP address from tag mappings" + operation: 'delete' + tags: "deletedagip" +''' + +RETURN = ''' +# Default return values +''' + +try: + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + + from pan.xapi import PanXapiError + + HAS_LIB = True +except ImportError: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def register_ip_to_tag_map(device, ip_addresses, tag): + exc = None + try: + device.userid.register(ip_addresses, tag) + except PanXapiError as exc: + return False, exc + + return True, exc + + +def get_all_address_group_mapping(device): + exc = None + ret = None + try: + ret = device.userid.get_registered_ip() + except PanXapiError as exc: + return False, exc + + return ret, exc + + +def delete_address_from_mapping(device, ip_address, tags): + exc = None + try: + ret = device.userid.unregister(ip_address, tags) + except PanXapiError as exc: + return False, exc + + return True, exc + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + devicegroup=dict(default=None), + description=dict(default=None), + ip_to_register=dict(type='str', required=False), + tag_names=dict(type='list', required=True), + commit=dict(type='bool', default=True), + operation=dict(type='str', required=True) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + commit = module.params['commit'] + devicegroup = module.params['devicegroup'] + operation = module.params['operation'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + result = None + if operation == 'add': + result, exc = register_ip_to_tag_map(device, + ip_addresses=module.params.get('ip_to_register', None), + tag=module.params.get('tag_names', None) + ) + elif operation == 'list': + result, exc = get_all_address_group_mapping(device) + elif operation == 'delete': + result, exc = delete_address_from_mapping(device, + ip_address=module.params.get('ip_to_register', None), + tags=module.params.get('tag_names', []) + ) + else: + module.fail_json(msg="Unsupported option") + + if not result: + module.fail_json(msg=exc.message) + + if commit: + try: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg=result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_import.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_import.py new file mode 100644 index 00000000..af7453e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_import.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_import +short_description: import file on PAN-OS devices +description: + - Import file on PAN-OS device +notes: + - API reference documentation can be read from the C(/api/) directory of your appliance + - Certificate validation is enabled by default as of Ansible 2.6. This may break existing playbooks but should be disabled with caution. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python + - requests + - requests_toolbelt +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + category: + description: + - Category of file uploaded. The default is software. + - See API > Import section of the API reference for category options. + default: software + file: + description: + - Location of the file to import into device. + url: + description: + - URL of the file that will be imported to device. + validate_certs: + description: + - If C(no), SSL certificates will not be validated. Disabling certificate validation is not recommended. + default: yes + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# import software image PanOS_vm-6.1.1 on 192.168.1.1 +- name: Import software image into PAN-OS + community.network.panos_import: + ip_address: 192.168.1.1 + username: admin + password: admin + file: /tmp/PanOS_vm-6.1.1 + category: software +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +import os.path +import xml.etree +import tempfile +import shutil +import os + +try: + import pan.xapi + import requests + import requests_toolbelt + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def import_file(xapi, module, ip_address, file_, category): + xapi.keygen() + + params = { + 'type': 'import', + 'category': category, + 'key': xapi.api_key + } + + filename = os.path.basename(file_) + + mef = requests_toolbelt.MultipartEncoder( + fields={ + 'file': (filename, open(file_, 'rb'), 'application/octet-stream') + } + ) + + r = requests.post( + 'https://' + ip_address + '/api/', + verify=module.params['validate_certs'], + params=params, + headers={'Content-Type': mef.content_type}, + data=mef + ) + + # if something goes wrong just raise an exception + r.raise_for_status() + + resp = xml.etree.ElementTree.fromstring(r.content) + + if resp.attrib['status'] == 'error': + module.fail_json(msg=r.content) + + return True, filename + + +def download_file(url): + r = requests.get(url, stream=True) + fo = tempfile.NamedTemporaryFile(prefix='ai', delete=False) + shutil.copyfileobj(r.raw, fo) + fo.close() + + return fo.name + + +def delete_file(path): + os.remove(path) + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + category=dict(default='software'), + file=dict(), + url=dict(), + validate_certs=dict(type='bool', default=True), + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_one_of=[['file', 'url']]) + if not HAS_LIB: + module.fail_json(msg='pan-python, requests, and requests_toolbelt are required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + file_ = module.params['file'] + url = module.params['url'] + + category = module.params['category'] + + # we can get file from URL or local storage + if url is not None: + file_ = download_file(url) + + try: + changed, filename = import_file(xapi, module, ip_address, file_, category) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + # cleanup and delete file if local + if url is not None: + delete_file(file_) + + module.exit_json(changed=changed, filename=filename, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_interface.py new file mode 100644 index 00000000..63dc366e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_interface.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_interface +short_description: configure data-port network interface for DHCP +description: + - Configure data-port (DP) network interface for DHCP. By default DP interfaces are static. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. +options: + if_name: + description: + - Name of the interface to configure. + required: true + zone_name: + description: > + Name of the zone for the interface. If the zone does not exist it is created but if the zone exists and + it is not of the layer3 type the operation will fail. + required: true + create_default_route: + description: + - Whether or not to add default route with router learned via DHCP. + default: "false" + type: bool + commit: + description: + - Commit if changed + default: true + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Enable DHCP client on ethernet1/1 in zone public + interface: + password: "admin" + ip_address: "192.168.1.1" + if_name: "ethernet1/1" + zone_name: "public" + create_default_route: "yes" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_IF_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/network/interface/ethernet/entry[@name='%s']" + +_ZONE_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry/zone/entry" +_ZONE_XPATH_QUERY = _ZONE_XPATH + "[network/layer3/member/text()='%s']" +_ZONE_XPATH_IF = _ZONE_XPATH + "[@name='%s']/network/layer3/member[text()='%s']" +_VR_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/network/virtual-router/entry" + + +def add_dhcp_if(xapi, if_name, zone_name, create_default_route): + if_xml = [ + '', + '', + '', + '%s', + '' + '' + '' + ] + cdr = 'yes' + if not create_default_route: + cdr = 'no' + if_xml = (''.join(if_xml)) % (if_name, cdr) + xapi.edit(xpath=_IF_XPATH % if_name, element=if_xml) + + xapi.set(xpath=_ZONE_XPATH + "[@name='%s']/network/layer3" % zone_name, + element='%s' % if_name) + xapi.set(xpath=_VR_XPATH + "[@name='default']/interface", + element='%s' % if_name) + + return True + + +def if_exists(xapi, if_name): + xpath = _IF_XPATH % if_name + xapi.get(xpath=xpath) + network = xapi.element_root.find('.//layer3') + return (network is not None) + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + if_name=dict(required=True), + zone_name=dict(required=True), + create_default_route=dict(type='bool', default=False), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + if_name = module.params['if_name'] + zone_name = module.params['zone_name'] + create_default_route = module.params['create_default_route'] + commit = module.params['commit'] + + ifexists = if_exists(xapi, if_name) + + if ifexists: + module.exit_json(changed=False, msg="interface exists, not changed") + + try: + changed = add_dhcp_if(xapi, if_name, zone_name, create_default_route) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_lic.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_lic.py new file mode 100644 index 00000000..6a40216d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_lic.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_lic +short_description: apply authcode to a device/instance +description: + - Apply an authcode to a device. + - The authcode should have been previously registered on the Palo Alto Networks support portal. + - The device should have Internet access. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + auth_code: + description: + - authcode to be applied + required: true + force: + description: + - whether to apply authcode even if device is already licensed + required: false + default: "false" + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' + - hosts: localhost + connection: local + tasks: + - name: Fetch license + community.network.panos_lic: + ip_address: "192.168.1.1" + password: "paloalto" + auth_code: "IBADCODE" + register: result + - name: Display serialnumber (if already registered) + ansible.builtin.debug: + var: "{{result.serialnumber}}" +''' + +RETURN = ''' +serialnumber: + description: serialnumber of the device in case that it has been already registered + returned: success + type: str + sample: 007200004214 +''' + + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_serial(xapi, module): + xapi.op(cmd="show system info", cmd_xml=True) + r = xapi.element_root + serial = r.find('.//serial') + if serial is None: + module.fail_json(msg="No tag in show system info") + + serial = serial.text + + return serial + + +def apply_authcode(xapi, module, auth_code): + try: + xapi.op(cmd='request license fetch auth-code "%s"' % auth_code, + cmd_xml=True) + except pan.xapi.PanXapiError: + if hasattr(xapi, 'xml_document'): + if 'Successfully' in xapi.xml_document: + return + + if 'Invalid Auth Code' in xapi.xml_document: + module.fail_json(msg="Invalid Auth Code") + + raise + + return + + +def fetch_authcode(xapi, module): + try: + xapi.op(cmd='request license fetch', cmd_xml=True) + except pan.xapi.PanXapiError: + if hasattr(xapi, 'xml_document'): + if 'Successfully' in xapi.xml_document: + return + + if 'Invalid Auth Code' in xapi.xml_document: + module.fail_json(msg="Invalid Auth Code") + + raise + + return + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + auth_code=dict(), + username=dict(default='admin'), + force=dict(type='bool', default=False) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + auth_code = module.params["auth_code"] + force = module.params['force'] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + if not force: + serialnumber = get_serial(xapi, module) + if serialnumber != 'unknown': + return module.exit_json(changed=False, serialnumber=serialnumber) + if auth_code: + apply_authcode(xapi, module, auth_code) + else: + fetch_authcode(xapi, module) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_loadcfg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_loadcfg.py new file mode 100644 index 00000000..6e34e2d8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_loadcfg.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_loadcfg +short_description: load configuration on PAN-OS device +description: + - Load configuration on PAN-OS device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + file: + description: + - configuration file to load + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# Import and load config file from URL + - name: Import configuration + panos_import: + ip_address: "192.168.1.1" + password: "admin" + url: "{{ConfigURL}}" + category: "configuration" + register: result + - name: Load configuration + community.network.panos_loadcfg: + ip_address: "192.168.1.1" + password: "admin" + file: "{{result.filename}}" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def load_cfgfile(xapi, module, ip_address, file_): + # load configuration file + cmd = '%s' %\ + file_ + + xapi.op(cmd=cmd) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + file=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + file_ = module.params['file'] + commit = module.params['commit'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + changed = load_cfgfile(xapi, module, ip_address, file_) + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_match_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_match_rule.py new file mode 100644 index 00000000..14d68b65 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_match_rule.py @@ -0,0 +1,388 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_match_rule +short_description: Test for match against a security rule on PAN-OS devices or Panorama management console. +description: + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. +author: "Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama NOT is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + rule_type: + description: + - Type of rule. Valid types are I(security) or I(nat). + required: true + choices: + - security + - nat + source_zone: + description: + - The source zone. + source_ip: + description: + - The source IP address. + required: true + source_port: + description: + - The source port. + source_user: + description: + - The source user or group. + to_interface: + description: + - The inbound interface in a NAT rule. + destination_zone: + description: + - The destination zone. + destination_ip: + description: + - The destination IP address. + destination_port: + description: + - The destination port. + application: + description: + - The application. + protocol: + description: + - The IP protocol number from 1 to 255. + category: + description: + - URL category + vsys_id: + description: + - ID of the VSYS object. + default: "vsys1" + required: true +''' + +EXAMPLES = ''' +- name: Check security rules for Google DNS + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'security' + source_ip: '10.0.0.0' + destination_ip: '8.8.8.8' + application: 'dns' + destination_port: '53' + protocol: '17' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check security rules inbound SSH with user match + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'security' + source_ip: '0.0.0.0' + source_user: 'mydomain\\jsmith' + destination_ip: '192.168.100.115' + destination_port: '22' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check NAT rules for source NAT + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'nat' + source_zone: 'Prod-DMZ' + source_ip: '10.10.118.50' + to_interface: 'ethernet1/2' + destination_zone: 'Internet' + destination_ip: '0.0.0.0' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check NAT rules for inbound web + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'nat' + source_zone: 'Internet' + source_ip: '0.0.0.0' + to_interface: 'ethernet1/1' + destination_zone: 'Prod DMZ' + destination_ip: '192.168.118.50' + destination_port: '80' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check security rules for outbound POP3 in vsys4 + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + vsys_id: 'vsys4' + rule_type: 'security' + source_ip: '10.0.0.0' + destination_ip: '4.3.2.1' + application: 'pop3' + destination_port: '110' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from pan.xapi import PanXapiError + from pan.xapi import PanXapiError + from pandevice import base + from pandevice import policies + from pandevice import panorama + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def create_security_test(**kwargs): + security_test = 'test security-policy-match' + + # Add the source IP (required) + if kwargs['source_ip']: + security_test += ' source \"%s\"' % kwargs['source_ip'] + + # Add the source user (optional) + if kwargs['source_user']: + security_test += ' source-user \"%s\"' % kwargs['source_user'] + + # Add the destination IP (required) + if kwargs['destination_ip']: + security_test += ' destination \"%s\"' % kwargs['destination_ip'] + + # Add the application (optional) + if kwargs['application']: + security_test += ' application \"%s\"' % kwargs['application'] + + # Add the destination port (required) + if kwargs['destination_port']: + security_test += ' destination-port \"%s\"' % kwargs['destination_port'] + + # Add the IP protocol number (required) + if kwargs['protocol']: + security_test += ' protocol \"%s\"' % kwargs['protocol'] + + # Add the URL category (optional) + if kwargs['category']: + security_test += ' category \"%s\"' % kwargs['category'] + + # Return the resulting string + return security_test + + +def create_nat_test(**kwargs): + nat_test = 'test nat-policy-match' + + # Add the source zone (optional) + if kwargs['source_zone']: + nat_test += ' from \"%s\"' % kwargs['source_zone'] + + # Add the source IP (required) + if kwargs['source_ip']: + nat_test += ' source \"%s\"' % kwargs['source_ip'] + + # Add the source user (optional) + if kwargs['source_port']: + nat_test += ' source-port \"%s\"' % kwargs['source_port'] + + # Add inbound interface (optional) + if kwargs['to_interface']: + nat_test += ' to-interface \"%s\"' % kwargs['to_interface'] + + # Add the destination zone (optional) + if kwargs['destination_zone']: + nat_test += ' to \"%s\"' % kwargs['destination_zone'] + + # Add the destination IP (required) + if kwargs['destination_ip']: + nat_test += ' destination \"%s\"' % kwargs['destination_ip'] + + # Add the destination port (optional) + if kwargs['destination_port']: + nat_test += ' destination-port \"%s\"' % kwargs['destination_port'] + + # Add the IP protocol number (required) + if kwargs['protocol']: + nat_test += ' protocol \"%s\"' % kwargs['protocol'] + + # Return the resulting string + return nat_test + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + vsys_id=dict(default='vsys1'), + rule_type=dict(required=True, choices=['security', 'nat']), + source_zone=dict(default=None), + source_ip=dict(default=None), + source_user=dict(default=None), + source_port=dict(default=None, type=int), + to_interface=dict(default=None), + destination_zone=dict(default=None), + category=dict(default=None), + application=dict(default=None), + protocol=dict(default=None, type=int), + destination_ip=dict(default=None), + destination_port=dict(default=None, type=int) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + vsys_id = module.params['vsys_id'] + rule_type = module.params['rule_type'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_user = module.params['source_user'] + source_port = module.params['source_port'] + to_interface = module.params['to_interface'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + destination_port = module.params['destination_port'] + category = module.params['category'] + application = module.params['application'] + protocol = module.params['protocol'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # Fail the module if this is a Panorama instance + if isinstance(device, panorama.Panorama): + module.fail_json( + failed=1, + msg='Panorama is not supported.' + ) + + # Create and attach security and NAT rulebases. Then populate them. + sec_rule_base = nat_rule_base = policies.Rulebase() + device.add(sec_rule_base) + device.add(nat_rule_base) + policies.SecurityRule.refreshall(sec_rule_base) + policies.NatRule.refreshall(nat_rule_base) + + # Which action shall we take on the object? + if rule_type == 'security': + # Search for the object + test_string = create_security_test( + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + destination_port=destination_port, + application=application, + protocol=protocol, + category=category + ) + elif rule_type == 'nat': + test_string = create_nat_test( + source_zone=source_zone, + source_ip=source_ip, + source_port=source_port, + to_interface=to_interface, + destination_zone=destination_zone, + destination_ip=destination_ip, + destination_port=destination_port, + protocol=protocol + ) + + # Submit the op command with the appropriate test string + try: + response = device.op(cmd=test_string, vsys=vsys_id) + except PanXapiError as exc: + module.fail_json(msg=exc.message) + + if response.find('result/rules').__len__() == 1: + rule_name = response.find('result/rules/entry').text.split(';')[0] + elif rule_type == 'nat': + module.exit_json(msg='No matching NAT rule.') + else: + module.fail_json(msg='Rule match failed. Please check playbook syntax.') + + if rule_type == 'security': + rule_match = sec_rule_base.find(rule_name, policies.SecurityRule) + elif rule_type == 'nat': + rule_match = nat_rule_base.find(rule_name, policies.NatRule) + + # Print out the rule + module.exit_json( + stdout_lines=json.dumps(xmltodict.parse(rule_match.element_str()), indent=2), + msg='Rule matched' + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_mgtconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_mgtconfig.py new file mode 100644 index 00000000..aedb2dc5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_mgtconfig.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_mgtconfig +short_description: configure management settings of device +description: + - Configure management settings of device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + dns_server_primary: + description: + - address of primary DNS server + dns_server_secondary: + description: + - address of secondary DNS server + panorama_primary: + description: + - address of primary Panorama server + panorama_secondary: + description: + - address of secondary Panorama server + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Set dns and panorama + community.network.panos_mgtconfig: + ip_address: "192.168.1.1" + password: "admin" + dns_server_primary: "1.1.1.1" + dns_server_secondary: "1.1.1.2" + panorama_primary: "1.1.1.3" + panorama_secondary: "1.1.1.4" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_XPATH_DNS_SERVERS = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/deviceconfig/system/dns-setting/servers" +_XPATH_PANORAMA_SERVERS = "/config" +\ + "/devices/entry[@name='localhost.localdomain']" +\ + "/deviceconfig/system" + + +def set_dns_server(xapi, new_dns_server, primary=True): + if primary: + tag = "primary" + else: + tag = "secondary" + xpath = _XPATH_DNS_SERVERS + "/" + tag + + # check the current element value + xapi.get(xpath) + val = xapi.element_root.find(".//" + tag) + if val is not None: + # element exists + val = val.text + if val == new_dns_server: + return False + + element = "<%(tag)s>%(value)s" %\ + dict(tag=tag, value=new_dns_server) + xapi.edit(xpath, element) + + return True + + +def set_panorama_server(xapi, new_panorama_server, primary=True): + if primary: + tag = "panorama-server" + else: + tag = "panorama-server-2" + xpath = _XPATH_PANORAMA_SERVERS + "/" + tag + + # check the current element value + xapi.get(xpath) + val = xapi.element_root.find(".//" + tag) + if val is not None: + # element exists + val = val.text + if val == new_panorama_server: + return False + + element = "<%(tag)s>%(value)s" %\ + dict(tag=tag, value=new_panorama_server) + xapi.edit(xpath, element) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + dns_server_primary=dict(), + dns_server_secondary=dict(), + panorama_primary=dict(), + panorama_secondary=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + dns_server_primary = module.params['dns_server_primary'] + dns_server_secondary = module.params['dns_server_secondary'] + panorama_primary = module.params['panorama_primary'] + panorama_secondary = module.params['panorama_secondary'] + commit = module.params['commit'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + changed = False + try: + if dns_server_primary is not None: + changed |= set_dns_server(xapi, dns_server_primary, primary=True) + if dns_server_secondary is not None: + changed |= set_dns_server(xapi, dns_server_secondary, primary=False) + if panorama_primary is not None: + changed |= set_panorama_server(xapi, panorama_primary, primary=True) + if panorama_secondary is not None: + changed |= set_panorama_server(xapi, panorama_secondary, primary=False) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_nat_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_nat_rule.py new file mode 100644 index 00000000..23f7ae7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_nat_rule.py @@ -0,0 +1,467 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, techbizdev +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: panos_nat_rule +short_description: create a policy NAT rule +description: > + - Create a policy nat rule. Keep in mind that we can either end up configuring source NAT, destination NAT, or + both. Instead of splitting it into two we will make a fair attempt to determine which one the user wants. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + required: true + choices: + - add + - update + - delete + - find + devicegroup: + description: + - If Panorama, the device group to put this rule in. + rule_name: + description: + - name of the SNAT rule + required: true + description: + description: + - The description + source_zone: + description: + - list of source zones + required: true + destination_zone: + description: + - destination zone + required: true + source_ip: + description: + - list of source addresses + default: ["any"] + destination_ip: + description: + - list of destination addresses + default: ["any"] + service: + description: + - service + default: "any" + snat_type: + description: + - type of source translation + choices: + - static-ip + - dynamic-ip-and-port + - dynamic-ip + snat_address_type: + description: + - type of source translation. Supported values are I(translated-address)/I(translated-address). + default: 'interface-address' + choices: + - interface-address + - translated-address + snat_static_address: + description: + - Source NAT translated address. Used with Static-IP translation. + snat_dynamic_address: + description: + - Source NAT translated address. Used with Dynamic-IP and Dynamic-IP-and-Port. + snat_interface: + description: + - snat interface + snat_interface_address: + description: + - snat interface address + snat_bidirectional: + description: + - bidirectional flag + type: bool + default: 'no' + dnat_address: + description: + - dnat translated address + dnat_port: + description: + - dnat translated port + tag_name: + description: + - Tag for the NAT rule. + to_interface: + description: + - Destination interface. + default: 'any' + commit: + description: + - Commit configuration if changed. + type: bool + default: 'yes' +''' + +EXAMPLES = ''' +# Create a source and destination nat rule + - name: Create NAT SSH rule for 10.0.1.101 + community.network.panos_nat_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_name: "Web SSH" + source_zone: ["external"] + destination_zone: "external" + source: ["any"] + destination: ["10.0.0.100"] + service: "service-tcp-221" + snat_type: "dynamic-ip-and-port" + snat_interface: "ethernet1/2" + dnat_address: "10.0.1.101" + dnat_port: "22" +''' + +RETURN = ''' +# Default return values +''' + +# import pydevd +# pydevd.settrace('localhost', port=60374, stdoutToServer=True, stderrToServer=True) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, pandevice.firewall.Firewall): + rulebase = pandevice.policies.Rulebase() + device.add(rulebase) + elif isinstance(device, pandevice.panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.NatRule.refreshall(rulebase) + return rulebase + + +def find_rule(rulebase, rule_name): + # Search for the rule name + rule = rulebase.find(rule_name) + if rule: + return rule + else: + return False + + +def create_nat_rule(**kwargs): + nat_rule = policies.NatRule( + name=kwargs['rule_name'], + description=kwargs['description'], + fromzone=kwargs['source_zone'], + source=kwargs['source_ip'], + tozone=kwargs['destination_zone'], + destination=kwargs['destination_ip'], + service=kwargs['service'], + to_interface=kwargs['to_interface'], + nat_type=kwargs['nat_type'] + ) + + # Source translation: Static IP + if kwargs['snat_type'] in ['static-ip'] and kwargs['snat_static_address']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_static_translated_address = kwargs['snat_static_address'] + # Bi-directional flag set? + if kwargs['snat_bidirectional']: + nat_rule.source_translation_static_bi_directional = kwargs['snat_bidirectional'] + + # Source translation: Dynamic IP and port + elif kwargs['snat_type'] in ['dynamic-ip-and-port']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_address_type = kwargs['snat_address_type'] + # Interface address? + if kwargs['snat_interface']: + nat_rule.source_translation_interface = kwargs['snat_interface'] + # Interface IP? + if kwargs['snat_interface_address']: + nat_rule.source_translation_ip_address = kwargs['snat_interface_address'] + else: + nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address'] + + # Source translation: Dynamic IP + elif kwargs['snat_type'] in ['dynamic-ip']: + if kwargs['snat_dynamic_address']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address'] + else: + return False + + # Destination translation + if kwargs['dnat_address']: + nat_rule.destination_translated_address = kwargs['dnat_address'] + if kwargs['dnat_port']: + nat_rule.destination_translated_port = kwargs['dnat_port'] + + # Any tags? + if 'tag_name' in kwargs: + nat_rule.tag = kwargs['tag_name'] + + return nat_rule + + +def add_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.create() + return True + else: + return False + + +def update_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.apply() + return True + else: + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + username=dict(default='admin'), + password=dict(required=True, no_log=True), + api_key=dict(no_log=True), + operation=dict(required=True, choices=['add', 'update', 'delete', 'find']), + rule_name=dict(required=True), + description=dict(), + tag_name=dict(), + source_zone=dict(type='list'), + source_ip=dict(type='list', default=['any']), + destination_zone=dict(), + destination_ip=dict(type='list', default=['any']), + service=dict(default='any'), + to_interface=dict(default='any'), + snat_type=dict(choices=['static-ip', 'dynamic-ip-and-port', 'dynamic-ip']), + snat_address_type=dict(choices=['interface-address', 'translated-address'], default='interface-address'), + snat_static_address=dict(), + snat_dynamic_address=dict(type='list'), + snat_interface=dict(), + snat_interface_address=dict(), + snat_bidirectional=dict(type='bool', default=False), + dnat_address=dict(), + dnat_port=dict(), + devicegroup=dict(), + commit=dict(type='bool', default=True) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + rule_name = module.params['rule_name'] + description = module.params['description'] + tag_name = module.params['tag_name'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + service = module.params['service'] + to_interface = module.params['to_interface'] + nat_type = 'ipv4' + snat_type = module.params['snat_type'] + snat_address_type = module.params['snat_address_type'] + snat_static_address = module.params['snat_static_address'] + snat_dynamic_address = module.params['snat_dynamic_address'] + snat_interface = module.params['snat_interface'] + snat_interface_address = module.params['snat_interface_address'] + snat_bidirectional = module.params['snat_bidirectional'] + dnat_address = module.params['dnat_address'] + dnat_port = module.params['dnat_port'] + devicegroup = module.params['devicegroup'] + + commit = module.params['commit'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # Get the rulebase + rulebase = get_rulebase(device, dev_group) + + # Which action shall we take on the object? + if operation == "find": + # Search for the rule + match = find_rule(rulebase, rule_name) + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Rule matched' + ) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "delete": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, delete it + if match: + try: + match.delete() + if commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted.' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "add": + # Look for required parameters + if source_zone and destination_zone and nat_type: + pass + else: + module.fail_json(msg='Missing parameter. Required: source_zone, destination_zone, nat_type') + # Search for the rule. Fail if found. + match = find_rule(rulebase, rule_name) + if match: + module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name) + else: + try: + new_rule = create_nat_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + destination_ip=destination_ip, + service=service, + to_interface=to_interface, + nat_type=nat_type, + snat_type=snat_type, + snat_address_type=snat_address_type, + snat_static_address=snat_static_address, + snat_dynamic_address=snat_dynamic_address, + snat_interface=snat_interface, + snat_interface_address=snat_interface_address, + snat_bidirectional=snat_bidirectional, + dnat_address=dnat_address, + dnat_port=dnat_port + ) + changed = add_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully added.' % rule_name) + elif operation == 'update': + # Search for the rule. Update if found. + match = find_rule(rulebase, rule_name) + if match: + try: + new_rule = create_nat_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + destination_ip=destination_ip, + service=service, + to_interface=to_interface, + nat_type=nat_type, + snat_type=snat_type, + snat_address_type=snat_address_type, + snat_static_address=snat_static_address, + snat_dynamic_address=snat_dynamic_address, + snat_interface=snat_interface, + snat_interface_address=snat_interface_address, + snat_bidirectional=snat_bidirectional, + dnat_address=dnat_address, + dnat_port=dnat_port + ) + changed = update_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated.' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_object.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_object.py new file mode 100644 index 00000000..9c3628f2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_object.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_object +short_description: create/read/update/delete object in PAN-OS or Panorama +description: + - Policy objects form the match criteria for policy rules and many other functions in PAN-OS. These may include + address object, address groups, service objects, service groups, and tag. +author: "Bob Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device or Panorama management console being configured. + required: true + username: + description: + - Username credentials to use for authentication. + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The operation to be performed. Supported values are I(add)/I(delete)/I(find). + required: true + choices: + - add + - update + - delete + - find + addressobject: + description: + - The name of the address object. + address: + description: + - The IP address of the host or network in CIDR notation. + address_type: + description: + - The type of address object definition. Valid types are I(ip-netmask) and I(ip-range). + default: 'ip-netmask' + choices: + - ip-netmask + - ip-range + - fqdn + addressgroup: + description: + - A static group of address objects or dynamic address group. + static_value: + description: + - A group of address objects to be used in an addressgroup definition. + dynamic_value: + description: + - The filter match criteria to be used in a dynamic addressgroup definition. + serviceobject: + description: + - The name of the service object. + source_port: + description: + - The source port to be used in a service object definition. + destination_port: + description: + - The destination port to be used in a service object definition. + protocol: + description: + - The IP protocol to be used in a service object definition. Valid values are I(tcp) or I(udp). + choices: + - tcp + - udp + servicegroup: + description: + - A group of service objects. + services: + description: + - The group of service objects used in a servicegroup definition. + description: + description: + - The description of the object. + tag_name: + description: + - The name of an object or rule tag. + color: + description: > + - The color of the tag object. Valid values are I(red, green, blue, yellow, copper, orange, purple, gray, + light green, cyan, light gray, blue gray, lime, black, gold, and brown). + choices: + - red + - green + - blue + - yellow + - copper + - orange + - purple + - gray + - light green + - cyan + - light gray + - blue gray + - lime + - black + - gold + - brown + devicegroup: + description: > + - The name of the Panorama device group. The group must exist on Panorama. If device group is not defined it + is assumed that we are contacting a firewall. +''' + +EXAMPLES = ''' +- name: Search for shared address object + community.network.panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'find' + address: 'DevNet' + +- name: Create an address group in devicegroup using API key + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + addressgroup: 'Prod_DB_Svrs' + static_value: ['prod-db1', 'prod-db2', 'prod-db3'] + description: 'Production DMZ database servers' + tag_name: 'DMZ' + devicegroup: 'DMZ Firewalls' + +- name: Create a global service for TCP 3306 + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + serviceobject: 'mysql-3306' + destination_port: '3306' + protocol: 'tcp' + description: 'MySQL on tcp/3306' + +- name: Create a global tag + community.network.panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + tag_name: 'ProjectX' + color: 'yellow' + description: 'Associated with Project X' + +- name: Delete an address object from a devicegroup using API key + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'delete' + addressobject: 'Win2K test' +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def find_object(device, dev_group, obj_name, obj_type): + # Get the firewall objects + obj_type.refreshall(device) + if isinstance(device, pandevice.firewall.Firewall): + addr = device.find(obj_name, obj_type) + return addr + elif isinstance(device, pandevice.panorama.Panorama): + addr = device.find(obj_name, obj_type) + if addr is None: + if dev_group: + device.add(dev_group) + obj_type.refreshall(dev_group) + addr = dev_group.find(obj_name, obj_type) + return addr + else: + return False + + +def create_object(**kwargs): + if kwargs['addressobject']: + newobject = objects.AddressObject( + name=kwargs['addressobject'], + value=kwargs['address'], + type=kwargs['address_type'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.type and newobject.value: + return newobject + else: + return False + elif kwargs['addressgroup']: + newobject = objects.AddressGroup( + name=kwargs['addressgroup'], + static_value=kwargs['static_value'], + dynamic_value=kwargs['dynamic_value'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.static_value or newobject.dynamic_value: + return newobject + else: + return False + elif kwargs['serviceobject']: + newobject = objects.ServiceObject( + name=kwargs['serviceobject'], + protocol=kwargs['protocol'], + source_port=kwargs['source_port'], + destination_port=kwargs['destination_port'], + tag=kwargs['tag_name'] + ) + if newobject.protocol and newobject.destination_port: + return newobject + else: + return False + elif kwargs['servicegroup']: + newobject = objects.ServiceGroup( + name=kwargs['servicegroup'], + value=kwargs['services'], + tag=kwargs['tag_name'] + ) + if newobject.value: + return newobject + else: + return False + elif kwargs['tag_name']: + newobject = objects.Tag( + name=kwargs['tag_name'], + color=kwargs['color'], + comments=kwargs['description'] + ) + if newobject.name: + return newobject + else: + return False + else: + return False + + +def add_object(device, dev_group, new_object): + if dev_group: + dev_group.add(new_object) + else: + device.add(new_object) + new_object.create() + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + operation=dict(required=True, choices=['add', 'update', 'delete', 'find']), + addressobject=dict(default=None), + addressgroup=dict(default=None), + serviceobject=dict(default=None), + servicegroup=dict(default=None), + address=dict(default=None), + address_type=dict(default='ip-netmask', choices=['ip-netmask', 'ip-range', 'fqdn']), + static_value=dict(type='list', default=None), + dynamic_value=dict(default=None), + protocol=dict(default=None, choices=['tcp', 'udp']), + source_port=dict(default=None), + destination_port=dict(default=None), + services=dict(type='list', default=None), + description=dict(default=None), + tag_name=dict(default=None), + color=dict(default=None, choices=['red', 'green', 'blue', 'yellow', 'copper', 'orange', 'purple', + 'gray', 'light green', 'cyan', 'light gray', 'blue gray', + 'lime', 'black', 'gold', 'brown']), + devicegroup=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']], + mutually_exclusive=[['addressobject', 'addressgroup', + 'serviceobject', 'servicegroup', + 'tag_name']] + ) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + addressobject = module.params['addressobject'] + addressgroup = module.params['addressgroup'] + serviceobject = module.params['serviceobject'] + servicegroup = module.params['servicegroup'] + address = module.params['address'] + address_type = module.params['address_type'] + static_value = module.params['static_value'] + dynamic_value = module.params['dynamic_value'] + protocol = module.params['protocol'] + source_port = module.params['source_port'] + destination_port = module.params['destination_port'] + services = module.params['services'] + description = module.params['description'] + tag_name = module.params['tag_name'] + color = module.params['color'] + devicegroup = module.params['devicegroup'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # What type of object are we talking about? + if addressobject: + obj_name = addressobject + obj_type = objects.AddressObject + elif addressgroup: + obj_name = addressgroup + obj_type = objects.AddressGroup + elif serviceobject: + obj_name = serviceobject + obj_type = objects.ServiceObject + elif servicegroup: + obj_name = servicegroup + obj_type = objects.ServiceGroup + elif tag_name: + obj_name = tag_name + obj_type = objects.Tag + else: + module.fail_json(msg='No object type defined!') + + # Which operation shall we perform on the object? + if operation == "find": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Object matched' + ) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "delete": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, delete it + if match: + try: + match.delete() + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Object \'%s\' successfully deleted' % obj_name) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "add": + # Search for the object. Fail if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + module.fail_json(msg='Object \'%s\' already exists. Use operation: \'update\' to change it.' % obj_name) + else: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Object \'%s\' successfully added' % obj_name) + elif operation == "update": + # Search for the object. Update if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Object \'%s\' successfully updated.' % obj_name) + else: + module.fail_json(msg='Object \'%s\' does not exist. Use operation: \'add\' to add it.' % obj_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_op.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_op.py new file mode 100644 index 00000000..0d027962 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_op.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_op +short_description: execute arbitrary OP commands on PANW devices (e.g. show interface all) +description: This module will allow user to pass and execute any supported OP command on the PANW device. +author: "Ivan Bojer (@ivanbojer)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is NOT supported. + - Panorama is NOT supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device or Panorama management console being configured. + required: true + username: + description: + - Username credentials to use for authentication. + required: false + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + cmd: + description: + - The OP command to be performed. + required: true +''' + +EXAMPLES = ''' +- name: Show list of all interfaces + community.network.panos_op: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + cmd: 'show interfaces all' + +- name: Show system info + community.network.panos_op: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + cmd: 'show system info' +''' + +RETURN = ''' +stdout: + description: output of the given OP command as JSON formatted string + returned: success + type: str + sample: "{system: {app-release-date: 2017/05/01 15:09:12}}" + +stdout_xml: + description: output of the given OP command as JSON formatted string + returned: success + type: str + sample: "fw2" +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + cmd=dict(required=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + cmd = module.params['cmd'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + changed = False + try: + xml_output = device.op(cmd, xml=True) + changed = True + except PanXapiError as exc: + if 'non NULL value' in exc.message: + # rewrap and call again + cmd_array = cmd.split() + cmd_array_len = len(cmd_array) + cmd_array[cmd_array_len - 1] = '\"' + cmd_array[cmd_array_len - 1] + '\"' + cmd2 = ' '.join(cmd_array) + try: + xml_output = device.op(cmd2, xml=True) + changed = True + except PanXapiError as exc: + module.fail_json(msg=exc.message) + + obj_dict = xmltodict.parse(xml_output) + json_output = json.dumps(obj_dict) + + module.exit_json(changed=changed, msg="Done", stdout=json_output, stdout_xml=xml_output) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_pg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_pg.py new file mode 100644 index 00000000..c8397e9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_pg.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_pg +short_description: create a security profiles group +description: + - Create a security profile group +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + pg_name: + description: + - name of the security profile group + required: true + data_filtering: + description: + - name of the data filtering profile + file_blocking: + description: + - name of the file blocking profile + spyware: + description: + - name of the spyware profile + url_filtering: + description: + - name of the url filtering profile + virus: + description: + - name of the anti-virus profile + vulnerability: + description: + - name of the vulnerability profile + wildfire: + description: + - name of the wildfire analysis profile + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Setup security profile group + community.network.panos_pg: + ip_address: "192.168.1.1" + password: "admin" + username: "admin" + pg_name: "pg-default" + virus: "default" + spyware: "default" + vulnerability: "default" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_PG_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry[@name='vsys1']" +\ + "/profile-group/entry[@name='%s']" + + +def pg_exists(xapi, pg_name): + xapi.get(_PG_XPATH % pg_name) + e = xapi.element_root.find('.//entry') + if e is None: + return False + return True + + +def add_pg(xapi, pg_name, data_filtering, file_blocking, spyware, + url_filtering, virus, vulnerability, wildfire): + if pg_exists(xapi, pg_name): + return False + + exml = [] + + if data_filtering is not None: + exml.append('%s' % + data_filtering) + if file_blocking is not None: + exml.append('%s' % + file_blocking) + if spyware is not None: + exml.append('%s' % + spyware) + if url_filtering is not None: + exml.append('%s' % + url_filtering) + if virus is not None: + exml.append('%s' % + virus) + if vulnerability is not None: + exml.append('%s' % + vulnerability) + if wildfire is not None: + exml.append('%s' % + wildfire) + + exml = ''.join(exml) + xapi.set(xpath=_PG_XPATH % pg_name, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + pg_name=dict(required=True), + data_filtering=dict(), + file_blocking=dict(), + spyware=dict(), + url_filtering=dict(), + virus=dict(), + vulnerability=dict(), + wildfire=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + pg_name = module.params['pg_name'] + data_filtering = module.params['data_filtering'] + file_blocking = module.params['file_blocking'] + spyware = module.params['spyware'] + url_filtering = module.params['url_filtering'] + virus = module.params['virus'] + vulnerability = module.params['vulnerability'] + wildfire = module.params['wildfire'] + commit = module.params['commit'] + + try: + changed = add_pg(xapi, pg_name, data_filtering, file_blocking, + spyware, url_filtering, virus, vulnerability, wildfire) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_query_rules.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_query_rules.py new file mode 100644 index 00000000..e7ebd769 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_query_rules.py @@ -0,0 +1,494 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_query_rules +short_description: PANOS module that allows search for security rules in PANW NGFW devices. +description: > + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. The + policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the + traffic is applied, the more specific rules must precede the more general ones. +author: "Bob Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtains from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS firewall or Panorama management console being queried. + required: true + username: + description: + - Username credentials to use for authentication. + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + application: + description: + - Name of the application or application group to be queried. + source_zone: + description: + - Name of the source security zone to be queried. + source_ip: + description: + - The source IP address to be queried. + source_port: + description: + - The source port to be queried. + destination_zone: + description: + - Name of the destination security zone to be queried. + destination_ip: + description: + - The destination IP address to be queried. + destination_port: + description: + - The destination port to be queried. + protocol: + description: + - The protocol used to be queried. Must be either I(tcp) or I(udp). + choices: + - tcp + - udp + tag_name: + description: + - Name of the rule tag to be queried. + devicegroup: + description: + - The Panorama device group in which to conduct the query. +''' + +EXAMPLES = ''' +- name: Search for rules with tcp/3306 + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + source_zone: 'DevNet' + destination_zone: 'DevVPC' + destination_port: '3306' + protocol: 'tcp' + +- name: Search devicegroup for inbound rules to dmz host + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + destination_zone: 'DMZ' + destination_ip: '10.100.42.18' + address: 'DeviceGroupA' + +- name: Search for rules containing a specified rule tag + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + tag_name: 'ProjectX' +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import ipaddress + import xmltodict + import json + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, firewall.Firewall): + rulebase = policies.Rulebase() + device.add(rulebase) + elif isinstance(device, panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.SecurityRule.refreshall(rulebase) + return rulebase + + +def get_object(device, dev_group, obj_name): + # Search global address objects + match = device.find(obj_name, objects.AddressObject) + if match: + return match + + # Search global address groups + match = device.find(obj_name, objects.AddressGroup) + if match: + return match + + # Search Panorama device group + if isinstance(device, pandevice.panorama.Panorama): + # Search device group address objects + match = dev_group.find(obj_name, objects.AddressObject) + if match: + return match + + # Search device group address groups + match = dev_group.find(obj_name, objects.AddressGroup) + if match: + return match + return False + + +def addr_in_obj(addr, obj): + ip = ipaddress.ip_address(addr) + # Process address objects + if isinstance(obj, objects.AddressObject): + if obj.type == 'ip-netmask': + net = ipaddress.ip_network(obj.value) + if ip in net: + return True + if obj.type == 'ip-range': + ip_range = obj.value.split('-') + lower = ipaddress.ip_address(ip_range[0]) + upper = ipaddress.ip_address(ip_range[1]) + if lower < ip < upper: + return True + return False + + +def get_services(device, dev_group, svc_list, obj_list): + for svc in svc_list: + + # Search global address objects + global_obj_match = device.find(svc, objects.ServiceObject) + if global_obj_match: + obj_list.append(global_obj_match) + + # Search global address groups + global_grp_match = device.find(svc, objects.ServiceGroup) + if global_grp_match: + get_services(device, dev_group, global_grp_match.value, obj_list) + + # Search Panorama device group + if isinstance(device, pandevice.panorama.Panorama): + + # Search device group address objects + dg_obj_match = dev_group.find(svc, objects.ServiceObject) + if dg_obj_match: + obj_list.append(dg_obj_match) + + # Search device group address groups + dg_grp_match = dev_group.find(svc, objects.ServiceGroup) + if dg_grp_match: + get_services(device, dev_group, dg_grp_match.value, obj_list) + + return obj_list + + +def port_in_svc(orientation, port, protocol, obj): + # Process address objects + if orientation == 'source': + for x in obj.source_port.split(','): + if '-' in x: + port_range = x.split('-') + lower = int(port_range[0]) + upper = int(port_range[1]) + if (lower <= int(port) <= upper) and (obj.protocol == protocol): + return True + else: + if port == x and obj.protocol == protocol: + return True + elif orientation == 'destination': + for x in obj.destination_port.split(','): + if '-' in x: + port_range = x.split('-') + lower = int(port_range[0]) + upper = int(port_range[1]) + if (lower <= int(port) <= upper) and (obj.protocol == protocol): + return True + else: + if port == x and obj.protocol == protocol: + return True + return False + + +def get_tag(device, dev_group, tag_name): + # Search global address objects + match = device.find(tag_name, objects.Tag) + if match: + return match + # Search Panorama device group + if isinstance(device, panorama.Panorama): + # Search device group address objects + match = dev_group.find(tag_name, objects.Tag) + if match: + return match + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + application=dict(default=None), + source_zone=dict(default=None), + destination_zone=dict(default=None), + source_ip=dict(default=None), + destination_ip=dict(default=None), + source_port=dict(default=None), + destination_port=dict(default=None), + protocol=dict(default=None, choices=['tcp', 'udp']), + tag_name=dict(default=None), + devicegroup=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']] + ) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + application = module.params['application'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_port = module.params['source_port'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + destination_port = module.params['destination_port'] + protocol = module.params['protocol'] + tag_name = module.params['tag_name'] + devicegroup = module.params['devicegroup'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # Grab the global objects + objects.AddressObject.refreshall(device) + objects.AddressGroup.refreshall(device) + objects.ServiceObject.refreshall(device) + objects.ServiceGroup.refreshall(device) + objects.Tag.refreshall(device) + + # If Panorama, validate the devicegroup and grab the devicegroup objects + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + objects.AddressObject.refreshall(dev_group) + objects.AddressGroup.refreshall(dev_group) + objects.ServiceObject.refreshall(dev_group) + objects.ServiceGroup.refreshall(dev_group) + objects.Tag.refreshall(dev_group) + else: + module.fail_json( + failed=1, + msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup + ) + + # Build the rulebase and produce list + rulebase = get_rulebase(device, dev_group) + rulelist = rulebase.children + hitbase = policies.Rulebase() + loose_match = True + + # Process each rule + for rule in rulelist: + hitlist = [] + + if source_zone: + source_zone_match = False + if loose_match and 'any' in rule.fromzone: + source_zone_match = True + else: + for object_string in rule.fromzone: + if object_string == source_zone: + source_zone_match = True + hitlist.append(source_zone_match) + + if destination_zone: + destination_zone_match = False + if loose_match and 'any' in rule.tozone: + destination_zone_match = True + else: + for object_string in rule.tozone: + if object_string == destination_zone: + destination_zone_match = True + hitlist.append(destination_zone_match) + + if source_ip: + source_ip_match = False + if loose_match and 'any' in rule.source: + source_ip_match = True + else: + for object_string in rule.source: + # Get a valid AddressObject or AddressGroup + obj = get_object(device, dev_group, object_string) + # Otherwise the object_string is not an object and should be handled differently + if obj is False: + if '-' in object_string: + obj = ipaddress.ip_address(source_ip) + source_range = object_string.split('-') + source_lower = ipaddress.ip_address(source_range[0]) + source_upper = ipaddress.ip_address(source_range[1]) + if source_lower <= obj <= source_upper: + source_ip_match = True + else: + if source_ip == object_string: + source_ip_match = True + if isinstance(obj, objects.AddressObject) and addr_in_obj(source_ip, obj): + source_ip_match = True + elif isinstance(obj, objects.AddressGroup) and obj.static_value: + for member_string in obj.static_value: + member = get_object(device, dev_group, member_string) + if addr_in_obj(source_ip, member): + source_ip_match = True + hitlist.append(source_ip_match) + + if destination_ip: + destination_ip_match = False + if loose_match and 'any' in rule.destination: + destination_ip_match = True + else: + for object_string in rule.destination: + # Get a valid AddressObject or AddressGroup + obj = get_object(device, dev_group, object_string) + # Otherwise the object_string is not an object and should be handled differently + if obj is False: + if '-' in object_string: + obj = ipaddress.ip_address(destination_ip) + destination_range = object_string.split('-') + destination_lower = ipaddress.ip_address(destination_range[0]) + destination_upper = ipaddress.ip_address(destination_range[1]) + if destination_lower <= obj <= destination_upper: + destination_ip_match = True + else: + if destination_ip == object_string: + destination_ip_match = True + if isinstance(obj, objects.AddressObject) and addr_in_obj(destination_ip, obj): + destination_ip_match = True + elif isinstance(obj, objects.AddressGroup) and obj.static_value: + for member_string in obj.static_value: + member = get_object(device, dev_group, member_string) + if addr_in_obj(destination_ip, member): + destination_ip_match = True + hitlist.append(destination_ip_match) + + if source_port: + source_port_match = False + orientation = 'source' + if loose_match and (rule.service[0] == 'any'): + source_port_match = True + elif rule.service[0] == 'application-default': + source_port_match = False # Fix this once apps are supported + else: + service_list = [] + service_list = get_services(device, dev_group, rule.service, service_list) + for obj in service_list: + if port_in_svc(orientation, source_port, protocol, obj): + source_port_match = True + break + hitlist.append(source_port_match) + + if destination_port: + destination_port_match = False + orientation = 'destination' + if loose_match and (rule.service[0] == 'any'): + destination_port_match = True + elif rule.service[0] == 'application-default': + destination_port_match = False # Fix this once apps are supported + else: + service_list = [] + service_list = get_services(device, dev_group, rule.service, service_list) + for obj in service_list: + if port_in_svc(orientation, destination_port, protocol, obj): + destination_port_match = True + break + hitlist.append(destination_port_match) + + if tag_name: + tag_match = False + if rule.tag: + for object_string in rule.tag: + obj = get_tag(device, dev_group, object_string) + if obj and (obj.name == tag_name): + tag_match = True + hitlist.append(tag_match) + + # Add to hit rulebase + if False not in hitlist: + hitbase.add(rule) + + # Dump the hit rulebase + if hitbase.children: + output_string = xmltodict.parse(hitbase.element_str()) + module.exit_json( + stdout_lines=json.dumps(output_string, indent=2), + msg='%s of %s rules matched' % (hitbase.children.__len__(), rulebase.children.__len__()) + ) + else: + module.fail_json(msg='No matching rules found.') + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_restart.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_restart.py new file mode 100644 index 00000000..af2d835b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_restart.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_restart +short_description: restart a device +description: + - Restart a device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Restart a device + community.network.panos_restart: + ip_address: "192.168.1.1" + username: "admin" + password: "admin" +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "okey dokey" +''' + +import sys +import traceback + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +def main(): + argument_spec = dict( + ip_address=dict(), + password=dict(no_log=True), + username=dict(default='admin') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + try: + xapi.op(cmd="") + except Exception as e: + if 'succeeded' in to_native(e): + module.exit_json(changed=True, msg=to_native(e)) + else: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_sag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_sag.py new file mode 100644 index 00000000..d2a899a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_sag.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_sag +short_description: Create a static address group. +description: + - Create a static address group object in the firewall used for policy rules. +author: "Vinay Venkataraghavan (@vinayvenkat)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + sag_name: + description: + - name of the dynamic address group + required: true + sag_match_filter: + description: + - Static filter user by the address group + type: list + devicegroup: + description: > + - The name of the Panorama device group. The group must exist on Panorama. If device group is not defined + it is assumed that we are contacting a firewall. + description: + description: + - The purpose / objective of the static Address Group + tags: + description: + - Tags to be associated with the address group + commit: + description: + - commit if changed + type: bool + default: 'yes' + operation: + description: + - The operation to perform Supported values are I(add)/I(list)/I(delete). + required: true + choices: + - add + - list + - delete +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Sag + community.network.panos_sag: + ip_address: "192.168.1.1" + password: "admin" + sag_name: "sag-1" + static_value: ['test-addresses', ] + description: "A description for the static address group" + tags: ["tags to be associated with the group", ] +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def find_object(device, dev_group, obj_name, obj_type): + # Get the firewall objects + obj_type.refreshall(device) + if isinstance(device, firewall.Firewall): + addr = device.find(obj_name, obj_type) + return addr + elif isinstance(device, panorama.Panorama): + addr = device.find(obj_name, obj_type) + if addr is None: + if dev_group: + device.add(dev_group) + obj_type.refreshall(dev_group) + addr = dev_group.find(obj_name, obj_type) + return addr + else: + return False + + +def create_address_group_object(**kwargs): + """ + Create an Address object + + :param kwargs: key word arguments to instantiate AddressGroup object + @return False or ```objects.AddressObject``` + """ + ad_object = objects.AddressGroup( + name=kwargs['address_gp_name'], + static_value=kwargs['sag_match_filter'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if ad_object.static_value or ad_object.dynamic_value: + return ad_object + else: + return None + + +def add_address_group(device, dev_group, ag_object): + """ + Create a new dynamic address group object on the + PAN FW. + + :param device: Firewall Handle + :param dev_group: Panorama device group + :param ag_object: Address group object + """ + + if dev_group: + dev_group.add(ag_object) + else: + device.add(ag_object) + + exc = None + try: + ag_object.create() + except Exception as exc: + return False, exc + + return True, exc + + +def delete_address_group(device, dev_group, obj_name): + """ + + :param device: + :param dev_group: + :param obj_name: + :return: + """ + static_obj = find_object(device, dev_group, obj_name, objects.AddressGroup) + # If found, delete it + + if static_obj: + try: + static_obj.delete() + except Exception as exc: + return False, exc + return True, None + else: + return False, None + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + sag_match_filter=dict(type='list', required=False), + sag_name=dict(required=True), + commit=dict(type='bool', default=True), + devicegroup=dict(default=None), + description=dict(default=None), + tags=dict(type='list', default=[]), + operation=dict(type='str', required=True, choices=['add', 'list', 'delete']) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + + ag_object = create_address_group_object(address_gp_name=module.params.get('sag_name', None), + sag_match_filter=module.params.get('sag_match_filter', None), + description=module.params.get('description', None), + tag_name=module.params.get('tags', None) + ) + commit = module.params['commit'] + + devicegroup = module.params['devicegroup'] + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + if operation == 'add': + result, exc = add_address_group(device, dev_group, ag_object) + + if result and commit: + try: + device.commit(sync=True) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + elif operation == 'delete': + obj_name = module.params.get('sag_name', None) + result, exc = delete_address_group(device, dev_group, obj_name) + if not result and exc: + module.fail_json(msg=exc.message) + elif not result: + module.fail_json(msg="Specified object not found.") + else: + module.fail_json(changed=False, msg="Unsupported option.") + + module.exit_json(changed=True, msg="Address Group Operation Completed.") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_security_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_security_rule.py new file mode 100644 index 00000000..25167d70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_security_rule.py @@ -0,0 +1,572 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, techbizdev +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: panos_security_rule +short_description: Create security rule policy on PAN-OS devices or Panorama management console. +description: + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. + The policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the traffic is applied, + the more specific rules must precede the more general ones. +author: "Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + default: 'add' + choices: + - add + - update + - delete + - find + category: + description: + - The category. + type: list + default: ['any'] + rule_name: + description: + - Name of the security rule. + required: true + rule_type: + description: + - Type of security rule (version 6.1 of PanOS and above). + default: "universal" + description: + description: + - Description for the security rule. + tag_name: + description: + - Administrative tags that can be added to the rule. Note, tags must be already defined. + source_zone: + description: + - List of source zones. + default: "any" + destination_zone: + description: + - List of destination zones. + default: "any" + source_ip: + description: + - List of source addresses. + default: "any" + source_user: + description: + - Use users to enforce policy for individual users or a group of users. + default: "any" + hip_profiles: + description: > + - If you are using GlobalProtect with host information profile (HIP) enabled, you can also base the policy + on information collected by GlobalProtect. For example, the user access level can be determined HIP that + notifies the firewall about the user's local configuration. + default: "any" + destination_ip: + description: + - List of destination addresses. + default: "any" + application: + description: + - List of applications. + default: "any" + service: + description: + - List of services. + default: "application-default" + log_start: + description: + - Whether to log at session start. + type: bool + default: false + log_end: + description: + - Whether to log at session end. + default: true + type: bool + action: + description: + - Action to apply once rules maches. + default: "allow" + group_profile: + description: > + - Security profile group that is already defined in the system. This property supersedes antivirus, + vulnerability, spyware, url_filtering, file_blocking, data_filtering, and wildfire_analysis properties. + antivirus: + description: + - Name of the already defined antivirus profile. + vulnerability: + description: + - Name of the already defined vulnerability profile. + spyware: + description: + - Name of the already defined spyware profile. + url_filtering: + description: + - Name of the already defined url_filtering profile. + file_blocking: + description: + - Name of the already defined file_blocking profile. + data_filtering: + description: + - Name of the already defined data_filtering profile. + wildfire_analysis: + description: + - Name of the already defined wildfire_analysis profile. + devicegroup: + description: > + - Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama. + If device group is not define we assume that we are contacting Firewall. + commit: + description: + - Commit configuration if changed. + type: bool + default: 'yes' +''' + +EXAMPLES = ''' +- name: Add an SSH inbound rule to devicegroup + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + rule_name: 'SSH permit' + description: 'SSH rule test' + tag_name: ['ProjectX'] + source_zone: ['public'] + destination_zone: ['private'] + source_ip: ['any'] + source_user: ['any'] + destination_ip: ['1.1.1.1'] + category: ['any'] + application: ['ssh'] + service: ['application-default'] + hip_profiles: ['any'] + action: 'allow' + devicegroup: 'Cloud Edge' + +- name: Add a rule to allow HTTP multimedia only from CDNs + community.network.panos_security_rule: + ip_address: '10.5.172.91' + username: 'admin' + password: 'paloalto' + operation: 'add' + rule_name: 'HTTP Multimedia' + description: 'Allow HTTP multimedia only to host at 1.1.1.1' + source_zone: ['public'] + destination_zone: ['private'] + source_ip: ['any'] + source_user: ['any'] + destination_ip: ['1.1.1.1'] + category: ['content-delivery-networks'] + application: ['http-video', 'http-audio'] + service: ['service-http', 'service-https'] + hip_profiles: ['any'] + action: 'allow' + +- name: Add a more complex rule that uses security profiles + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + rule_name: 'Allow HTTP w profile' + log_start: false + log_end: true + action: 'allow' + antivirus: 'default' + vulnerability: 'default' + spyware: 'default' + url_filtering: 'default' + wildfire_analysis: 'default' + +- name: Delete a devicegroup security rule + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'delete' + rule_name: 'Allow telnet' + devicegroup: 'DC Firewalls' + +- name: Find a specific security rule + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + password: '{{ password }}' + operation: 'find' + rule_name: 'Allow RDP to DCs' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, pandevice.firewall.Firewall): + rulebase = pandevice.policies.Rulebase() + device.add(rulebase) + elif isinstance(device, pandevice.panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.SecurityRule.refreshall(rulebase) + return rulebase + + +def find_rule(rulebase, rule_name): + # Search for the rule name + rule = rulebase.find(rule_name) + if rule: + return rule + else: + return False + + +def rule_is_match(propose_rule, current_rule): + + match_check = ['name', 'description', 'group_profile', 'antivirus', 'vulnerability', + 'spyware', 'url_filtering', 'file_blocking', 'data_filtering', + 'wildfire_analysis', 'type', 'action', 'tag', 'log_start', 'log_end'] + list_check = ['tozone', 'fromzone', 'source', 'source_user', 'destination', 'category', + 'application', 'service', 'hip_profiles'] + + for check in match_check: + propose_check = getattr(propose_rule, check, None) + current_check = getattr(current_rule, check, None) + if propose_check != current_check: + return False + for check in list_check: + propose_check = getattr(propose_rule, check, []) + current_check = getattr(current_rule, check, []) + if set(propose_check) != set(current_check): + return False + return True + + +def create_security_rule(**kwargs): + security_rule = policies.SecurityRule( + name=kwargs['rule_name'], + description=kwargs['description'], + fromzone=kwargs['source_zone'], + source=kwargs['source_ip'], + source_user=kwargs['source_user'], + hip_profiles=kwargs['hip_profiles'], + tozone=kwargs['destination_zone'], + destination=kwargs['destination_ip'], + application=kwargs['application'], + service=kwargs['service'], + category=kwargs['category'], + log_start=kwargs['log_start'], + log_end=kwargs['log_end'], + action=kwargs['action'], + type=kwargs['rule_type'] + ) + + if 'tag_name' in kwargs: + security_rule.tag = kwargs['tag_name'] + + # profile settings + if 'group_profile' in kwargs: + security_rule.group = kwargs['group_profile'] + else: + if 'antivirus' in kwargs: + security_rule.virus = kwargs['antivirus'] + if 'vulnerability' in kwargs: + security_rule.vulnerability = kwargs['vulnerability'] + if 'spyware' in kwargs: + security_rule.spyware = kwargs['spyware'] + if 'url_filtering' in kwargs: + security_rule.url_filtering = kwargs['url_filtering'] + if 'file_blocking' in kwargs: + security_rule.file_blocking = kwargs['file_blocking'] + if 'data_filtering' in kwargs: + security_rule.data_filtering = kwargs['data_filtering'] + if 'wildfire_analysis' in kwargs: + security_rule.wildfire_analysis = kwargs['wildfire_analysis'] + return security_rule + + +def add_rule(rulebase, sec_rule): + if rulebase: + rulebase.add(sec_rule) + sec_rule.create() + return True + else: + return False + + +def update_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.apply() + return True + else: + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + operation=dict(default='add', choices=['add', 'update', 'delete', 'find']), + rule_name=dict(required=True), + description=dict(default=''), + tag_name=dict(type='list'), + destination_zone=dict(type='list', default=['any']), + source_zone=dict(type='list', default=['any']), + source_ip=dict(type='list', default=["any"]), + source_user=dict(type='list', default=['any']), + destination_ip=dict(type='list', default=["any"]), + category=dict(type='list', default=['any']), + application=dict(type='list', default=['any']), + service=dict(type='list', default=['application-default']), + hip_profiles=dict(type='list', default=['any']), + group_profile=dict(), + antivirus=dict(), + vulnerability=dict(), + spyware=dict(), + url_filtering=dict(), + file_blocking=dict(), + data_filtering=dict(), + wildfire_analysis=dict(), + log_start=dict(type='bool', default=False), + log_end=dict(type='bool', default=True), + rule_type=dict(default='universal'), + action=dict(default='allow'), + devicegroup=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + rule_name = module.params['rule_name'] + description = module.params['description'] + tag_name = module.params['tag_name'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_user = module.params['source_user'] + hip_profiles = module.params['hip_profiles'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + application = module.params['application'] + service = module.params['service'] + category = module.params['category'] + log_start = module.params['log_start'] + log_end = module.params['log_end'] + action = module.params['action'] + group_profile = module.params['group_profile'] + antivirus = module.params['antivirus'] + vulnerability = module.params['vulnerability'] + spyware = module.params['spyware'] + url_filtering = module.params['url_filtering'] + file_blocking = module.params['file_blocking'] + data_filtering = module.params['data_filtering'] + wildfire_analysis = module.params['wildfire_analysis'] + rule_type = module.params['rule_type'] + devicegroup = module.params['devicegroup'] + + commit = module.params['commit'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # Get the rulebase + rulebase = get_rulebase(device, dev_group) + + # Which action shall we take on the object? + if operation == "find": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Rule matched' + ) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "delete": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, delete it + if match: + try: + if commit: + match.delete() + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "add": + new_rule = create_security_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + category=category, + application=application, + service=service, + hip_profiles=hip_profiles, + group_profile=group_profile, + antivirus=antivirus, + vulnerability=vulnerability, + spyware=spyware, + url_filtering=url_filtering, + file_blocking=file_blocking, + data_filtering=data_filtering, + wildfire_analysis=wildfire_analysis, + log_start=log_start, + log_end=log_end, + rule_type=rule_type, + action=action + ) + # Search for the rule. Fail if found. + match = find_rule(rulebase, rule_name) + if match: + if rule_is_match(match, new_rule): + module.exit_json(changed=False, msg='Rule \'%s\' is already in place' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name) + else: + try: + changed = add_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully added' % rule_name) + elif operation == 'update': + # Search for the rule. Update if found. + match = find_rule(rulebase, rule_name) + if match: + try: + new_rule = create_security_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + category=category, + application=application, + service=service, + hip_profiles=hip_profiles, + group_profile=group_profile, + antivirus=antivirus, + vulnerability=vulnerability, + spyware=spyware, + url_filtering=url_filtering, + file_blocking=file_blocking, + data_filtering=data_filtering, + wildfire_analysis=wildfire_analysis, + log_start=log_start, + log_end=log_end, + rule_type=rule_type, + action=action + ) + changed = update_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_set.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_set.py new file mode 100644 index 00000000..3491d64f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/panos/panos_set.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2018, Jasper Mackenzie +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_set +short_description: Execute arbitrary commands on a PAN-OS device using XPath and element +description: + - Run an arbitrary 'xapi' command taking an XPath (i.e get) or XPath and element (i.e set). + - See https://github.com/kevinsteves/pan-python/blob/master/doc/pan.xapi.rst for details + - Runs a 'set' command by default + - This should support _all_ commands that your PAN-OS device accepts vi it's cli + - cli commands are found as + - Once logged in issue 'debug cli on' + - Enter configuration mode by issuing 'configure' + - Enter your set (or other) command, for example 'set deviceconfig system timezone Australia/Melbourne' + - returns + - > + "Australia/Melbourne + - The 'xpath' is "/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system" + - The 'element' is "Australia/Melbourne" +author: "Jasper Mackenzie (@spmp)" +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +requirements: + - pan-python +options: + ip_address: + description: + - IP address or host FQDN of the target PAN-OS NVA + required: true + username: + description: + - User name for a user with admin rights on the PAN-OS NVA + default: admin + password: + description: + - Password for the given 'username' + required: true + command: + description: + - Xapi method name which supports 'xpath' or 'xpath' and 'element' + choices: + - set + - edit + - delete + - get + - show + - override + default: set + xpath: + description: + - The 'xpath' for the commands configurable + required: true + element: + description: + - The 'element' for the 'xpath' if required +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' + +- name: Set timezone on PA NVA + community.network.panos_set: + ip_address: "192.168.1.1" + username: "my-random-admin" + password: "admin1234" + xpath: "/config/devices/entry/deviceconfig/system" + element: "Australia/Melbourne" + +- name: Commit configuration + panos_commit: + ip_address: "192.168.1.1" + username: "my-random-admin" + password: "admin1234" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + command=dict(default='set', choices=['set', 'edit', 'delete', 'get', 'show', 'override']), + xpath=dict(required=True), + element=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + xpath = module.params['xpath'] + element = module.params['element'] + xcommand = module.params['command'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password, + timeout=60 + ) + + if element is None: + # Issue command with no `element` + try: + getattr(xapi, xcommand)(xpath=xpath) + except Exception as e: + raise Exception("Failed to run '%s' with xpath: '%s' with the following error: %s" % + (xcommand, xpath, e)) + else: + # Issue command with `element` + try: + getattr(xapi, xcommand)(xpath=xpath, element=element) + except Exception as e: + raise Exception("Failed to run '%s' with xpath: '%s' and element '%s' with the following error: %s" % + (xcommand, xpath, element, e)) + + module.exit_json( + status="success" + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_commit.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_commit.py new file mode 100644 index 00000000..19c25b60 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_commit.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_commit +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Commits pending configuration changes on Radware devices +description: + - Commits pending configuration changes on one or more Radware devices via vDirect server. + - For Alteon ADC device, apply, sync and save actions will be performed by default. + Skipping of an action is possible by explicit parameter specifying. + - For Alteon VX Container device, no sync operation will be performed + since sync action is only relevant for Alteon ADC devices. + - For DefensePro and AppWall devices, a bulk commit action will be performed. + Explicit apply, sync and save actions specifying is not relevant. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as C(VDIRECT_IP) environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as C(VDIRECT_USER) environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as C(VDIRECT_PASSWORD) environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as C(VDIRECT_SECONDARY_IP) environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as C(VDIRECT_WAIT) environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as C(VDIRECT_HTTPS_PORT) environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as C(VDIRECT_HTTP_PORT) environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as C(VDIRECT_TIMEOUT) environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as C(VDIRECT_HTTPS) or C(VDIRECT_USE_SSL) environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as C(VDIRECT_VALIDATE_CERTS) or C(VDIRECT_VERIFY) environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + devices: + description: + - List of Radware Alteon device names for commit operations. + required: true + apply: + description: + - If C(no), apply action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + save: + description: + - If C(no), save action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + sync: + description: + - If C(no), sync action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Commit + community.network.vdirect_commit: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + devices: ['dev1', 'dev2'] + sync: no +''' + +RETURN = ''' +result: + description: Message detailing actions result + returned: success + type: str + sample: "Requested actions were successfully performed on all devices." +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + + +SUCCESS = 'Requested actions were successfully performed on all devices.' +FAILURE = 'Failure occurred while performing requested actions on devices. See details' + +ADC_DEVICE_TYPE = 'Adc' +CONTAINER_DEVICE_TYPE = 'Container' +PARTITIONED_CONTAINER_DEVICE_TYPE = 'AlteonPartitioned' +APPWALL_DEVICE_TYPE = 'AppWall' +DP_DEVICE_TYPE = 'DefensePro' + +SUCCEEDED = 'succeeded' +FAILED = 'failed' +NOT_PERFORMED = 'not performed' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + devices=dict( + required=True, type='list'), + apply=dict( + required=False, default=True, type='bool'), + save=dict( + required=False, default=True, type='bool'), + sync=dict( + required=False, default=True, type='bool'), +) + + +class CommitException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class MissingDeviceException(CommitException): + def __init__(self, device_name): + super(MissingDeviceException, self).__init__( + 'Device missing', + 'Device ' + repr(device_name) + ' does not exist') + + +class VdirectCommit(object): + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + self.devices = params['devices'] + self.apply = params['apply'] + self.save = params['save'] + self.sync = params['sync'] + self.devicesMap = {} + + def _validate_devices(self): + for device in self.devices: + try: + res = self.client.adc.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: ADC_DEVICE_TYPE}) + continue + res = self.client.container.get(device) + if res[rest_client.RESP_STATUS] == 200: + if res[rest_client.RESP_DATA]['type'] == PARTITIONED_CONTAINER_DEVICE_TYPE: + self.devicesMap.update({device: CONTAINER_DEVICE_TYPE}) + continue + res = self.client.appWall.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: APPWALL_DEVICE_TYPE}) + continue + res = self.client.defensePro.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: DP_DEVICE_TYPE}) + continue + + except Exception as e: + raise CommitException('Failed to communicate with device ' + device, str(e)) + + raise MissingDeviceException(device) + + def _perform_action_and_update_result(self, device, action, perform, failure_occurred, actions_result): + + if not perform or failure_occurred: + actions_result[action] = NOT_PERFORMED + return True + + try: + if self.devicesMap[device] == ADC_DEVICE_TYPE: + res = self.client.adc.control_device(device, action) + elif self.devicesMap[device] == CONTAINER_DEVICE_TYPE: + res = self.client.container.control(device, action) + elif self.devicesMap[device] == APPWALL_DEVICE_TYPE: + res = self.client.appWall.control_device(device, action) + elif self.devicesMap[device] == DP_DEVICE_TYPE: + res = self.client.defensePro.control_device(device, action) + + if res[rest_client.RESP_STATUS] in [200, 204]: + actions_result[action] = SUCCEEDED + else: + actions_result[action] = FAILED + actions_result['failure_description'] = res[rest_client.RESP_STR] + return False + except Exception as e: + actions_result[action] = FAILED + actions_result['failure_description'] = 'Exception occurred while performing '\ + + action + ' action. Exception: ' + str(e) + return False + + return True + + def commit(self): + self._validate_devices() + + result_to_return = dict() + result_to_return['details'] = list() + + for device in self.devices: + failure_occurred = False + device_type = self.devicesMap[device] + actions_result = dict() + actions_result['device_name'] = device + actions_result['device_type'] = device_type + + if device_type in [DP_DEVICE_TYPE, APPWALL_DEVICE_TYPE]: + failure_occurred = not self._perform_action_and_update_result( + device, 'commit', True, failure_occurred, actions_result)\ + or failure_occurred + else: + failure_occurred = not self._perform_action_and_update_result( + device, 'apply', self.apply, failure_occurred, actions_result)\ + or failure_occurred + if device_type != CONTAINER_DEVICE_TYPE: + failure_occurred = not self._perform_action_and_update_result( + device, 'sync', self.sync, failure_occurred, actions_result)\ + or failure_occurred + failure_occurred = not self._perform_action_and_update_result( + device, 'save', self.save, failure_occurred, actions_result)\ + or failure_occurred + + result_to_return['details'].extend([actions_result]) + + if failure_occurred: + result_to_return['msg'] = FAILURE + + if 'msg' not in result_to_return: + result_to_return['msg'] = SUCCESS + + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_commit = VdirectCommit(module.params) + result = vdirect_commit.commit() + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_file.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_file.py new file mode 100644 index 00000000..9ce7c008 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_file.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_file +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Uploads a new or updates an existing runnable file into Radware vDirect server +description: + - Uploads a new or updates an existing configuration template or workflow template into the Radware vDirect server. + All parameters may be set as environment variables. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as VDIRECT_IP environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as VDIRECT_USER environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as VDIRECT_PASSWORD environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as VDIRECT_SECONDARY_IP environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as VDIRECT_WAIT environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as VDIRECT_HTTPS_PORT environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as VDIRECT_HTTP_PORT environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as VDIRECT_TIMEOUT environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as VDIRECT_HTTPS or VDIRECT_USE_SSL environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as VDIRECT_VALIDATE_CERTS or VDIRECT_VERIFY environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + file_name: + description: + - vDirect runnable file name to be uploaded. + - May be velocity configuration template (.vm) or workflow template zip file (.zip). + required: true + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Upload a new or updates an existing runnable file + community.network.vdirect_file: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + file_name: /tmp/get_vlans.vm +''' + +RETURN = ''' +result: + description: Message detailing upload result + returned: success + type: str + sample: "Workflow template created" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback +import os +import os.path + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + +TEMPLATE_EXTENSION = '.vm' +WORKFLOW_EXTENSION = '.zip' +WRONG_EXTENSION_ERROR = 'The file_name parameter must have ' \ + 'velocity script (.vm) extension or ZIP archive (.zip) extension' +CONFIGURATION_TEMPLATE_CREATED_SUCCESS = 'Configuration template created' +CONFIGURATION_TEMPLATE_UPDATED_SUCCESS = 'Configuration template updated' +WORKFLOW_TEMPLATE_CREATED_SUCCESS = 'Workflow template created' +WORKFLOW_TEMPLATE_UPDATED_SUCCESS = 'Workflow template updated' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + file_name=dict(required=True) +) + + +class FileException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class InvalidSourceException(FileException): + def __init__(self, message): + super(InvalidSourceException, self).__init__( + 'Error parsing file', repr(message)) + + +class VdirectFile(object): + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + + def upload(self, fqn): + if fqn.endswith(TEMPLATE_EXTENSION): + template_name = os.path.basename(fqn) + template = rest_client.Template(self.client) + runnable_file = open(fqn, 'r') + file_content = runnable_file.read() + + result_to_return = CONFIGURATION_TEMPLATE_CREATED_SUCCESS + result = template.create_from_source(file_content, template_name, fail_if_invalid=True) + if result[rest_client.RESP_STATUS] == 409: + result_to_return = CONFIGURATION_TEMPLATE_UPDATED_SUCCESS + result = template.upload_source(file_content, template_name, fail_if_invalid=True) + + if result[rest_client.RESP_STATUS] == 400: + raise InvalidSourceException(str(result[rest_client.RESP_STR])) + elif fqn.endswith(WORKFLOW_EXTENSION): + workflow = rest_client.WorkflowTemplate(self.client) + + runnable_file = open(fqn, 'rb') + file_content = runnable_file.read() + + result_to_return = WORKFLOW_TEMPLATE_CREATED_SUCCESS + result = workflow.create_template_from_archive(file_content, fail_if_invalid=True) + if result[rest_client.RESP_STATUS] == 409: + result_to_return = WORKFLOW_TEMPLATE_UPDATED_SUCCESS + result = workflow.update_archive(file_content, os.path.splitext(os.path.basename(fqn))[0]) + + if result[rest_client.RESP_STATUS] == 400: + raise InvalidSourceException(str(result[rest_client.RESP_STR])) + else: + result_to_return = WRONG_EXTENSION_ERROR + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_file = VdirectFile(module.params) + result = vdirect_file.upload(module.params['file_name']) + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_runnable.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_runnable.py new file mode 100644 index 00000000..0274fed5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/radware/vdirect_runnable.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_runnable +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Runs templates and workflow actions in Radware vDirect server +description: + - Runs configuration templates, creates workflows and runs workflow actions in Radware vDirect server. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as C(VDIRECT_IP) environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as C(VDIRECT_USER) environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as C(VDIRECT_PASSWORD) environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as C(VDIRECT_SECONDARY_IP) environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as C(VDIRECT_WAIT) environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as C(VDIRECT_HTTPS_PORT) environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as C(VDIRECT_HTTP_PORT) environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as C(VDIRECT_TIMEOUT) environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as C(VDIRECT_HTTPS) or C(VDIRECT_USE_SSL) environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as C(VDIRECT_VALIDATE_CERTS) or C(VDIRECT_VERIFY) environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + runnable_type: + description: + - vDirect runnable type. + required: true + choices: ['ConfigurationTemplate', 'Workflow', 'WorkflowTemplate', 'Plugin'] + runnable_name: + description: + - vDirect runnable name to run. + - May be configuration template name, workflow template name or workflow instance name. + required: true + action_name: + description: + - Workflow action name to run. + - Required if I(runnable_type=Workflow). + parameters: + description: + - Action parameters dictionary. In case of C(ConfigurationTemplate) runnable type, + - the device connection details should always be passed as a parameter. + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Run the module from Ansible playbook + community.network.vdirect_runnable: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + runnable_type: ConfigurationTemplate + runnable_name: get_vlans + parameters: {'vlans_needed':1,'adc':[{'type':'Adc','name':'adc-1'}]} +''' + +RETURN = ''' +result: + description: Message detailing run result + returned: success + type: str + sample: "Workflow action run completed." +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + +CONFIGURATION_TEMPLATE_RUNNABLE_TYPE = 'ConfigurationTemplate' +WORKFLOW_TEMPLATE_RUNNABLE_TYPE = 'WorkflowTemplate' +WORKFLOW_RUNNABLE_TYPE = 'Workflow' +PLUGIN_RUNNABLE_TYPE = 'Plugin' + +TEMPLATE_SUCCESS = 'Configuration template run completed.' +WORKFLOW_CREATION_SUCCESS = 'Workflow created.' +WORKFLOW_ACTION_SUCCESS = 'Workflow action run completed.' +PLUGIN_ACTION_SUCCESS = 'Plugin action run completed.' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + runnable_type=dict( + required=True, + choices=[CONFIGURATION_TEMPLATE_RUNNABLE_TYPE, WORKFLOW_TEMPLATE_RUNNABLE_TYPE, WORKFLOW_RUNNABLE_TYPE, PLUGIN_RUNNABLE_TYPE]), + runnable_name=dict(required=True), + action_name=dict(required=False, default=None), + parameters=dict(required=False, type='dict', default={}) +) + + +class RunnableException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class WrongActionNameException(RunnableException): + def __init__(self, action, available_actions): + super(WrongActionNameException, self).__init__('Wrong action name ' + repr(action), + 'Available actions are: ' + repr(available_actions)) + + +class MissingActionParametersException(RunnableException): + def __init__(self, required_parameters): + super(MissingActionParametersException, self).__init__( + 'Action parameters missing', + 'Required parameters are: ' + repr(required_parameters)) + + +class MissingRunnableException(RunnableException): + def __init__(self, name): + super(MissingRunnableException, self).__init__( + 'Runnable missing', + 'Runnable ' + name + ' is missing') + + +class VdirectRunnable(object): + + CREATE_WORKFLOW_ACTION = 'createWorkflow' + RUN_ACTION = 'run' + + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + strict_http_results=True, + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + self.params = params + self.type = self.params['runnable_type'] + self.name = self.params['runnable_name'] + + if self.type == WORKFLOW_TEMPLATE_RUNNABLE_TYPE: + self.action_name = VdirectRunnable.CREATE_WORKFLOW_ACTION + elif self.type == CONFIGURATION_TEMPLATE_RUNNABLE_TYPE: + self.action_name = VdirectRunnable.RUN_ACTION + else: + self.action_name = self.params['action_name'] + + if 'parameters' in self.params and self.params['parameters']: + self.action_params = self.params['parameters'] + else: + self.action_params = {} + + def _validate_runnable_exists(self): + if self.type == WORKFLOW_RUNNABLE_TYPE: + res = self.client.runnable.get_runnable_objects(self.type) + runnable_names = res[rest_client.RESP_DATA]['names'] + if self.name not in runnable_names: + raise MissingRunnableException(self.name) + else: + try: + self.client.catalog.get_catalog_item(self.type, self.name) + except rest_client.RestClientException: + raise MissingRunnableException(self.name) + + def _validate_action_name(self): + if self.type in [WORKFLOW_RUNNABLE_TYPE, PLUGIN_RUNNABLE_TYPE]: + res = self.client.runnable.get_available_actions(self.type, self.name) + available_actions = res[rest_client.RESP_DATA]['names'] + if self.action_name not in available_actions: + raise WrongActionNameException(self.action_name, available_actions) + + def _validate_required_action_params(self): + action_params_names = [n for n in self.action_params] + + res = self.client.runnable.get_action_info(self.type, self.name, self.action_name) + if 'parameters' in res[rest_client.RESP_DATA]: + action_params_spec = res[rest_client.RESP_DATA]['parameters'] + else: + action_params_spec = [] + + required_action_params_dict = [{'name': p['name'], 'type': p['type']} for p in action_params_spec + if p['type'] == 'alteon' or + p['type'] == 'defensePro' or + p['type'] == 'appWall' or + p['type'] == 'alteon[]' or + p['type'] == 'defensePro[]' or + p['type'] == 'appWall[]' or + p['direction'] != 'out'] + required_action_params_names = [n['name'] for n in required_action_params_dict] + + if set(required_action_params_names) & set(action_params_names) != set(required_action_params_names): + raise MissingActionParametersException(required_action_params_dict) + + def run(self): + self._validate_runnable_exists() + self._validate_action_name() + self._validate_required_action_params() + + data = self.action_params + + result = self.client.runnable.run(data, self.type, self.name, self.action_name) + result_to_return = {'msg': ''} + if result[rest_client.RESP_STATUS] == 200: + if result[rest_client.RESP_DATA]['success']: + if self.type == WORKFLOW_TEMPLATE_RUNNABLE_TYPE: + result_to_return['msg'] = WORKFLOW_CREATION_SUCCESS + elif self.type == CONFIGURATION_TEMPLATE_RUNNABLE_TYPE: + result_to_return['msg'] = TEMPLATE_SUCCESS + elif self.type == PLUGIN_RUNNABLE_TYPE: + result_to_return['msg'] = PLUGIN_ACTION_SUCCESS + else: + result_to_return['msg'] = WORKFLOW_ACTION_SUCCESS + result_to_return['output'] = result[rest_client.RESP_DATA] + + else: + if 'exception' in result[rest_client.RESP_DATA]: + raise RunnableException(result[rest_client.RESP_DATA]['exception']['message'], + result[rest_client.RESP_STR]) + else: + raise RunnableException('The status returned ' + str(result[rest_client.RESP_DATA]['status']), + result[rest_client.RESP_STR]) + else: + raise RunnableException(result[rest_client.RESP_REASON], + result[rest_client.RESP_STR]) + + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args, + required_if=[['runnable_type', WORKFLOW_RUNNABLE_TYPE, ['action_name']], + ['runnable_type', PLUGIN_RUNNABLE_TYPE, ['action_name']]]) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_runnable = VdirectRunnable(module.params) + result = vdirect_runnable.run() + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_api.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_api.py new file mode 100644 index 00000000..9969e6d6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_api.py @@ -0,0 +1,482 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Nikolay Dachev +# GNU General Public License v3.0+ https://www.gnu.org/licenses/gpl-3.0.txt + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_api +version_added: 1.1.0 +author: "Nikolay Dachev (@NikolayDachev)" +short_description: Ansible module for RouterOS API +description: + - Ansible module for RouterOS API with python librouteros. + - This module can add, remove, update, query and execute arbitrary command in routeros via API. +notes: + - I(add), I(remove), I(update), I(cmd) and I(query) are mutually exclusive. + - I(check_mode) is not supported. +requirements: + - librouteros + - Python >= 3.6 (for librouteros) +options: + hostname: + description: + - RouterOS hostname API. + required: true + type: str + username: + description: + - RouterOS login user. + required: true + type: str + password: + description: + - RouterOS user password. + required: true + type: str + ssl: + description: + - If is set TLS will be used for RouterOS API connection. + required: false + type: bool + default: false + port: + description: + - RouterOS api port. If ssl is set, port will apply to ssl connection. + - Defaults are C(8728) for the HTTP API, and C(8729) for the HTTPS API. + type: int + path: + description: + - Main path for all other arguments. + - If other arguments are not set, api will return all items in selected path. + - Example C(ip address). Equivalent of RouterOS CLI C(/ip address print). + required: true + type: str + add: + description: + - Will add selected arguments in selected path to RouterOS config. + - Example C(address=1.1.1.1/32 interface=ether1). + - Equivalent in RouterOS CLI C(/ip address add address=1.1.1.1/32 interface=ether1). + type: str + remove: + description: + - Remove config/value from RouterOS by '.id'. + - Example C(*03) will remove config/value with C(id=*03) in selected path. + - Equivalent in RouterOS CLI C(/ip address remove numbers=1). + - Note C(number) in RouterOS CLI is different from C(.id). + type: str + update: + description: + - Update config/value in RouterOS by '.id' in selected path. + - Example C(.id=*03 address=1.1.1.3/32) and path C(ip address) will replace existing ip address with C(.id=*03). + - Equivalent in RouterOS CLI C(/ip address set address=1.1.1.3/32 numbers=1). + - Note C(number) in RouterOS CLI is different from C(.id). + type: str + query: + description: + - Query given path for selected query attributes from RouterOS aip and return '.id'. + - WHERE is key word which extend query. WHERE format is key operator value - with spaces. + - WHERE valid operators are C(==), C(!=), C(>), C(<). + - Example path C(ip address) and query C(.id address) will return only C(.id) and C(address) for all items in C(ip address) path. + - Example path C(ip address) and query C(.id address WHERE address == 1.1.1.3/32). + will return only C(.id) and C(address) for items in C(ip address) path, where address is eq to 1.1.1.3/32. + - Example path C(interface) and query C(mtu name WHERE mut > 1400) will + return only interfaces C(mtu,name) where mtu is bigger than 1400. + - Equivalent in RouterOS CLI C(/interface print where mtu > 1400). + type: str + cmd: + description: + - Execute any/arbitrary command in selected path, after the command we can add C(.id). + - Example path C(system script) and cmd C(run .id=*03) is equivalent in RouterOS CLI C(/system script run number=0). + - Example path C(ip address) and cmd C(print) is equivalent in RouterOS CLI C(/ip address print). + type: str +''' + +EXAMPLES = ''' +--- +- name: Test routeros_api + hosts: localhost + gather_facts: no + vars: + hostname: "ros_api_hostname/ip" + username: "admin" + password: "secret_password" + + path: "ip address" + + nic: "ether2" + ip1: "1.1.1.1/32" + ip2: "2.2.2.2/32" + ip3: "3.3.3.3/32" + + tasks: + - name: Get "{{ path }} print" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + register: print_path + + - name: Dump "{{ path }} print" output + ansible.builtin.debug: + msg: '{{ print_path }}' + + - name: Add ip address "{{ ip1 }}" and "{{ ip2 }}" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + add: "{{ item }}" + loop: + - "address={{ ip1 }} interface={{ nic }}" + - "address={{ ip2 }} interface={{ nic }}" + register: addout + + - name: Dump "Add ip address" output - ".id" for new added items + ansible.builtin.debug: + msg: '{{ addout }}' + + - name: Query for ".id" in "{{ path }} WHERE address == {{ ip2 }}" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + query: ".id address WHERE address == {{ ip2 }}" + register: queryout + + - name: Dump "Query for" output and set fact with ".id" for "{{ ip2 }}" + ansible.builtin.debug: + msg: '{{ queryout }}' + + - ansible.builtin.set_fact: + query_id : "{{ queryout['msg'][0]['.id'] }}" + + - name: Update ".id = {{ query_id }}" taken with custom fact "fquery_id" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + update: ".id={{ query_id }} address={{ ip3 }}" + register: updateout + + - name: Dump "Update" output + ansible.builtin.debug: + msg: '{{ updateout }}' + + - name: Remove ips - stage 1 - query ".id" for "{{ ip2 }}" and "{{ ip3 }}" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + query: ".id address WHERE address == {{ item }}" + register: id_to_remove + loop: + - "{{ ip2 }}" + - "{{ ip3 }}" + + - name: set fact for ".id" from "Remove ips - stage 1 - query" + ansible.builtin.set_fact: + to_be_remove: "{{ to_be_remove |default([]) + [item['msg'][0]['.id']] }}" + loop: "{{ id_to_remove.results }}" + + - name: Dump "Remove ips - stage 1 - query" output + ansible.builtin.debug: + msg: '{{ to_be_remove }}' + + # Remove "{{ rmips }}" with ".id" by "to_be_remove" from query + - name: Remove ips - stage 2 - remove "{{ ip2 }}" and "{{ ip3 }}" by '.id' + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + remove: "{{ item }}" + register: remove + loop: "{{ to_be_remove }}" + + - name: Dump "Remove ips - stage 2 - remove" output + ansible.builtin.debug: + msg: '{{ remove }}' + + - name: Arbitrary command example "/system identity print" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "system identity" + cmd: "print" + register: cmdout + + - name: Dump "Arbitrary command example" output + ansible.builtin.debug: + msg: "{{ cmdout }}" +''' + +RETURN = ''' +--- +message: + description: All outputs are in list with dictionary elements returned from RouterOS api. + sample: C([{...},{...}]) + type: list + returned: always +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils._text import to_native + +import ssl +import traceback + +LIB_IMP_ERR = None +try: + from librouteros import connect + from librouteros.query import Key + HAS_LIB = True +except Exception as e: + HAS_LIB = False + LIB_IMP_ERR = traceback.format_exc() + + +class ROS_api_module: + def __init__(self): + module_args = (dict( + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + hostname=dict(type='str', required=True), + port=dict(type='int'), + ssl=dict(type='bool', default=False), + path=dict(type='str', required=True), + add=dict(type='str'), + remove=dict(type='str'), + update=dict(type='str'), + cmd=dict(type='str'), + query=dict(type='str'))) + + self.module = AnsibleModule(argument_spec=module_args, + supports_check_mode=False, + mutually_exclusive=(('add', 'remove', 'update', + 'cmd', 'query'),),) + + if not HAS_LIB: + self.module.fail_json(msg=missing_required_lib("librouteros"), + exception=LIB_IMP_ERR) + + self.api = self.ros_api_connect(self.module.params['username'], + self.module.params['password'], + self.module.params['hostname'], + self.module.params['port'], + self.module.params['ssl']) + + self.path = self.list_remove_empty(self.module.params['path'].split(' ')) + self.add = self.module.params['add'] + self.remove = self.module.params['remove'] + self.update = self.module.params['update'] + self.arbitrary = self.module.params['cmd'] + + self.where = None + self.query = self.module.params['query'] + if self.query: + if 'WHERE' in self.query: + split = self.query.split('WHERE') + self.query = self.list_remove_empty(split[0].split(' ')) + self.where = self.list_remove_empty(split[1].split(' ')) + else: + self.query = self.list_remove_empty(self.module.params['query'].split(' ')) + + self.result = dict( + message=[]) + + # create api base path + self.api_path = self.api_add_path(self.api, self.path) + + # api call's + if self.add: + self.api_add() + elif self.remove: + self.api_remove() + elif self.update: + self.api_update() + elif self.query: + self.api_query() + elif self.arbitrary: + self.api_arbitrary() + else: + self.api_get_all() + + def list_remove_empty(self, check_list): + while("" in check_list): + check_list.remove("") + return check_list + + def list_to_dic(self, ldict): + dict = {} + for p in ldict: + if '=' not in p: + self.errors("missing '=' after '%s'" % p) + p = p.split('=') + if p[0] == 'id': + self.errors("'%s' must be '.id'" % p[0]) + if p[1]: + dict[p[0]] = p[1] + return dict + + def api_add_path(self, api, path): + api_path = api.path() + for p in path: + api_path = api_path.join(p) + return api_path + + def api_get_all(self): + try: + for i in self.api_path: + self.result['message'].append(i) + self.return_result(False, True) + except Exception as e: + self.errors(e) + + def api_add(self): + param = self.list_to_dic(self.add.split(' ')) + try: + self.result['message'].append("added: .id= %s" + % self.api_path.add(**param)) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_remove(self): + try: + self.api_path.remove(self.remove) + self.result['message'].append("removed: .id= %s" % self.remove) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_update(self): + param = self.list_to_dic(self.update.split(' ')) + if '.id' not in param.keys(): + self.errors("missing '.id' for %s" % param) + try: + self.api_path.update(**param) + self.result['message'].append("updated: %s" % param) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_query(self): + keys = {} + for k in self.query: + if 'id' in k and k != ".id": + self.errors("'%s' must be '.id'" % k) + keys[k] = Key(k) + try: + if self.where: + if len(self.where) < 3: + self.errors("invalid syntax for 'WHERE %s'" + % ' '.join(self.where)) + + where = [] + if self.where[1] == '==': + select = self.api_path.select(*keys).where(keys[self.where[0]] == self.where[2]) + elif self.where[1] == '!=': + select = self.api_path.select(*keys).where(keys[self.where[0]] != self.where[2]) + elif self.where[1] == '>': + select = self.api_path.select(*keys).where(keys[self.where[0]] > self.where[2]) + elif self.where[1] == '<': + select = self.api_path.select(*keys).where(keys[self.where[0]] < self.where[2]) + else: + self.errors("'%s' is not operator for 'where'" + % self.where[1]) + for row in select: + self.result['message'].append(row) + else: + for row in self.api_path.select(*keys): + self.result['message'].append(row) + if len(self.result['message']) < 1: + msg = "no results for '%s 'query' %s" % (' '.join(self.path), + ' '.join(self.query)) + if self.where: + msg = msg + ' WHERE %s' % ' '.join(self.where) + self.result['message'].append(msg) + self.return_result(False) + except Exception as e: + self.errors(e) + + def api_arbitrary(self): + param = {} + self.arbitrary = self.arbitrary.split(' ') + arb_cmd = self.arbitrary[0] + if len(self.arbitrary) > 1: + param = self.list_to_dic(self.arbitrary[1:]) + try: + arbitrary_result = self.api_path(arb_cmd, **param) + for i in arbitrary_result: + self.result['message'].append(i) + self.return_result(False) + except Exception as e: + self.errors(e) + + def return_result(self, ch_status=False, status=True): + if status == "False": + self.module.fail_json(msg=to_native(self.result['message'])) + else: + self.module.exit_json(changed=ch_status, + msg=self.result['message']) + + def errors(self, e): + if e.__class__.__name__ == 'TrapError': + self.result['message'].append("%s" % e) + self.return_result(False, True) + self.result['message'].append("%s" % e) + self.return_result(False, False) + + def ros_api_connect(self, username, password, host, port, use_ssl): + # connect to routeros api + conn_status = {"connection": {"username": username, + "hostname": host, + "port": port, + "ssl": use_ssl, + "status": "Connected"}} + try: + if use_ssl is True: + if not port: + port = 8729 + conn_status["connection"]["port"] = port + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.set_ciphers('ADH:@SECLEVEL=0') + api = connect(username=username, + password=password, + host=host, + ssl_wrapper=ctx.wrap_socket, + port=port) + else: + if not port: + port = 8728 + conn_status["connection"]["port"] = port + api = connect(username=username, + password=password, + host=host, + port=port) + except Exception as e: + conn_status["connection"]["status"] = "error: %s" % e + self.module.fail_json(msg=to_native([conn_status])) + return api + + +def main(): + + ROS_api_module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_command.py new file mode 100644 index 00000000..403df21a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_command.py @@ -0,0 +1,181 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_command +author: "Egor Zaitsev (@heuels)" +short_description: Run commands on remote devices running MikroTik RouterOS +description: + - Sends arbitrary commands to an RouterOS node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +options: + commands: + description: + - List of commands to send to the remote RouterOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run command on remote devices + community.network.routeros_command: + commands: /system routerboard print + + - name: Run command and check to see if output contains routeros + community.network.routeros_command: + commands: /system resource print + wait_for: result[0] contains MikroTik + + - name: Run multiple commands on remote nodes + community.network.routeros_command: + commands: + - /system routerboard print + - /system identity print + + - name: Run multiple commands and evaluate the output + community.network.routeros_command: + commands: + - /system routerboard print + - /interface ethernet print + wait_for: + - result[0] contains x86 + - result[1] contains ether1 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import run_commands +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import routeros_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(routeros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, module.params['commands']) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_facts.py new file mode 100644 index 00000000..05d89152 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/routeros/routeros_facts.py @@ -0,0 +1,629 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_facts +author: "Egor Zaitsev (@heuels)" +short_description: Collect facts from remote devices running MikroTik RouterOS +description: + - Collects a base set of device facts from a remote device that + is running RotuerOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + C(all), C(hardware), C(config), and C(interfaces). Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.routeros_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.routeros_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.routeros_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_arch: + description: The CPU architecture of the device + returned: always + type: str + version_added: 1.2.0 +ansible_net_uptime: + description: The uptime of the device + returned: always + type: str + version_added: 1.2.0 +ansible_net_cpu_load: + description: Current CPU load + returned: always + type: str + version_added: 1.2.0 + +# hardware +ansible_net_spacefree_mb: + description: The available disk space on the remote device in MiB + returned: when hardware is configured + type: dict +ansible_net_spacetotal_mb: + description: The total disk space on the remote device in MiB + returned: when hardware is configured + type: dict +ansible_net_memfree_mb: + description: The available free memory on the remote device in MiB + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in MiB + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of neighbors from the remote device + returned: when interfaces is configured + type: dict + +# routing +ansible_net_bgp_peer: + description: The dict bgp peer + returned: peer information + type: dict + version_added: 1.2.0 +ansible_net_bgp_vpnv4_route: + description: The dict bgp vpnv4 route + returned: vpnv4 route information + type: dict + version_added: 1.2.0 +ansible_net_bgp_instance: + description: The dict bgp instance + returned: bgp instance information + type: dict + version_added: 1.2.0 +ansible_net_route: + description: The dict routes in all routing table + returned: routes information in all routing table + type: dict + version_added: 1.2.0 +ansible_net_ospf_instance: + description: The dict ospf instance + returned: ospf instance information + type: dict + version_added: 1.2.0 +ansible_net_ospf_neighbor: + description: The dict ospf neighbor + returned: ospf neighbor information + type: dict + version_added: 1.2.0 +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import run_commands +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import routeros_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = [ + '/system identity print without-paging', + '/system resource print without-paging', + '/system routerboard print without-paging' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['hostname'] = self.parse_hostname(data) + data = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['arch'] = self.parse_arch(data) + self.facts['uptime'] = self.parse_uptime(data) + self.facts['cpu_load'] = self.parse_cpu_load(data) + data = self.responses[2] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + def parse_hostname(self, data): + match = re.search(r'name:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_version(self, data): + match = re.search(r'version:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'model:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_arch(self, data): + match = re.search(r'architecture-name:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_uptime(self, data): + match = re.search(r'uptime:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_cpu_load(self, data): + match = re.search(r'cpu-load:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'serial-number:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + '/system resource print without-paging' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.parse_filesystem_info(data) + self.parse_memory_info(data) + + def parse_filesystem_info(self, data): + match = re.search(r'free-hdd-space:\s(.*)([KMG]iB)', data, re.M) + if match: + self.facts['spacefree_mb'] = self.to_megabytes(match) + match = re.search(r'total-hdd-space:\s(.*)([KMG]iB)', data, re.M) + if match: + self.facts['spacetotal_mb'] = self.to_megabytes(match) + + def parse_memory_info(self, data): + match = re.search(r'free-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M) + if match: + self.facts['memfree_mb'] = self.to_megabytes(match) + match = re.search(r'total-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M) + if match: + self.facts['memtotal_mb'] = self.to_megabytes(match) + + def to_megabytes(self, data): + if data.group(2) == 'KiB': + return float(data.group(1)) / 1024 + elif data.group(2) == 'MiB': + return float(data.group(1)) + elif data.group(2) == 'GiB': + return float(data.group(1)) * 1024 + else: + return None + + +class Config(FactsBase): + + COMMANDS = ['/export verbose'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + '/interface print detail without-paging', + '/ip address print detail without-paging', + '/ipv6 address print detail without-paging', + '/ip neighbor print detail without-paging' + ] + + DETAIL_RE = re.compile(r'([\w\d\-]+)=\"?(\w{3}/\d{2}/\d{4}\s\d{2}:\d{2}:\d{2}|[\w\d\-\.:/]+)') + WRAPPED_LINE_RE = re.compile(r'^\s+(?!\d)') + + def populate(self): + super(Interfaces, self).populate() + + self.facts['interfaces'] = dict() + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + self.facts['neighbors'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv4') + + data = self.responses[2] + if data: + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv6') + + data = self.responses[3] + if data: + self.facts['neighbors'] = list(self.parse_detail(data)) + + def populate_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key] = value + + def populate_addresses(self, data, family): + for value in data: + key = value['interface'] + if family not in self.facts['interfaces'][key]: + self.facts['interfaces'][key][family] = list() + addr, subnet = value['address'].split("/") + ip = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), family) + self.facts['interfaces'][key][family].append(ip) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def preprocess(self, data): + preprocessed = list() + for line in data.split('\n'): + if len(line) == 0 or line[:5] == 'Flags': + continue + elif not re.match(self.WRAPPED_LINE_RE, line): + preprocessed.append(line) + else: + preprocessed[-1] += line + return preprocessed + + def parse_interfaces(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "name" not in parsed: + continue + facts[parsed["name"]] = dict(re.findall(self.DETAIL_RE, line)) + return facts + + def parse_detail(self, data): + data = self.preprocess(data) + for line in data: + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "interface" not in parsed: + continue + yield parsed + + +class Routing(FactsBase): + + COMMANDS = [ + '/routing bgp peer print detail without-paging', + '/routing bgp vpnv4-route print detail without-paging', + '/routing bgp instance print detail without-paging', + '/ip route print detail without-paging', + '/routing ospf instance print detail without-paging', + '/routing ospf neighbor print detail without-paging' + ] + + DETAIL_RE = re.compile(r'([\w\d\-]+)=\"?(\w{3}/\d{2}/\d{4}\s\d{2}:\d{2}:\d{2}|[\w\d\-\.:/]+)') + WRAPPED_LINE_RE = re.compile(r'^\s+(?!\d)') + + def populate(self): + super(Routing, self).populate() + self.facts['bgp_peer'] = dict() + self.facts['bgp_vpnv4_route'] = dict() + self.facts['bgp_instance'] = dict() + self.facts['route'] = dict() + self.facts['ospf_instance'] = dict() + self.facts['ospf_neighbor'] = dict() + data = self.responses[0] + if data: + peer = self.parse_bgp_peer(data) + self.populate_bgp_peer(peer) + data = self.responses[1] + if data: + vpnv4 = self.parse_vpnv4_route(data) + self.populate_vpnv4_route(vpnv4) + data = self.responses[2] + if data: + instance = self.parse_instance(data) + self.populate_bgp_instance(instance) + data = self.responses[3] + if data: + route = self.parse_route(data) + self.populate_route(route) + data = self.responses[4] + if data: + instance = self.parse_instance(data) + self.populate_ospf_instance(instance) + data = self.responses[5] + if data: + instance = self.parse_ospf_neighbor(data) + self.populate_ospf_neighbor(instance) + + def preprocess(self, data): + preprocessed = list() + for line in data.split('\n'): + if len(line) == 0 or line[:5] == 'Flags': + continue + elif not re.match(self.WRAPPED_LINE_RE, line): + preprocessed.append(line) + else: + preprocessed[-1] += line + return preprocessed + + def parse_name(self, data): + match = re.search(r'name=.(\S+\b)', data, re.M) + if match: + return match.group(1) + + def parse_interface(self, data): + match = re.search(r'interface=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + + def parse_instance_name(self, data): + match = re.search(r'instance=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + + def parse_routing_mark(self, data): + match = re.search(r'routing-mark=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + else: + match = 'main' + return match + + def parse_bgp_peer(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_instance(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_vpnv4_route(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_interface(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_route(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_routing_mark(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_ospf_instance(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_ospf_neighbor(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_instance_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def populate_bgp_peer(self, data): + for key, value in iteritems(data): + self.facts['bgp_peer'][key] = value + + def populate_vpnv4_route(self, data): + for key, value in iteritems(data): + self.facts['bgp_vpnv4_route'][key] = value + + def populate_bgp_instance(self, data): + for key, value in iteritems(data): + self.facts['bgp_instance'][key] = value + + def populate_route(self, data): + for key, value in iteritems(data): + self.facts['route'][key] = value + + def populate_ospf_instance(self, data): + for key, value in iteritems(data): + self.facts['ospf_instance'][key] = value + + def populate_ospf_neighbor(self, data): + for key, value in iteritems(data): + self.facts['ospf_neighbor'][key] = value + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + routing=Routing, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +warnings = list() + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + argument_spec.update(routeros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset: %s' % subset) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_command.py new file mode 100644 index 00000000..97a37296 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_command.py @@ -0,0 +1,219 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: slxos_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme Networks SLX-OS +description: + - Sends arbitrary commands to an SLX node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.slxos_config) to configure SLX-OS devices. +notes: + - Tested against SLX-OS 17s.1.02 + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote SLX-OS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.slxos_command: + commands: show version + + - name: Run show version and check to see if output contains SLX + community.network.slxos_command: + commands: show version + wait_for: result[0] contains SLX + + - name: Run multiple commands on remote nodes + community.network.slxos_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.slxos_command: + commands: + - show version + - show interface status + wait_for: + - result[0] contains SLX + - result[1] contains Eth + - name: Run command that requires answering a prompt + community.network.slxos_command: + commands: + - command: 'clear sessions' + prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' + answer: y +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +__metaclass__ = type + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='slxos_command does not support running config mode ' + 'commands. Please use slxos_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_config.py new file mode 100644 index 00000000..76351637 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_config.py @@ -0,0 +1,460 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: slxos_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme Networks SLX-OS configuration sections +description: + - Extreme SLX-OS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with SLX-OS configuration sections in + a deterministic way. +notes: + - Tested against SLX-OS 17s.1.02 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the SLX-OS device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + default: "@" + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config. If the argument is set to I(changed), then the running-config + will only be copied to the startup-config if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.slxos_config: + lines: hostname {{ inventory_hostname }} + +- name: Configure interface settings + community.network.slxos_config: + lines: + - description test interface + - ip address 172.31.1.1/24 + parents: interface Ethernet 0/1 + +- name: Configure multiple interfaces + community.network.slxos_config: + lines: + - lacp timeout long + parents: "{{ item }}" + with_items: + - interface Ethernet 0/1 + - interface Ethernet 0/2 + +- name: Load new acl into device + community.network.slxos_config: + lines: + - seq 10 permit ip host 1.1.1.1 any log + - seq 20 permit ip host 2.2.2.2 any log + - seq 30 permit ip host 3.3.3.3 any log + - seq 40 permit ip host 4.4.4.4 any log + - seq 50 permit ip host 5.5.5.5 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.slxos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.slxos_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: Save running to startup when modified + community.network.slxos_config: + save_when: modified + +- name: Configurable backup path + community.network.slxos_config: + lines: hostname {{ inventory_hostname }} + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/slxos_config.2018-02-12@18:26:34 +""" + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands, get_config, load_config +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + +__metaclass__ = type + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def get_running_config(module, current_config=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + src = module.params['src'] + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "copy running-config startup-config", + "prompt": "This operation will modify your startup configuration. Do you want to continue", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `copy running-config startup-config` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + path = module.params['parents'] + configobjs = candidate.difference(config, path=path, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show startup-config']) + + running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show startup-config') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_facts.py new file mode 100644 index 00000000..d08584cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_facts.py @@ -0,0 +1,451 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from devices running Extreme SLX-OS +description: + - Collects a base set of device facts from a remote device that + is running SLX-OS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against SLX-OS 17s.1.02 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: ['!config'] +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.slxos_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.slxos_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.slxos_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS) + + def run(self, cmd): + return run_commands(self.module, cmd) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show inventory chassis', + r'show running-config | include host\-name' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[2] + if data: + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'SLX-OS Operating System Version: (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'SID:(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'SN:(\S+)', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show process memory summary' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) + + def parse_memtotal(self, data): + match = re.search(r'Total\s*Memory: (\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'Total Free: (\d+)\s', data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interface', + 'show ipv6 interface brief', + r'show lldp nei detail | inc ^Local\ Interface|^Remote\ Interface|^System\ Name' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + + data = self.responses[1] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = list() + primary_address = addresses = [] + primary_address = re.findall(r'Primary Internet Address is (\S+)', value, re.M) + addresses = re.findall(r'Secondary Internet Address is (\S+)', value, re.M) + if not primary_address: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + # Only gets primary IPv6 addresses + def populate_ipv6_interfaces(self, data): + interfaces = re.split('=+', data)[1].strip() + matches = re.findall(r'(\S+ \S+) +[\w-]+.+\s+([\w:/]+/\d+)', interfaces, re.M) + for match in matches: + interface = match[0] + self.facts['interfaces'][interface]['ipv6'] = list() + address, masklen = match[1].split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][interface]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Local Interface: ') + if not lines: + return facts + for line in lines: + match = re.search(r'(\w+ \S+)\s+\(Local Int.+?\)[\s\S]+Remote Interface: (\S+.+?) \(Remote Int.+?\)[\s\S]+System Name: (\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for interface in data.split('\n\n'): + match = re.match(r'^(\S+ \S+)', interface, re.M) + if not match: + continue + else: + parsed[match.group(1)] = interface + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is Ethernet, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Primary Internet Address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+) bytes', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'LineSpeed Actual\s+:\s(.+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'Duplex: (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.match(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config"], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_interface.py new file mode 100644 index 00000000..f13e8a42 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_interface.py @@ -0,0 +1,464 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_interface +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Interfaces on Extreme SLX-OS network devices +description: + - This module provides declarative management of Interfaces + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + name: + description: + - Name of the Interface. + required: true + description: + description: + - Description of Interface. + enabled: + description: + - Interface link status. + default: True + type: bool + speed: + description: + - Interface link speed. + mtu: + description: + - Maximum size of transmit packet. + tx_rate: + description: + - Transmit rate in bits per second (bps). + rx_rate: + description: + - Receiver rate in bits per second (bps). + neighbors: + description: + - Check the operational state of given interface C(name) for LLDP neighbor. + - The following suboptions are available. + suboptions: + host: + description: + - "LLDP neighbor host for given interface C(name)." + port: + description: + - "LLDP neighbor port to which given interface C(name) is connected." + aggregate: + description: List of Interfaces definitions. + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + default: 10 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + choices: ['present', 'absent', 'up', 'down'] +''' + +EXAMPLES = """ +- name: Configure interface + community.network.slxos_interface: + name: Ethernet 0/2 + description: test-interface + speed: 1000 + mtu: 9216 + +- name: Remove interface + community.network.slxos_interface: + name: Loopback 9 + state: absent + +- name: Make interface up + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: True + +- name: Make interface down + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: False + +- name: Check intent arguments + community.network.slxos_interface: + name: Ethernet 0/2 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.slxos_interface: + name: Ethernet 0/41 + neighbors: + - port: Ethernet 0/41 + host: SLX + +- name: Config + intent + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: False + state: down + +- name: Add interface using aggregate + community.network.slxos_interface: + aggregate: + - { name: Ethernet 0/1, mtu: 1548, description: test-interface-1 } + - { name: Ethernet 0/2, mtu: 1548, description: test-interface-2 } + speed: 10000 + state: present + +- name: Delete interface using aggregate + community.network.slxos_interface: + aggregate: + - name: Loopback 9 + - name: Loopback 10 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/2 + - description test-interface + - mtu 1548 +""" +import re + +from copy import deepcopy +from time import sleep + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def validate_mtu(value, module): + if value and not 1548 <= int(value) <= 9216: + module.fail_json(msg='mtu must be between 1548 and 9216') + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_shutdown(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^shutdown', cfg, re.M) + if match: + return True + else: + return False + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+ \S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'description': parse_config_argument(configobj, item, 'description'), + 'speed': parse_config_argument(configobj, item, 'speed'), + 'mtu': parse_config_argument(configobj, item, 'mtu'), + 'disable': True if parse_shutdown(configobj, item) else False, + 'state': 'present' + } + instances.append(obj) + return instances + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'description': module.params['description'], + 'speed': module.params['speed'], + 'mtu': module.params['mtu'], + 'state': module.params['state'], + 'delay': module.params['delay'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + + validate_param_values(module, params) + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + args = ('speed', 'description', 'mtu') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + + if state == 'absent' and obj_in_have: + commands.append('no ' + interface) + + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate != running: + if candidate: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'shutdown', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'no shutdown', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('no shutdown') + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interface %s' % w['name'] + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + if want_state in ('up', 'down'): + match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) + have_state = None + if match: + have_state = match.group(1) + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + if want_tx_rate: + match = re.search(r'%s (\d+)' % 'Output', out, re.M) + have_tx_rate = None + if match: + have_tx_rate = match.group(1) + + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + + if want_rx_rate: + match = re.search(r'%s (\d+)' % 'Input', out, re.M) + have_rx_rate = None + if match: + have_rx_rate = match.group(1) + + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + + if want_neighbors: + have_host = [] + have_port = [] + if have_neighbors is None: + rc, have_neighbors, 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 Interface: ') + short_name = w['name'].replace('Ethernet', 'Eth') + for line in lines: + field = line.split('\n') + if field[0].split('(')[0].strip() == short_name: + for item in field: + if item.startswith('System Name:'): + have_host.append(item.split(':')[1].strip()) + if item.startswith('Remote Interface:'): + have_port.append(item.split(':')[1].split('(')[0].strip()) + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + neighbors_spec = dict( + host=dict(), + port=dict() + ) + + element_spec = dict( + name=dict(), + description=dict(), + speed=dict(), + mtu=dict(), + enabled=dict(default=True, type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions, changed=result['changed']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l2_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l2_interface.py new file mode 100644 index 00000000..ccd0e0f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l2_interface.py @@ -0,0 +1,501 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_l2_interface +short_description: Manage Layer-2 interface on Extreme Networks SLX-OS devices. +description: + - This module provides declarative management of Layer-2 interface on + Extreme slxos devices. +author: + - Matthew Stone (@bigmstone) +options: + name: + description: + - Full name of the interface excluding any logical + unit number, i.e. Ethernet 0/1. + required: true + aliases: ['interface'] + mode: + description: + - Mode in which interface needs to be configured. + default: access + choices: ['access', 'trunk'] + access_vlan: + description: + - Configure given VLAN in access port. + If C(mode=access), used as the access VLAN ID. + trunk_vlans: + description: + - List of VLANs to be configured in trunk port. + If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk. + native_vlan: + description: + - Native VLAN to be configured in trunk port. + If C(mode=trunk), used as the trunk native VLAN ID. + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. + If C(mode=trunk), these are the only VLANs that will be + configured on the trunk, i.e. "2-10,15". + aggregate: + description: + - List of Layer-2 interface definitions. + state: + description: + - Manage the state of the Layer-2 Interface configuration. + default: present + choices: ['present','absent', 'unconfigured'] +''' + +EXAMPLES = """ +- name: Ensure Ethernet 0/5 is in its default l2 interface state + community.network.slxos_l2_interface: + name: Ethernet 0/5 + state: unconfigured + +- name: Ensure Ethernet 0/5 is configured for access vlan 20 + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: access + access_vlan: 20 + +- name: Ensure Ethernet 0/5 only has vlans 5-10 as trunk vlans + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 5-10 + +- name: Ensure Ethernet 0/5 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 2-50 + +- name: Ensure these VLANs are not being tagged on the trunk + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + trunk_vlans: 51-4094 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/5 + - switchport access vlan 20 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config, run_commands + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Interface name\s+:\s', config) + return bool(match) + return False + + +def interface_is_portchannel(name, module): + if get_interface_type(name) == 'ethernet': + config = get_config(module) + if 'channel group' in config: + return True + + return False + + +def get_switchport(name, module): + config = run_commands(module, ['show interface {0} switchport'.format(name)])[0] + mode = re.search(r'Switchport mode\s+:\s(?:.* )?(\w+)$', config, re.M) + if mode: + mode = mode.group(1) + access = re.search(r'Default Vlan\s+:\s(\d+)', config) + if access: + access = access.group(1) + native = re.search(r'Native Vlan\s+:\s(\d+)', config) + if native: + native = native.group(1) + trunk = re.search(r'Active Vlans\s+:\s(.+)$', config, re.M) + if trunk: + trunk = trunk.group(1) + if trunk == 'ALL': + trunk = '1-4094' + + switchport_config = { + "interface": name, + "mode": mode, + "access_vlan": access, + "native_vlan": native, + "trunk_vlans": trunk, + } + + return switchport_config + + +def remove_switchport_config_commands(name, existing, proposed, module): + mode = proposed.get('mode') + commands = [] + command = None + + if mode == 'access': + av_check = existing.get('access_vlan') == proposed.get('access_vlan') + if av_check: + command = 'no switchport access vlan {0}'.format(existing.get('access_vlan')) + 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) + + native_check = existing.get('native_vlan') == proposed.get('native_vlan') + if native_check and proposed.get('native_vlan'): + command = 'no switchport trunk native vlan {0}'.format(existing.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def get_switchport_config_commands(name, existing, proposed, module): + """Gets commands required to config a given switchport interface + """ + + proposed_mode = proposed.get('mode') + existing_mode = existing.get('mode') + commands = [] + command = None + + if proposed_mode != existing_mode: + if proposed_mode == 'trunk': + command = 'switchport mode trunk' + elif proposed_mode == 'access': + command = 'switchport mode access' + + if command: + commands.append(command) + + if proposed_mode == 'access': + av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan')) + if not av_check: + command = 'switchport access vlan {0}'.format(proposed.get('access_vlan')) + commands.append(command) + + elif proposed_mode == 'trunk': + tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list') + + if not tv_check: + if proposed.get('allowed'): + command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_allowed_vlans')) + commands.append(command) + + else: + existing_vlans = existing.get('trunk_vlans_list') + proposed_vlans = proposed.get('trunk_vlans_list') + vlans_to_add = set(proposed_vlans).difference(existing_vlans) + if vlans_to_add: + command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_vlans')) + commands.append(command) + + native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan')) + if not native_check and proposed.get('native_vlan'): + command = 'switchport trunk native vlan {0}'.format(proposed.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def is_switchport_default(existing): + """Determines if switchport has a default config based on mode + Args: + existing (dict): existing switchport configuration from Ansible mod + Returns: + boolean: True if switchport has OOB Layer 2 config, i.e. + vlan 1 and trunk all and mode is access + """ + + c1 = str(existing['access_vlan']) == '1' + c2 = str(existing['native_vlan']) == '1' + c3 = existing['trunk_vlans'] == '1-4094' + c4 = existing['mode'] == 'access' + + default = c1 and c2 and c3 and c4 + + return default + + +def default_switchport_config(name): + commands = [] + commands.append('interface ' + name) + commands.append('switchport mode access') + commands.append('switch access vlan 1') + commands.append('switchport trunk native vlan 1') + commands.append('switchport trunk allowed vlan all') + return commands + + +def vlan_range_to_list(vlans): + result = [] + if vlans: + for part in vlans.split(','): + if part == 'none': + break + if '-' in part: + start, stop = (int(i) for i in part.split('-')) + result.extend(range(start, stop + 1)) + else: + result.append(int(part)) + return sorted(result) + + +def get_list_of_vlans(module): + config = run_commands(module, ['show vlan brief'])[0] + vlans = set() + + lines = config.strip().splitlines() + for line in lines: + line_parts = line.split() + if line_parts: + try: + int(line_parts[0]) + except ValueError: + continue + vlans.add(line_parts[0]) + + return list(vlans) + + +def flatten_list(commands): + flat_list = [] + for command in commands: + if isinstance(command, list): + flat_list.extend(command) + else: + flat_list.append(command) + return flat_list + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'mode': module.params['mode'], + 'access_vlan': module.params['access_vlan'], + 'native_vlan': module.params['native_vlan'], + 'trunk_vlans': module.params['trunk_vlans'], + 'trunk_allowed_vlans': module.params['trunk_allowed_vlans'], + 'state': module.params['state'] + }) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(type='str', aliases=['interface']), + mode=dict(choices=['access', 'trunk'], default='access'), + access_vlan=dict(type='str'), + native_vlan=dict(type='str'), + trunk_vlans=dict(type='str'), + trunk_allowed_vlans=dict(type='str'), + state=dict(choices=['absent', 'present', 'unconfigured'], default='present') + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=[['access_vlan', 'trunk_vlans'], + ['access_vlan', 'native_vlan'], + ['access_vlan', 'trunk_allowed_vlans']], + supports_check_mode=True) + + warnings = list() + commands = [] + result = {'changed': False, 'warnings': warnings} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + mode = w['mode'] + access_vlan = w['access_vlan'] + state = w['state'] + trunk_vlans = w['trunk_vlans'] + native_vlan = w['native_vlan'] + trunk_allowed_vlans = w['trunk_allowed_vlans'] + + args = dict(name=name, mode=mode, access_vlan=access_vlan, + native_vlan=native_vlan, trunk_vlans=trunk_vlans, + trunk_allowed_vlans=trunk_allowed_vlans) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + name = name.lower() + + if mode == 'access' and state == 'present' and not access_vlan: + module.fail_json(msg='access_vlan param is required when mode=access && state=present') + + if mode == 'trunk' and access_vlan: + module.fail_json(msg='access_vlan param not supported when using mode=trunk') + + if not is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L2' + '\nport first before using this module. You can use' + '\nthe slxos_interface module for this.') + + if interface_is_portchannel(name, module): + module.fail_json(msg='Cannot change L2 config on physical ' + '\nport because it is in a portchannel. ' + '\nYou should update the portchannel config.') + + # existing will never be null for Eth intfs as there is always a default + existing = get_switchport(name, module) + + # Safeguard check + # If there isn't an existing, something is wrong per previous comment + if not existing: + module.fail_json(msg='Make sure you are using the FULL interface name') + + if trunk_vlans or trunk_allowed_vlans: + if trunk_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_vlans) + elif trunk_allowed_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans) + proposed['allowed'] = True + + existing_trunks_list = vlan_range_to_list((existing['trunk_vlans'])) + + existing['trunk_vlans_list'] = existing_trunks_list + proposed['trunk_vlans_list'] = trunk_vlans_list + + current_vlans = get_list_of_vlans(module) + + if state == 'present': + if access_vlan and access_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=access_vlan) + elif native_vlan and native_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=native_vlan) + else: + command = get_switchport_config_commands(name, existing, proposed, module) + commands.append(command) + elif state == 'unconfigured': + is_default = is_switchport_default(existing) + if not is_default: + command = default_switchport_config(name) + commands.append(command) + elif state == 'absent': + command = remove_switchport_config_commands(name, existing, proposed, module) + commands.append(command) + + if trunk_vlans or trunk_allowed_vlans: + existing.pop('trunk_vlans_list') + proposed.pop('trunk_vlans_list') + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + result['changed'] = True + load_config(module, cmds) + if 'configure' in cmds: + cmds.pop(0) + + result['commands'] = cmds + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l3_interface.py new file mode 100644 index 00000000..240f1024 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_l3_interface.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_l3_interface +author: "Matthew Stone (@bigmstone)" +short_description: Manage L3 interfaces on Extreme Networks SLX-OS network devices. +description: + - This module provides declarative management of L3 interfaces + on slxos network devices. +notes: + - Tested against slxos 15.2 +options: + name: + description: + - Name of the L3 interface to be configured eg. Ethernet 0/2 + ipv4: + description: + - IPv4 address to be set for the L3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + ipv6: + description: + - IPv6 address to be set for the L3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64 + aggregate: + description: + - List of L3 interfaces definitions. Each of the entry in aggregate list should + define name of interface C(name) and a optional C(ipv4) or C(ipv6) address. + state: + description: + - State of the L3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Remove Ethernet 0/3 IPv4 and IPv6 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + state: absent + +- name: Set Ethernet 0/3 IPv4 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + ipv4: 192.168.0.1/24 + +- name: Set Ethernet 0/3 IPv6 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set IP addresses on aggregate + community.network.slxos_l3_interface: + aggregate: + - { name: Ethernet 0/3, ipv4: 192.168.2.10/24 } + - { name: Ethernet 0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.slxos_l3_interface: + aggregate: + - { name: Ethernet 0/3, ipv4: 192.168.2.10/24 } + - { name: Ethernet 0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/2 + - ip address 192.168.0.1/24 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" +import re + +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1).strip() + + return None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + commands.append('no ip address %s' % ipv4) + else: + commands.append('no ip address') + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address %s' % ipv6) + else: + commands.append('no ipv6 address') + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + commands.append('ip address %s' % ipv4) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() != obj_in_have['ipv6'].lower(): + commands.append('ipv6 address %s' % ipv6) + + if commands[-1] == interface: + commands.pop(-1) + + return commands + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+\s[0-9]+/[0-9]+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + # eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24 + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '%s/%s' % (address[0], to_text(to_masklen(address[1]))) + + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'] + }) + + validate_param_values(module, obj) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_linkagg.py new file mode 100644 index 00000000..69b5db2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_linkagg.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: slxos_linkagg +author: "Matthew Stone (@bigmstone)" +short_description: Manage link aggregation groups on Extreme Networks SLX-OS network devices +description: + - This module provides declarative management of link aggregation groups + on Extreme Networks SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-1024. + mode: + description: + - Mode of the link aggregation group. + choices: ['active', 'on', 'passive'] + members: + description: + - List of members of the link aggregation group. + aggregate: + description: List of link aggregation definitions. + state: + description: + - State of the link aggregation group. + default: present + choices: ['present', 'absent'] + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: false +''' + +EXAMPLES = """ +- name: Create link aggregation group + community.network.slxos_linkagg: + group: 10 + state: present + +- name: Delete link aggregation group + community.network.slxos_linkagg: + group: 10 + state: absent + +- name: Set link aggregation group to members + community.network.slxos_linkagg: + group: 200 + mode: active + members: + - Ethernet 0/1 + - Ethernet 0/2 + +- name: Remove link aggregation group from Ethernet 0/1 + community.network.slxos_linkagg: + group: 200 + mode: active + members: + - Ethernet 0/1 + +- name: Create aggregate of linkagg definitions + community.network.slxos_linkagg: + aggregate: + - { group: 3, mode: on, members: [Ethernet 0/1] } + - { group: 100, mode: passive, members: [Ethernet 0/2] } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface port-channel 30 + - interface Ethernet 0/3 + - channel-group 30 mode on + - no interface port-channel 30 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + group = w['group'] + mode = w['mode'] + members = w.get('members') or [] + state = w['state'] + del w['state'] + + obj_in_have = search_obj_in_list(group, have) + + if state == 'absent': + if obj_in_have: + commands.append('no interface port-channel {0}'.format(group)) + + elif state == 'present': + cmd = ['interface port-channel {0}'.format(group), + 'exit'] + if not obj_in_have: + if not group: + module.fail_json(msg='group is a required option') + commands.extend(cmd) + + if members: + for m in members: + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + else: + if members: + if 'members' not in obj_in_have.keys(): + for m in members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + elif set(members) != set(obj_in_have['members']): + missing_members = list(set(members) - set(obj_in_have['members'])) + for m in missing_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + superfluous_members = list(set(obj_in_have['members']) - set(members)) + for m in superfluous_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('no channel-group') + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['group'], want) + if not obj_in_want: + commands.append('no interface port-channel {0}'.format(h['group'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['group'] = str(d['group']) + + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'] + }) + + return obj + + +def parse_mode(module, config, group, member): + mode = None + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = ['interface {0}'.format(member)] + body = netcfg.get_section(parents) + + match_int = re.findall(r'interface {0}\n'.format(member), body, re.M) + if match_int: + match = re.search(r'channel-group {0} mode (\S+)'.format(group), body, re.M) + if match: + mode = match.group(1) + + return mode + + +def parse_members(module, config, group): + members = [] + + for line in config.strip().split('!'): + l = line.strip() + if l.startswith('interface'): + match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M) + if match_group: + match = re.search(r'^interface (\S+\s\S+)$', l, re.M) + if match: + members.append(match.group(1)) + + return members + + +def get_channel(module, config, group): + match = re.findall(r'^interface (\S+\s\S+)$', config, re.M) + + if not match: + return {} + + channel = {} + for item in set(match): + member = item + channel['mode'] = parse_mode(module, config, group, member) + channel['members'] = parse_members(module, config, group) + + return channel + + +def map_config_to_obj(module): + objs = list() + config = get_config(module) + + for line in config.split('\n'): + l = line.strip() + match = re.search(r'interface Port-channel (\S+)', l, re.M) + if match: + obj = {} + group = match.group(1) + obj['group'] = group + obj.update(get_channel(module, config, group)) + objs.append(obj) + + return objs + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + group=dict(type='int'), + mode=dict(choices=['active', 'on', 'passive']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True) + + required_one_of = [['group', 'aggregate']] + required_together = [['members', 'mode']] + mutually_exclusive = [['group', 'aggregate']] + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, + required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_lldp.py new file mode 100644 index 00000000..3606902e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_lldp.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_lldp +author: "Matthew Stone (@bigmstone)" +short_description: Manage LLDP configuration on Extreme Networks SLX-OS network devices. +description: + - This module provides declarative management of LLDP service + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + state: + description: + - State of the LLDP configuration. If value is I(present) lldp will be enabled + else if it is I(absent) it will be disabled. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Enable LLDP service + community.network.slxos_lldp: + state: present + +- name: Disable LLDP service + community.network.slxos_lldp: + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lldp run +""" +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import ( + load_config, + get_config +) + +PROTOCOL = "protocol lldp" + + +def has_lldp(module): + config = get_config(module) + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = [PROTOCOL] + body = netcfg.get_section(parents) + + for line in body.split('\n'): + l = line.strip() + match = re.search(r'disable', l, re.M) + if match: + return False + + return True + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + state=dict(default='present', + choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + HAS_LLDP = has_lldp(module) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + commands = [] + + if module.params['state'] == 'absent' and HAS_LLDP: + commands.append('protocol lldp') + commands.append('disable') + elif module.params['state'] == 'present' and not HAS_LLDP: + commands.append('protocol lldp') + commands.append('no disable') + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_vlan.py new file mode 100644 index 00000000..0ecf8a8d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/slxos/slxos_vlan.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_vlan +author: "Lindsay Hill (@lindsayhill)" +short_description: Manage VLANs on Extreme Networks SLX-OS network devices +description: + - This module provides declarative management of VLANs + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 18r.1.00 +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + interfaces: + description: + - List of interfaces that should be associated to the VLAN. + required: true + delay: + description: + - Delay the play should wait to check for declarative intent params values. + default: 10 + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + type: bool + default: no + state: + description: + - State of the VLAN configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vlan + community.network.slxos_vlan: + vlan_id: 100 + name: test-vlan + state: present +- name: Add interfaces to VLAN + community.network.slxos_vlan: + vlan_id: 100 + interfaces: + - Ethernet 0/1 + - Ethernet 0/2 +- name: Delete vlan + community.network.slxos_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import load_config, run_commands + + +def search_obj_in_list(vlan_id, lst): + for o in lst: + if o['vlan_id'] == vlan_id: + return o + return None + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + interfaces = w['interfaces'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + commands.append('no vlan %s' % vlan_id) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan %s' % vlan_id) + if name: + commands.append('name %s' % name) + + if interfaces: + for i in interfaces: + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + else: + if name: + if name != obj_in_have['name']: + commands.append('vlan %s' % vlan_id) + commands.append('name %s' % name) + + if interfaces: + if not obj_in_have['interfaces']: + for i in interfaces: + commands.append('vlan %s ' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + elif set(interfaces) != set(obj_in_have['interfaces']): + missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces'])) + for i in missing_interfaces: + commands.append('vlan %s' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) + for i in superfluous_interfaces: + commands.append('vlan %s' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport mode access') + commands.append('no switchport access vlan %s' % vlan_id) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + if not obj_in_want and h['vlan_id'] != '1': + commands.append('no vlan %s' % h['vlan_id']) + + return commands + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['vlan_id'] = str(d['vlan_id']) + + obj.append(d) + else: + obj.append({ + 'vlan_id': str(module.params['vlan_id']), + 'name': module.params['name'], + 'interfaces': module.params['interfaces'], + 'state': module.params['state'] + }) + + return obj + + +def map_config_to_obj(module): + output = run_commands(module, ['show vlan brief']) + lines = output[0].strip().splitlines()[5:] + + if not lines: + return list() + + objs = list() + obj = {} + + for l in lines: + splitted_line = re.split(r'([0-9]+)? +(\S.{14})? +(ACTIVE|INACTIVE\(.+?\))?.*(Eth .+?|Po .+?|Tu .+?)\([ut]\).*$', l.rstrip()) + if len(splitted_line) == 1: + # Handle situation where VLAN is configured, but has no associated ports + inactive = re.match(r'([0-9]+)? +(\S.{14}) +INACTIVE\(no member port\).*$', l.rstrip()) + if inactive: + splitted_line = ['', inactive.groups()[0], inactive.groups()[1], '', ''] + else: + continue + + splitted_line[4] = splitted_line[4].replace('Eth', 'Ethernet').replace('Po', 'Port-channel').replace('Tu', 'Tunnel') + + if splitted_line[1] is None: + obj['interfaces'].append(splitted_line[4]) + continue + + obj = {} + obj['vlan_id'] = splitted_line[1] + obj['name'] = splitted_line[2].strip() + obj['interfaces'] = [splitted_line[4]] + + objs.append(obj) + + return objs + + +def check_declarative_intent_params(want, module): + if module.params['interfaces']: + time.sleep(module.params['delay']) + have = map_config_to_obj(module) + + for w in want: + for i in w['interfaces']: + obj_in_have = search_obj_in_list(w['vlan_id'], have) + if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']: + module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + if result['changed']: + check_declarative_intent_params(want, module) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_command.py new file mode 100644 index 00000000..5ff8b0c0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_command.py @@ -0,0 +1,228 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_command +author: "Peter Sprygada (@privateip)" +short_description: Run commands on remote devices running Nokia SR OS +description: + - Sends arbitrary commands to an SR OS node and returns the results + read from the device. This module includes an argument that will + cause the module to wait for a specific condition before returning + or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.sros_config) to configure SR OS devices. +extends_documentation_fragment: +- community.network.sros + +options: + commands: + description: + - List of commands to send to the remote SR OS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +tasks: + - name: Run show version on remote devices + community.network.sros_command: + commands: show version + provider: "{{ cli }}" + + - name: Run show version and check to see if output contains sros + community.network.sros_command: + commands: show version + wait_for: result[0] contains sros + provider: "{{ cli }}" + + - name: Run multiple commands on remote nodes + community.network.sros_command: + commands: + - show version + - show port detail + provider: "{{ cli }}" + + - name: Run multiple commands and evaluate the output + community.network.sros_command: + commands: + - show version + - show port detail + wait_for: + - result[0] contains TiMOS-B-14.0.R4 + provider: "{{ cli }}" +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible.module_utils.six import string_types +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import run_commands, sros_argument_spec, check_args + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + module.fail_json( + msg='sros_command does not support running config mode ' + 'commands. Please use sros_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(sros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result = { + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + } + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_config.py new file mode 100644 index 00000000..1701516c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_config.py @@ -0,0 +1,329 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_config +author: "Peter Sprygada (@privateip)" +short_description: Manage Nokia SR OS device configuration +description: + - Nokia SR OS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with SR OS configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.sros + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. The I(lines) argument only supports current + context lines. See EXAMPLES + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. + If match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + force: + description: + - The force argument instructs the module to not consider the + current devices running-config. When set to true, this will + cause the module to push the contents of I(src) into the device + without first checking if already configured. + - Note this argument should be considered deprecated. To achieve + the equivalent, set the C(match=none) which is idempotent. This argument + will be removed in a future release. + type: bool + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(admin display-config detail). + type: bool + default: 'no' + aliases: ['detail'] + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +- name: Enable rollback location + community.network.sros_config: + lines: configure system rollback rollback-location "cf3:/ansible" + provider: "{{ cli }}" + +- name: Set system name to {{ inventory_hostname }} using one line + community.network.sros_config: + lines: + - configure system name "{{ inventory_hostname }}" + provider: "{{ cli }}" + +- name: Set system name to {{ inventory_hostname }} using parents + community.network.sros_config: + lines: + - 'name "{{ inventory_hostname }}"' + parents: + - configure + - system + provider: "{{ cli }}" + backup: yes + +- name: Load config from file + community.network.sros_config: + src: "{{ inventory_hostname }}.cfg" + provider: "{{ cli }}" + save: yes + +- name: Invalid use of lines + community.network.sros_config: + lines: + - service + - vpls 1000 customer foo 1 create + - description "invalid lines example" + provider: "{{ cli }}" + +- name: Valid use of lines + community.network.sros_config: + lines: + - description "invalid lines example" + parents: + - service + - vpls 1000 customer foo 1 create + provider: "{{ cli }}" + +- name: Configurable backup path + community.network.sros_config: + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['config system name "sros01"'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['config system name "sros01"'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/sros_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import sros_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import load_config, run_commands, get_config + + +def get_active_config(module): + contents = module.params['config'] + if not contents: + flags = [] + if module.params['defaults']: + flags = ['detail'] + return get_config(module, flags) + return contents + + +def get_candidate(module): + candidate = NetworkConfig(indent=4) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + + candidate = get_candidate(module) + + if match != 'none': + config_text = get_active_config(module) + config = NetworkConfig(indent=4, contents=config_text) + configobjs = candidate.difference(config) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands') + commands = commands.split('\n') + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + config=dict(), + defaults=dict(type='bool', default=False, aliases=['detail']), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + argument_spec.update(sros_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + result = dict(changed=False, warnings=list()) + + warnings = list() + check_args(module, warnings) + if warnings: + result['warnings'] = warnings + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + if module.params['save']: + if not module.check_mode: + run_commands(module, ['admin save']) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_rollback.py new file mode 100644 index 00000000..6980466c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/sros/sros_rollback.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_rollback +author: "Peter Sprygada (@privateip)" +short_description: Configure Nokia SR OS rollback +description: + - Configure the rollback feature on remote Nokia devices running + the SR OS operating system. this module provides a stateful + implementation for managing the configuration of the rollback + feature +extends_documentation_fragment: +- community.network.sros + +options: + rollback_location: + description: + - The I(rollback_location) specifies the location and filename + of the rollback checkpoint files. This argument supports any + valid local or remote URL as specified in SR OS + remote_max_checkpoints: + description: + - The I(remote_max_checkpoints) argument configures the maximum + number of rollback files that can be transferred and saved to + a remote location. Valid values for this argument are in the + range of 1 to 50 + local_max_checkpoints: + description: + - The I(local_max_checkpoints) argument configures the maximum + number of rollback files that can be saved on the devices local + compact flash. Valid values for this argument are in the range + of 1 to 50 + rescue_location: + description: + - The I(rescue_location) specifies the location of the + rescue file. This argument supports any valid local + or remote URL as specified in SR OS + state: + description: + - The I(state) argument specifies the state of the configuration + entries in the devices active configuration. When the state + value is set to C(true) the configuration is present in the + devices active configuration. When the state value is set to + C(false) the configuration values are removed from the devices + active configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +- name: Configure rollback location + community.network.sros_rollback: + rollback_location: "cb3:/ansible" + provider: "{{ cli }}" + +- name: Remove all rollback configuration + community.network.sros_rollback: + state: absent + provider: "{{ cli }}" +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['...', '...'] +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import load_config, get_config, sros_argument_spec, check_args + + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) + + +def sanitize_config(lines): + commands = list() + for line in lines: + for index, entry in enumerate(commands): + if line.startswith(entry): + del commands[index] + break + commands.append(line) + return commands + + +def present(module, commands): + setters = set() + for key, value in module.argument_spec.items(): + if module.params[key] is not None: + setter = value.get('setter') or 'set_%s' % key + if setter not in setters: + setters.add(setter) + invoke(setter, module, commands) + + +def absent(module, commands): + config = get_config(module) + if 'rollback-location' in config: + commands.append('configure system rollback no rollback-location') + if 'rescue-location' in config: + commands.append('configure system rollback no rescue-location') + if 'remote-max-checkpoints' in config: + commands.append('configure system rollback no remote-max-checkpoints') + if 'local-max-checkpoints' in config: + commands.append('configure system rollback no remote-max-checkpoints') + + +def set_rollback_location(module, commands): + value = module.params['rollback_location'] + commands.append('configure system rollback rollback-location "%s"' % value) + + +def set_local_max_checkpoints(module, commands): + value = module.params['local_max_checkpoints'] + if not 1 <= value <= 50: + module.fail_json(msg='local_max_checkpoints must be between 1 and 50') + commands.append('configure system rollback local-max-checkpoints %s' % value) + + +def set_remote_max_checkpoints(module, commands): + value = module.params['remote_max_checkpoints'] + if not 1 <= value <= 50: + module.fail_json(msg='remote_max_checkpoints must be between 1 and 50') + commands.append('configure system rollback remote-max-checkpoints %s' % value) + + +def set_rescue_location(module, commands): + value = module.params['rescue_location'] + commands.append('configure system rollback rescue-location "%s"' % value) + + +def get_device_config(module): + contents = get_config(module) + return NetworkConfig(indent=4, contents=contents) + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + rollback_location=dict(), + + local_max_checkpoints=dict(type='int'), + remote_max_checkpoints=dict(type='int'), + + rescue_location=dict(), + + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(sros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + state = module.params['state'] + + result = dict(changed=False) + + commands = list() + invoke(state, module, commands) + + candidate = NetworkConfig(indent=4, contents='\n'.join(commands)) + config = get_device_config(module) + configobjs = candidate.difference(config) + + if configobjs: + # commands = dumps(configobjs, 'lines') + commands = dumps(configobjs, 'commands') + commands = sanitize_config(commands.split('\n')) + + result['updates'] = commands + result['commands'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_command.py new file mode 100644 index 00000000..16aa7b53 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_command.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: voss_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme VOSS +description: + - Sends arbitrary commands to an Extreme VSP device running VOSS, and + returns the results read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.voss_config) to configure VOSS devices. +notes: + - Tested against VOSS 7.0.0 +options: + commands: + description: + - List of commands to send to the remote VOSS device. The + resulting output from the command is returned. If the + I(wait_for) argument is provided, the module is not returned + until the condition is satisfied or the number of retries has + expired. If a command sent to the device requires answering a + prompt, it is possible to pass a dict containing I(command), + I(answer) and I(prompt). Common answers are 'y' or "\\r" + (carriage return, must be double quotes). See examples. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +""" + +EXAMPLES = r""" +tasks: + - name: Run show sys software on remote devices + community.network.voss_command: + commands: show sys software + + - name: Run show sys software and check to see if output contains VOSS + community.network.voss_command: + commands: show sys software + wait_for: result[0] contains VOSS + + - name: Run multiple commands on remote nodes + community.network.voss_command: + commands: + - show sys software + - show interfaces vlan + + - name: Run multiple commands and evaluate the output + community.network.voss_command: + commands: + - show sys software + - show interfaces vlan + wait_for: + - result[0] contains Version + - result[1] contains Basic + + - name: Run command that requires answering a prompt + community.network.voss_command: + commands: + - command: 'reset' + prompt: 'Are you sure you want to reset the switch? (y/n)' + answer: 'y' +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='voss_command does not support running config mode ' + 'commands. Please use voss_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_config.py new file mode 100644 index 00000000..79440b90 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_config.py @@ -0,0 +1,451 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type +DOCUMENTATION = ''' +--- +module: voss_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme VOSS configuration sections +description: + - Extreme VOSS configurations use a simple flat text file syntax. + This module provides an implementation for working with EXOS + configuration lines in a deterministic way. +notes: + - Tested against VOSS 7.0.0 + - Abbreviated commands are NOT idempotent, see + L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands). +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The parent line that uniquely identifies the section the commands + should be checked against. If this argument is omitted, the commands + are checked against the set of top level or global commands. Note + that VOSS configurations only support one level of nested commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + choices: ['line', 'strict', 'exact', 'none'] + default: line + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) 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. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config verbose). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that behavior. If the argument is set to + I(always), then the running-config will always be saved and the + I(modified) flag will always be set to True. If the argument is set + to I(modified), then the running-config will only be saved if it + has changed since the last save to startup-config. If the argument + is set to I(never), the running-config will never be saved. + If the argument is set to I(changed), then the running-config + will only be saved if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure system name + community.network.voss_config: + lines: prompt "{{ inventory_hostname }}" + +- name: Configure interface settings + community.network.voss_config: + lines: + - name "ServerA" + backup: yes + parents: interface GigabitEthernet 1/1 + +- name: Check the running-config against master config + community.network.voss_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.voss_config: + diff_against: startup + diff_ignore_lines: + - qos queue-profile .* + +- name: Save running to startup when modified + community.network.voss_config: + save_when: modified + +- name: Configurable backup path + community.network.voss_config: + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['prompt "VSP200"'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['interface GigabitEthernet 1/1', 'name "ServerA"', 'exit'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/vsp200_config.2018-08-21@15:00:21 +""" +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands, get_config +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import get_defaults_flag, get_connection +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import get_sublevel_config, VossNetworkConfig +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps + + +def get_candidate_config(module): + candidate = VossNetworkConfig(indent=0) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + commands = module.params['lines'][0] + if (isinstance(commands, dict)) and (isinstance(commands['command'], list)): + candidate.add(commands['command'], parents=parents) + elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)): + candidate.add([commands['command']], parents=parents) + else: + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def get_running_config(module, current_config=None, flags=None): + running = module.params['running_config'] + if not running: + if not module.params['defaults'] and current_config: + running = current_config + else: + running = get_config(module, flags=flags) + + return running + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'save config\r') + else: + module.warn('Skipping command `save config` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + parents = module.params['parents'] or list() + + match = module.params['match'] + replace = module.params['replace'] + + warnings = list() + result['warnings'] = warnings + + diff_ignore_lines = module.params['diff_ignore_lines'] + + config = None + contents = None + flags = get_defaults_flag(module) if module.params['defaults'] else [] + connection = get_connection(module) + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = VossNetworkConfig(indent=0, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + candidate = get_candidate_config(module) + if match != 'none': + config = get_running_config(module) + config = VossNetworkConfig(contents=config, indent=0) + + if parents: + config = get_sublevel_config(config, module) + configobjs = candidate.difference(config, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands') + commands = commands.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + try: + connection.edit_config(candidate=commands) + except ConnectionError as exc: + module.fail_json(msg=to_text(commands, errors='surrogate_then_replace')) + + result['changed'] = True + + running_config = module.params['running_config'] + startup = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + match = module.params['match'] + replace = module.params['replace'] + try: + # Note we need to re-retrieve running config, not use cached version + running = connection.get_config(source='running') + startup = connection.get_config(source='startup') + response = connection.get_diff(candidate=startup, running=running, diff_match=match, + diff_ignore_lines=diff_ignore_lines, path=None, + diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response['config_diff'] + if config_diff: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + try: + # Note we need to re-retrieve running config, not use cached version + contents = connection.get_config(source='running') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + else: + contents = running_config + + # recreate the object in order to process diff_ignore_lines + running_config = VossNetworkConfig(indent=0, contents=contents, + ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup: + try: + contents = connection.get_config(source='startup') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + else: + contents = startup + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = VossNetworkConfig(indent=0, contents=contents, + ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_facts.py new file mode 100644 index 00000000..2e420abf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/network/voss/voss_facts.py @@ -0,0 +1,503 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: voss_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from remote devices running Extreme VOSS +description: + - Collects a base set of device facts from a remote device that + is running VOSS. This module prepends all of the base network fact + keys with C(ansible_net_). The facts module will always collect + a base set of facts from the device and can enable or disable + collection of additional facts. +notes: + - Tested against VOSS 7.0.0 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.voss_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.voss_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.voss_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show sys-info'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'SysDescr\s+: \S+ \((\S+)\)', data) + if match: + return match.group(1) + return '' + + def parse_hostname(self, data): + match = re.search(r'SysName\s+: (\S+)', data, re.M) + if match: + return match.group(1) + return '' + + def parse_model(self, data): + match = re.search(r'Chassis\s+: (\S+)', data, re.M) + if match: + return match.group(1) + return '' + + def parse_serialnum(self, data): + match = re.search(r'Serial#\s+: (\S+)', data) + if match: + return match.group(1) + return '' + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show khi performance memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + + if data: + match = re.search(r'Free:\s+(\d+)\s+\(KB\)', data, re.M) + if match: + self.facts['memfree_mb'] = int(round(int(match.group(1)) / 1024, 0)) + match = re.search(r'Used:\s+(\d+)\s+\(KB\)', data, re.M) + if match: + memused_mb = int(round(int(match.group(1)) / 1024, 0)) + self.facts['memtotal_mb'] = self.facts.get('memfree_mb', 0) + memused_mb + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces gigabitEthernet interface', + 'show interfaces gigabitEthernet name', + 'show ip interface', + 'show ipv6 address interface', + 'show lldp neighbor | include Port|SysName' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces_eth(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_interfaces_eth_additional(data) + + data = self.responses[2] + if data: + data = self.parse_interfaces(data) + self.populate_ipv4_interfaces(data) + + data = self.responses[3] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[4] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces_eth(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + match = re.match(r'^\d+\s+(\S+)\s+\w+\s+\w+\s+(\d+)\s+([a-f\d:]+)\s+(\w+)\s+(\w+)$', value) + if match: + intf['mediatype'] = match.group(1) + intf['mtu'] = match.group(2) + intf['macaddress'] = match.group(3) + intf['adminstatus'] = match.group(4) + intf['operstatus'] = match.group(5) + intf['type'] = 'Ethernet' + facts[key] = intf + return facts + + def populate_interfaces_eth_additional(self, interfaces): + for key, value in iteritems(interfaces): + # This matches when no description is set + match = re.match(r'^\w+\s+\w+\s+(\w+)\s+(\d+)\s+\w+$', value) + if match: + self.facts['interfaces'][key]['description'] = '' + self.facts['interfaces'][key]['duplex'] = match.group(1) + self.facts['interfaces'][key]['bandwidth'] = match.group(2) + else: + # This matches when a description is set + match = re.match(r'^(.+)\s+\w+\s+\w+\s+(\w+)\s+(\d+)\s+\w+$', value) + if match: + self.facts['interfaces'][key]['description'] = match.group(1).strip() + self.facts['interfaces'][key]['duplex'] = match.group(2) + self.facts['interfaces'][key]['bandwidth'] = match.group(3) + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + if key not in self.facts['interfaces']: + if re.match(r'Vlan\d+', key): + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['type'] = 'VLAN' + elif re.match(r'Clip\d+', key): + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['type'] = 'Loopback' + if re.match(r'Port(\d+/\d+)', key): + key = re.split('Port', key)[1] + self.facts['interfaces'][key]['ipv4'] = list() + match = re.match(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', value, re.M) + if match: + addr = match.group(1) + subnet = match.group(2) + ipv4 = dict(address=addr, subnet=subnet) + self.add_ip_address(addr, 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + def populate_ipv6_interfaces(self, data): + addresses = re.split(r'-{3,}', data)[1].lstrip() + for line in addresses.split('\n'): + if not line: + break + + match = re.match(r'^([\da-f:]+)/(\d+)\s+([CV])-(\d+)\s+.+$', line) + if match: + address = match.group(1) + subnet = match.group(2) + interface_short_name = match.group(3) + interface_id = match.group(4) + if interface_short_name == 'C': + intf_type = 'Loopback' + interface_name = 'Clip' + interface_id + elif interface_short_name == 'V': + intf_type = 'VLAN' + interface_name = 'Vlan' + interface_id + else: + # Unknown interface type, better to gracefully ignore it for now + break + ipv6 = dict(address=address, subnet=subnet) + self.add_ip_address(address, 'ipv6') + try: + self.facts['interfaces'][interface_name].setdefault('ipv6', []).append(ipv6) + self.facts['interfaces'][interface_name]['type'] = intf_type + except KeyError: + self.facts['interfaces'][interface_name] = dict() + self.facts['interfaces'][interface_name]['type'] = intf_type + self.facts['interfaces'][interface_name].setdefault('ipv6', []).append(ipv6) + else: + break + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Port: ') + if not lines: + return facts + for line in lines: + match = re.search(r'^(\w.*?)\s+Index.*IfName\s+(\w.*)$\s+SysName\s+:\s(\S+)', line, (re.M | re.S)) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + interfaces = re.split(r'-{3,}', data)[1].lstrip() + for line in interfaces.split('\n'): + if not line or re.match('^All', line): + break + else: + match = re.split(r'^(\S+)\s+', line) + key = match[1] + parsed[key] = match[2].strip() + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_macaddress(self, data): + match = re.search(r'Hardware is (?:.*), address is (\S+)', data) + if match: + return match.group(1) + return '' + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + return '' + + def parse_bandwidth(self, data): + match = re.search(r'BW (\d+)', data) + if match: + return int(match.group(1)) + return '' + + def parse_duplex(self, data): + match = re.search(r'(\w+) Duplex', data, re.M) + if match: + return match.group(1) + return '' + + def parse_mediatype(self, data): + match = re.search(r'media type is (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + return '' + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + return '' + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_command.py new file mode 100644 index 00000000..b47e2493 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_command.py @@ -0,0 +1,219 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: nos_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme Networks NOS +description: + - Sends arbitrary commands to a NOS device and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.nos_config) to configure NOS devices. +notes: + - Tested against NOS 7.2.0 + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote NOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.nos_command: + commands: show version + + - name: Run show version and check to see if output contains NOS + community.network.nos_command: + commands: show version + wait_for: result[0] contains NOS + + - name: Run multiple commands on remote nodes + community.network.nos_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.nos_command: + commands: + - show version + - show interface status + wait_for: + - result[0] contains NOS + - result[1] contains Te + - name: Run command that requires answering a prompt + community.network.nos_command: + commands: + - command: 'clear sessions' + prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' + answer: y +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +__metaclass__ = type + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='nos_command does not support running config mode ' + 'commands. Please use nos_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_config.py new file mode 100644 index 00000000..38e4fb7e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_config.py @@ -0,0 +1,389 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: nos_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme Networks NOS configuration sections +description: + - Extreme NOS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with NOS configuration sections in + a deterministic way. +notes: + - Tested against NOS 7.2.0 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the NOS device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + default: "@" + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.nos_config: + lines: logging raslog console INFO + +- name: Configure interface settings + community.network.nos_config: + lines: + - description test interface + - ip address 172.31.1.1/24 + parents: + - interface TenGigabitEthernet 104/0/1 + +- name: Configure multiple interfaces + community.network.nos_config: + lines: + - lacp timeout long + parents: "{{ item }}" + with_items: + - interface TenGigabitEthernet 104/0/1 + - interface TenGigabitEthernet 104/0/2 + +- name: Load new acl into device + community.network.nos_config: + lines: + - seq 10 permit ip host 1.1.1.1 any log + - seq 20 permit ip host 2.2.2.2 any log + - seq 30 permit ip host 3.3.3.3 any log + - seq 40 permit ip host 4.4.4.4 any log + - seq 50 permit ip host 5.5.5.5 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.nos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Configurable backup path + community.network.nos_config: + lines: logging raslog console INFO + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/nos_config.2018-02-12@18:26:34 +""" + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands, get_config, load_config +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + +__metaclass__ = type + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def get_running_config(module, current_config=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + src = module.params['src'] + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + diff_against=dict(choices=['intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + configobjs = candidate.difference(config, path=path, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_facts.py new file mode 100644 index 00000000..efa991e5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nos_facts.py @@ -0,0 +1,453 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nos_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from devices running Extreme NOS +description: + - Collects a base set of device facts from a remote device that + is running NOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against NOS 7.2.0 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +# Collect all facts from the device +- community.network.nos_facts: + gather_subset: all + +# Collect only the config and default facts +- community.network.nos_facts: + gather_subset: + - config + +# Do not collect hardware facts +- community.network.nos_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.nos.nos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS) + + def run(self, cmd): + return run_commands(self.module, cmd) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show inventory chassis', + r'show running-config | include host\-name' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[2] + if data: + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'Network Operating System Version: (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'SID:(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'SN:(\S+)', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show process memory summary' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) + + def parse_memtotal(self, data): + match = re.search(r'TotalMemory: (\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'Total Free: (\d+)\s', data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interface', + 'show ipv6 interface brief', + r'show lldp nei detail | inc ^Local\ Interface|^Remote\ Interface|^System\ Name' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + + data = self.responses[1] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + else: + self.facts['neighbors'] = dict() + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = list() + primary_address = addresses = [] + primary_address = re.findall(r'Primary Internet Address is (\S+)', value, re.M) + addresses = re.findall(r'Secondary Internet Address is (\S+)', value, re.M) + if not primary_address: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + # Only gets primary IPv6 addresses + def populate_ipv6_interfaces(self, data): + interfaces = re.split('=+', data)[1].strip() + matches = re.findall(r'(\S+ \S+) +[\w-]+.+\s+([\w:/]+/\d+)', interfaces, re.M) + for match in matches: + interface = match[0] + self.facts['interfaces'][interface]['ipv6'] = list() + address, masklen = match[1].split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][interface]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Local Interface: ') + if not lines: + return facts + for line in lines: + match = re.search(r'(\w+ \S+)\s+\(Local Int.+?\)[\s\S]+Remote Interface: (\S+.+?) \(Remote Int.+?\)[\s\S]+System Name: (\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for interface in data.split('\n\n'): + match = re.match(r'^(\S+ \S+)', interface, re.M) + if not match: + continue + else: + parsed[match.group(1)] = interface + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is Ethernet, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Primary Internet Address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+) bytes', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'LineSpeed Actual\s+:\s(.+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'Duplex: (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.match(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config"], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_action.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_action.py new file mode 100644 index 00000000..74ca48e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_action.py @@ -0,0 +1,184 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_action +extends_documentation_fragment: +- community.network.nso + +short_description: Executes Cisco NSO actions and verifies output. +description: + - This module provides support for executing Cisco NSO actions and then + verifying that the output is as expected. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO action. + required: true + input: + description: > + NSO action parameters. + output_required: + description: > + Required output parameters. + output_invalid: + description: > + List of result parameter names that will cause the task to fail if they + are present. + validate_strict: + description: > + If set to true, the task will fail if any output parameters not in + output_required is present in the output. + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Sync NSO device + community.network.nso_action: + url: http://localhost:8080/jsonrpc + username: username + password: password + path: /ncs:devices/device{ce0}/sync-from + input: {} +''' + +RETURN = ''' +output: + description: Action output + returned: success + type: dict + sample: + result: true +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import normalize_value +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoAction(object): + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, + path, input, + output_required, output_invalid, validate_strict): + self._check_mode = check_mode + self._client = client + self._path = path + self._input = input + self._output_required = output_required + self._output_invalid = output_invalid + self._validate_strict = validate_strict + + def main(self): + schema = self._client.get_schema(path=self._path) + if schema['data']['kind'] != 'action': + raise ModuleFailException('{0} is not an action'.format(self._path)) + + input_schema = [c for c in schema['data']['children'] + if c.get('is_action_input', False)] + + for key, value in self._input.items(): + child = next((c for c in input_schema if c['name'] == key), None) + if child is None: + raise ModuleFailException('no parameter {0}'.format(key)) + + # implement type validation in the future + + if self._check_mode: + return {} + else: + return self._run_and_verify() + + def _run_and_verify(self): + output = self._client.run_action(None, self._path, self._input) + for key, value in self._output_required.items(): + if key not in output: + raise ModuleFailException('{0} not in result'.format(key)) + + n_value = normalize_value(value, output[key], key) + if value != n_value: + msg = '{0} value mismatch. expected {1} got {2}'.format( + key, value, n_value) + raise ModuleFailException(msg) + + for key in self._output_invalid.keys(): + if key in output: + raise ModuleFailException('{0} not allowed in result'.format(key)) + + if self._validate_strict: + for name in output.keys(): + if name not in self._output_required: + raise ModuleFailException('{0} not allowed in result'.format(name)) + + return output + + +def main(): + argument_spec = dict( + path=dict(required=True), + input=dict(required=False, type='dict', default={}), + output_required=dict(required=False, type='dict', default={}), + output_invalid=dict(required=False, type='dict', default={}), + validate_strict=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_action = NsoAction( + module.check_mode, client, + p['path'], + p['input'], + p['output_required'], + p['output_invalid'], + p['validate_strict']) + try: + verify_version(client, NsoAction.REQUIRED_VERSIONS) + + output = nso_action.main() + client.logout() + module.exit_json(changed=True, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_config.py new file mode 100644 index 00000000..eb938251 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_config.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_config +extends_documentation_fragment: +- community.network.nso + +short_description: Manage Cisco NSO configuration and service synchronization. +description: + - This module provides support for managing configuration in Cisco NSO and + can also ensure services are in sync. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as | display json converted to YAML. List entries can + be annotated with a __state entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true +''' + +EXAMPLES = ''' +- name: Create L3VPN + community.network.nso_config: + url: http://localhost:8080/jsonrpc + username: username + password: password + data: + l3vpn:vpn: + l3vpn: + - name: company + route-distinguisher: 999 + endpoint: + - id: branch-office1 + ce-device: ce6 + ce-interface: GigabitEthernet0/12 + ip-network: 10.10.1.0/24 + bandwidth: 12000000 + as-number: 65101 + - id: branch-office2 + ce-device: ce1 + ce-interface: GigabitEthernet0/11 + ip-network: 10.7.7.0/24 + bandwidth: 6000000 + as-number: 65102 + - id: branch-office3 + __state: absent + __state: in-sync +''' + +RETURN = ''' +changes: + description: List of changes + returned: always + type: complex + sample: + - path: "/l3vpn:vpn/l3vpn{example}/endpoint{office}/bandwidth" + from: '6000000' + to: '12000000' + type: set + contains: + path: + description: Path to value changed + returned: always + type: str + from: + description: Previous value if any, else null + returned: When previous value is present on value change + type: str + to: + description: Current value if any, else null. + returned: When new value is present on value change + type: + description: Type of change. create|delete|set|re-deploy +diffs: + description: List of sync changes + returned: always + type: complex + sample: + - path: "/l3vpn:vpn/l3vpn{example}" + diff: |2 + devices { + device pe3 { + config { + alu:service { + vprn 65101 { + bgp { + group example-ce6 { + - peer-as 65102; + + peer-as 65101; + } + } + } + } + } + } + } + contains: + path: + description: keypath to service changed + returned: always + type: str + diff: + description: configuration difference triggered the re-deploy + returned: always + type: str +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import State, ValueBuilder +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoConfig(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, data): + self._check_mode = check_mode + self._client = client + self._data = data + + self._changes = [] + self._diffs = [] + + def main(self): + # build list of values from configured data + value_builder = ValueBuilder(self._client) + for key, value in self._data.items(): + value_builder.build('', key, value) + + self._data_write(value_builder.values) + + # check sync AFTER configuration is written + sync_values = self._sync_check(value_builder.values) + self._sync_ensure(sync_values) + + return self._changes, self._diffs + + def _data_write(self, values): + th = self._client.get_trans(mode='read_write') + + for value in values: + if value.state == State.SET: + self._client.set_value(th, value.path, value.value) + elif value.state == State.PRESENT: + self._client.create(th, value.path) + elif value.state == State.ABSENT: + self._client.delete(th, value.path) + + changes = self._client.get_trans_changes(th) + for change in changes: + if change['op'] == 'value_set': + self._changes.append({ + 'path': change['path'], + 'from': change['old'] or None, + 'to': change['value'], + 'type': 'set' + }) + elif change['op'] in ('created', 'deleted'): + self._changes.append({ + 'path': change['path'], + 'type': change['op'][:-1] + }) + + if len(changes) > 0: + warnings = self._client.validate_commit(th) + if len(warnings) > 0: + raise NsoException( + 'failed to validate transaction with warnings: {0}'.format( + ', '.join((str(warning) for warning in warnings))), {}) + + if self._check_mode or len(changes) == 0: + self._client.delete_trans(th) + else: + self._client.commit(th) + + def _sync_check(self, values): + sync_values = [] + + for value in values: + if value.state in (State.CHECK_SYNC, State.IN_SYNC): + action = 'check-sync' + elif value.state in (State.DEEP_CHECK_SYNC, State.DEEP_IN_SYNC): + action = 'deep-check-sync' + else: + action = None + + if action is not None: + action_path = '{0}/{1}'.format(value.path, action) + action_params = {'outformat': 'cli'} + resp = self._client.run_action(None, action_path, action_params) + if len(resp) > 0: + sync_values.append( + ValueBuilder.Value(value.path, value.state, resp[0]['value'])) + + return sync_values + + def _sync_ensure(self, sync_values): + for value in sync_values: + if value.state in (State.CHECK_SYNC, State.DEEP_CHECK_SYNC): + raise NsoException( + '{0} out of sync, diff {1}'.format(value.path, value.value), {}) + + action_path = '{0}/{1}'.format(value.path, 're-deploy') + if not self._check_mode: + result = self._client.run_action(None, action_path) + if not result: + raise NsoException( + 'failed to re-deploy {0}'.format(value.path), {}) + + self._changes.append({'path': value.path, 'type': 're-deploy'}) + self._diffs.append({'path': value.path, 'diff': value.value}) + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_config = NsoConfig(module.check_mode, client, p['data']) + try: + verify_version(client, NsoConfig.REQUIRED_VERSIONS) + + changes, diffs = nso_config.main() + client.logout() + + changed = len(changes) > 0 + module.exit_json( + changed=changed, changes=changes, diffs=diffs) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_query.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_query.py new file mode 100644 index 00000000..2f7b53ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_query.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_query +extends_documentation_fragment: +- community.network.nso + +short_description: Query data from Cisco NSO. +description: + - This module provides support for querying data from Cisco NSO using XPath. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + xpath: + description: XPath selection relative to the root. + required: true + fields: + description: > + List of fields to select from matching nodes. + required: true +''' + +EXAMPLES = ''' +- name: Select device name and description + community.network.nso_query: + url: http://localhost:8080/jsonrpc + username: username + password: password + xpath: /ncs:devices/device + fields: + - name + - description +''' + +RETURN = ''' +output: + description: Value of matching nodes + returned: success + type: list +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoQuery(object): + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, xpath, fields): + self._check_mode = check_mode + self._client = client + self._xpath = xpath + self._fields = fields + + def main(self): + if self._check_mode: + return [] + else: + return self._client.query(self._xpath, self._fields) + + +def main(): + argument_spec = dict( + xpath=dict(required=True, type='str'), + fields=dict(required=True, type='list') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_query = NsoQuery( + module.check_mode, client, + p['xpath'], p['fields']) + try: + verify_version(client, NsoQuery.REQUIRED_VERSIONS) + + output = nso_query.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_show.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_show.py new file mode 100644 index 00000000..c7c05fb6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_show.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_show +extends_documentation_fragment: +- community.network.nso + +short_description: Displays data from Cisco NSO. +description: + - This module provides support for displaying data from Cisco NSO. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.1.9 or higher, 4.2.6 or higher, + 4.3.7 or higher, 4.4.5 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO data. + required: true + operational: + description: > + Controls whether or not operational data is included in the result. + type: bool + default: false +''' + +EXAMPLES = ''' +- name: Show devices including operational data + community.network.nso_show: + url: http://localhost:8080/jsonrpc + username: username + password: password + path: /ncs:devices/device + operational: true +''' + +RETURN = ''' +output: + description: Configuration + returned: success + type: dict +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoShow(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 5), + (4, 3, 7), + (4, 2, 6), + (4, 1, 9), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, path, operational): + self._check_mode = check_mode + self._client = client + self._path = path + self._operational = operational + + def main(self): + if self._check_mode: + return {} + else: + return self._client.show_config(self._path, self._operational) + + +def main(): + argument_spec = dict( + path=dict(required=True, type='str'), + operational=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_show = NsoShow( + module.check_mode, client, + p['path'], p['operational']) + try: + verify_version(client, NsoShow.REQUIRED_VERSIONS) + + output = nso_show.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_verify.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_verify.py new file mode 100644 index 00000000..637aa6ed --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nso_verify.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_verify +extends_documentation_fragment: +- community.network.nso + +short_description: Verifies Cisco NSO configuration. +description: + - This module provides support for verifying Cisco NSO configuration is in + compliance with specified values. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as C(| display json) converted to YAML. List entries can + be annotated with a C(__state) entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true +''' + +EXAMPLES = ''' +- name: Verify interface is up + nso_config: + url: http://localhost:8080/jsonrpc + username: username + password: password + data: + ncs:devices: + device: + - name: ce0 + live-status: + interfaces: + interface: + - name: GigabitEthernet0/12 + - state: Up +''' + +RETURN = ''' +violations: + description: List of value violations + returned: failed + type: complex + sample: + - path: /ncs:devices/device{ce0}/description + expected-value: CE0 example + value: null + contains: + path: + description: Path to the value in violation + returned: always + type: str + expected-value: + description: Expected value of path + returned: always + type: str + value: + description: Current value of path + returned: always + type: str +''' + +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import normalize_value +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import State, ValueBuilder +from ansible_collections.community.network.plugins.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoVerify(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, client, data): + self._client = client + self._data = data + + def main(self): + violations = [] + + # build list of values from configured data + value_builder = ValueBuilder(self._client, 'verify') + for key, value in self._data.items(): + value_builder.build('', key, value) + + for expected_value in value_builder.values: + if expected_value.state == State.PRESENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'present', + 'value': 'absent' + }) + elif expected_value.state == State.ABSENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'absent', + 'value': 'present' + }) + elif expected_value.state == State.SET: + try: + value = self._client.get_value(expected_value.path)['value'] + except NsoException as ex: + if ex.error.get('type', '') == 'data.not_found': + value = None + else: + raise + + # handle different types properly + n_value = normalize_value( + expected_value.value, value, expected_value.path) + if n_value != expected_value.value: + # if the value comparison 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( + expected_value.state, expected_value.path)) + + return violations + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_verify = NsoVerify(client, p['data']) + try: + verify_version(client, NsoVerify.REQUIRED_VERSIONS) + + violations = nso_verify.main() + client.logout() + + num_violations = len(violations) + if num_violations > 0: + msg = '{0} value{1} differ'.format( + num_violations, num_violations > 1 and 's' or '') + module.fail_json(msg=msg, violations=violations) + else: + module.exit_json(changed=False) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/nuage_vspk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nuage_vspk.py new file mode 100644 index 00000000..39fdae7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/nuage_vspk.py @@ -0,0 +1,1016 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Nokia +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: nuage_vspk +short_description: Manage Nuage VSP environments +description: + - Manage or find Nuage VSP entities, this includes create, update, delete, assign, unassign and find, with all supported properties. +author: Philippe Dellaert (@pdellaert) +options: + auth: + description: + - Dict with the authentication information required to connect to a Nuage VSP environment. + - Requires a I(api_username) parameter (example csproot). + - Requires either a I(api_password) parameter (example csproot) or a I(api_certificate) and I(api_key) parameters, + which point to the certificate and key files for certificate based authentication. + - Requires a I(api_enterprise) parameter (example csp). + - Requires a I(api_url) parameter (example https://10.0.0.10:8443). + - Requires a I(api_version) parameter (example v4_0). + required: true + type: + description: + - The type of entity you want to work on (example Enterprise). + - This should match the objects CamelCase class name in VSPK-Python. + - This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html). + required: true + id: + description: + - The ID of the entity you want to work on. + - In combination with I(command=find), it will only return the single entity. + - In combination with I(state), it will either update or delete this entity. + - Will take precedence over I(match_filter) and I(properties) whenever an entity needs to be found. + parent_id: + description: + - The ID of the parent of the entity you want to work on. + - When I(state) is specified, the entity will be gathered from this parent, if it exists, unless an I(id) is specified. + - When I(command=find) is specified, the entity will be searched for in this parent, unless an I(id) is specified. + - If specified, I(parent_type) also needs to be specified. + parent_type: + description: + - The type of parent the ID is specified for (example Enterprise). + - This should match the objects CamelCase class name in VSPK-Python. + - This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html). + - If specified, I(parent_id) also needs to be specified. + state: + description: + - Specifies the desired state of the entity. + - If I(state=present), in case the entity already exists, will update the entity if it is needed. + - If I(state=present), in case the relationship with the parent is a member relationship, will assign the entity as a member of the parent. + - If I(state=absent), in case the relationship with the parent is a member relationship, will unassign the entity as a member of the parent. + - Either I(state) or I(command) needs to be defined, both can not be defined at the same time. + choices: + - present + - absent + command: + description: + - Specifies a command to be executed. + - With I(command=find), if I(parent_id) and I(parent_type) are defined, it will only search within the parent. Otherwise, if allowed, + will search in the root object. + - With I(command=find), if I(id) is specified, it will only return the single entity matching the id. + - With I(command=find), otherwise, if I(match_filter) is define, it will use that filter to search. + - With I(command=find), otherwise, if I(properties) are defined, it will do an AND search using all properties. + - With I(command=change_password), a password of a user can be changed. Warning - In case the password is the same as the existing, + it will throw an error. + - With I(command=wait_for_job), the module will wait for a job to either have a status of SUCCESS or ERROR. In case an ERROR status is found, + the module will exit with an error. + - With I(command=wait_for_job), the job will always be returned, even if the state is ERROR situation. + - Either I(state) or I(command) needs to be defined, both can not be defined at the same time. + choices: + - find + - change_password + - wait_for_job + - get_csp_enterprise + match_filter: + description: + - A filter used when looking (both in I(command) and I(state) for entities, in the format the Nuage VSP API expects. + - If I(match_filter) is defined, it will take precedence over the I(properties), but not on the I(id) + properties: + description: + - Properties are the key, value pairs of the different properties an entity has. + - If no I(id) and no I(match_filter) is specified, these are used to find or determine if the entity exists. + children: + description: + - Can be used to specify a set of child entities. + - A mandatory property of each child is the I(type). + - Supported optional properties of each child are I(id), I(properties) and I(match_filter). + - The function of each of these properties is the same as in the general task definition. + - This can be used recursively + - Only useable in case I(state=present). +notes: + - Check mode is supported, but with some caveats. It will not do any changes, and if possible try to determine if it is able do what is requested. + - In case a parent id is provided from a previous task, it might be empty and if a search is possible on root, it will do so, which can impact performance. +requirements: + - Python 2.7 + - Supports Nuage VSP 4.0Rx & 5.x.y + - Proper VSPK-Python installed for your Nuage version + - Tested with NuageX U(https://nuagex.io) +''' + +EXAMPLES = ''' +# This can be executed as a single role, with the following vars +# vars: +# auth: +# api_username: csproot +# api_password: csproot +# api_enterprise: csp +# api_url: https://10.0.0.10:8443 +# api_version: v5_0 +# enterprise_name: Ansible-Enterprise +# enterprise_new_name: Ansible-Updated-Enterprise +# +# or, for certificate based authentication +# vars: +# auth: +# api_username: csproot +# api_certificate: /path/to/user-certificate.pem +# api_key: /path/to/user-Key.pem +# api_enterprise: csp +# api_url: https://10.0.0.10:8443 +# api_version: v5_0 +# enterprise_name: Ansible-Enterprise +# enterprise_new_name: Ansible-Updated-Enterprise + +# Creating a new enterprise +- name: Create Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + state: present + properties: + name: "{{ enterprise_name }}-basic" + register: nuage_enterprise + +# Checking if an Enterprise with the new name already exists +- name: Check if an Enterprise exists with the new name + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + command: find + properties: + name: "{{ enterprise_new_name }}-basic" + ignore_errors: yes + register: nuage_check_enterprise + +# Updating an enterprise's name +- name: Update Enterprise name + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + id: "{{ nuage_enterprise.id }}" + state: present + properties: + name: "{{ enterprise_new_name }}-basic" + when: nuage_check_enterprise is failed + +# Creating a User in an Enterprise +- name: Create admin user + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + state: present + match_filter: "userName == 'ansible-admin'" + properties: + email: "ansible@localhost.local" + first_name: "Ansible" + last_name: "Admin" + password: "ansible-password" + user_name: "ansible-admin" + register: nuage_user + +# Updating password for User +- name: Update admin password + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + command: change_password + properties: + password: "ansible-new-password" + ignore_errors: yes + +# Finding a group in an enterprise +- name: Find Administrators group in Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Group + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + command: find + properties: + name: "Administrators" + register: nuage_group + +# Assign the user to the group +- name: Assign admin user to administrators + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + parent_id: "{{ nuage_group.id }}" + parent_type: Group + state: present + +# Creating multiple DomainTemplates +- name: Create multiple DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + state: present + properties: + name: "{{ item }}" + description: "Created by Ansible" + with_items: + - "Template-1" + - "Template-2" + +# Finding all DomainTemplates +- name: Fetching all DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + parent_id: "{{ nuage_enterprise.id }}" + parent_type: Enterprise + command: find + register: nuage_domain_templates + +# Deleting all DomainTemplates +- name: Deleting all found DomainTemplates + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: DomainTemplate + state: absent + id: "{{ item.ID }}" + with_items: "{{ nuage_domain_templates.entities }}" + when: nuage_domain_templates.entities is defined + +# Unassign user from group +- name: Unassign admin user to administrators + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: User + id: "{{ nuage_user.id }}" + parent_id: "{{ nuage_group.id }}" + parent_type: Group + state: absent + +# Deleting an enterprise +- name: Delete Enterprise + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + id: "{{ nuage_enterprise.id }}" + state: absent + +# Setup an enterprise with Children +- name: Setup Enterprise and domain structure + connection: local + community.network.nuage_vspk: + auth: "{{ nuage_auth }}" + type: Enterprise + state: present + properties: + name: "Child-based-Enterprise" + children: + - type: L2DomainTemplate + properties: + name: "Unmanaged-Template" + children: + - type: EgressACLTemplate + match_filter: "name == 'Allow All'" + properties: + name: "Allow All" + active: true + default_allow_ip: true + default_allow_non_ip: true + default_install_acl_implicit_rules: true + description: "Created by Ansible" + priority_type: "TOP" + - type: IngressACLTemplate + match_filter: "name == 'Allow All'" + properties: + name: "Allow All" + active: true + default_allow_ip: true + default_allow_non_ip: true + description: "Created by Ansible" + priority_type: "TOP" +''' + +RETURN = ''' +id: + description: The id of the entity that was found, created, updated or assigned. + returned: On state=present and command=find in case one entity was found. + type: str + sample: bae07d8d-d29c-4e2b-b6ba-621b4807a333 +entities: + description: A list of entities handled. Each element is the to_dict() of the entity. + returned: On state=present and find, with only one element in case of state=present or find=one. + type: list + sample: [{ + "ID": acabc435-3946-4117-a719-b8895a335830", + "assocEntityType": "DOMAIN", + "command": "BEGIN_POLICY_CHANGES", + "creationDate": 1487515656000, + "entityScope": "ENTERPRISE", + "externalID": null, + "lastUpdatedBy": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e", + "lastUpdatedDate": 1487515656000, + "owner": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e", + "parameters": null, + "parentID": "a22fddb9-3da4-4945-bd2e-9d27fe3d62e0", + "parentType": "domain", + "progress": 0.0, + "result": null, + "status": "RUNNING" + }] +''' + +import time + +try: + import importlib + HAS_IMPORTLIB = True +except ImportError: + HAS_IMPORTLIB = False + +try: + from bambou.exceptions import BambouHTTPError + HAS_BAMBOU = True +except ImportError: + HAS_BAMBOU = False + +from ansible.module_utils.basic import AnsibleModule + + +SUPPORTED_COMMANDS = ['find', 'change_password', 'wait_for_job', 'get_csp_enterprise'] +VSPK = None + + +class NuageEntityManager(object): + """ + This module is meant to manage an entity in a Nuage VSP Platform + """ + + def __init__(self, module): + self.module = module + self.auth = module.params['auth'] + self.api_username = None + self.api_password = None + self.api_enterprise = None + self.api_url = None + self.api_version = None + self.api_certificate = None + self.api_key = None + self.type = module.params['type'] + + self.state = module.params['state'] + self.command = module.params['command'] + self.match_filter = module.params['match_filter'] + self.entity_id = module.params['id'] + self.parent_id = module.params['parent_id'] + self.parent_type = module.params['parent_type'] + self.properties = module.params['properties'] + self.children = module.params['children'] + + self.entity = None + self.entity_class = None + self.parent = None + self.parent_class = None + self.entity_fetcher = None + + self.result = { + 'state': self.state, + 'id': self.entity_id, + 'entities': [] + } + self.nuage_connection = None + + self._verify_api() + self._verify_input() + self._connect_vspk() + self._find_parent() + + def _connect_vspk(self): + """ + Connects to a Nuage API endpoint + """ + try: + # Connecting to Nuage + if self.api_certificate and self.api_key: + self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, enterprise=self.api_enterprise, api_url=self.api_url, + certificate=(self.api_certificate, self.api_key)) + else: + self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, password=self.api_password, enterprise=self.api_enterprise, + api_url=self.api_url) + self.nuage_connection.start() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to connect to the API URL with given username, password and enterprise: {0}'.format(error)) + + def _verify_api(self): + """ + Verifies the API and loads the proper VSPK version + """ + # Checking auth parameters + if ('api_password' not in list(self.auth.keys()) or not self.auth['api_password']) and ('api_certificate' not in list(self.auth.keys()) or + 'api_key' not in list(self.auth.keys()) or + not self.auth['api_certificate'] or not self.auth['api_key']): + self.module.fail_json(msg='Missing api_password or api_certificate and api_key parameter in auth') + + self.api_username = self.auth['api_username'] + if 'api_password' in list(self.auth.keys()) and self.auth['api_password']: + self.api_password = self.auth['api_password'] + if 'api_certificate' in list(self.auth.keys()) and 'api_key' in list(self.auth.keys()) and self.auth['api_certificate'] and self.auth['api_key']: + self.api_certificate = self.auth['api_certificate'] + self.api_key = self.auth['api_key'] + self.api_enterprise = self.auth['api_enterprise'] + self.api_url = self.auth['api_url'] + self.api_version = self.auth['api_version'] + + try: + global VSPK + VSPK = importlib.import_module('vspk.{0:s}'.format(self.api_version)) + except ImportError: + self.module.fail_json(msg='vspk is required for this module, or the API version specified does not exist.') + + def _verify_input(self): + """ + Verifies the parameter input for types and parent correctness and necessary parameters + """ + + # Checking if type exists + try: + self.entity_class = getattr(VSPK, 'NU{0:s}'.format(self.type)) + except AttributeError: + self.module.fail_json(msg='Unrecognised type specified') + + if self.module.check_mode: + return + + if self.parent_type: + # Checking if parent type exists + try: + self.parent_class = getattr(VSPK, 'NU{0:s}'.format(self.parent_type)) + except AttributeError: + # The parent type does not exist, fail + self.module.fail_json(msg='Unrecognised parent type specified') + + fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name) + if fetcher is None: + # The parent has no fetcher, fail + self.module.fail_json(msg='Specified parent is not a valid parent for the specified type') + elif not self.entity_id: + # If there is an id, we do not need a parent because we'll interact directly with the entity + # If an assign needs to happen, a parent will have to be provided + # Root object is the parent + self.parent_class = VSPK.NUMe + fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name) + if fetcher is None: + self.module.fail_json(msg='No parent specified and root object is not a parent for the type') + + # Verifying if a password is provided in case of the change_password command: + if self.command and self.command == 'change_password' and 'password' not in self.properties.keys(): + self.module.fail_json(msg='command is change_password but the following are missing: password property') + + def _find_parent(self): + """ + Fetches the parent if needed, otherwise configures the root object as parent. Also configures the entity fetcher + Important notes: + - If the parent is not set, the parent is automatically set to the root object + - It the root object does not hold a fetcher for the entity, you have to provide an ID + - If you want to assign/unassign, you have to provide a valid parent + """ + self.parent = self.nuage_connection.user + + if self.parent_id: + self.parent = self.parent_class(id=self.parent_id) + try: + self.parent.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified parent: {0}'.format(error)) + + self.entity_fetcher = self.parent.fetcher_for_rest_name(self.entity_class.rest_name) + + def _find_entities(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None): + """ + Will return a set of entities matching a filter or set of properties if the match_filter is unset. If the + entity_id is set, it will return only the entity matching that ID as the single element of the list. + :param entity_id: Optional ID of the entity which should be returned + :param entity_class: Optional class of the entity which needs to be found + :param match_filter: Optional search filter + :param properties: Optional set of properties the entities should contain + :param entity_fetcher: The fetcher for the entity type + :return: List of matching entities + """ + search_filter = '' + + if entity_id: + found_entity = entity_class(id=entity_id) + try: + found_entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error)) + + return [found_entity] + + elif match_filter: + search_filter = match_filter + elif properties: + # Building filter + for num, property_name in enumerate(properties): + if num > 0: + search_filter += ' and ' + search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name]) + + if entity_fetcher is not None: + try: + return entity_fetcher.get(filter=search_filter) + except BambouHTTPError: + pass + return [] + + def _find_entity(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None): + """ + Finds a single matching entity that matches all the provided properties, unless an ID is specified, in which + case it just fetches the one item + :param entity_id: Optional ID of the entity which should be returned + :param entity_class: Optional class of the entity which needs to be found + :param match_filter: Optional search filter + :param properties: Optional set of properties the entities should contain + :param entity_fetcher: The fetcher for the entity type + :return: The first entity matching the criteria, or None if none was found + """ + search_filter = '' + if entity_id: + found_entity = entity_class(id=entity_id) + try: + found_entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error)) + + return found_entity + + elif match_filter: + search_filter = match_filter + elif properties: + # Building filter + for num, property_name in enumerate(properties): + if num > 0: + search_filter += ' and ' + search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name]) + + if entity_fetcher is not None: + try: + return entity_fetcher.get_first(filter=search_filter) + except BambouHTTPError: + pass + return None + + def handle_main_entity(self): + """ + Handles the Ansible task + """ + if self.command and self.command == 'find': + self._handle_find() + elif self.command and self.command == 'change_password': + self._handle_change_password() + elif self.command and self.command == 'wait_for_job': + self._handle_wait_for_job() + elif self.command and self.command == 'get_csp_enterprise': + self._handle_get_csp_enterprise() + elif self.state == 'present': + self._handle_present() + elif self.state == 'absent': + self._handle_absent() + self.module.exit_json(**self.result) + + def _handle_absent(self): + """ + Handles the Ansible task when the state is set to absent + """ + # Absent state + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.entity and (self.entity_fetcher is None or self.entity_fetcher.relationship in ['child', 'root']): + # Entity is present, deleting + if self.module.check_mode: + self.result['changed'] = True + else: + self._delete_entity(self.entity) + self.result['id'] = None + elif self.entity and self.entity_fetcher.relationship == 'member': + # Entity is a member, need to check if already present + if self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._unassign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent, + set_output=True) + + def _handle_present(self): + """ + Handles the Ansible task when the state is set to present + """ + # Present state + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + # Determining action to take + if self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and not self.entity: + self.module.fail_json(msg='Trying to assign an entity that does not exist') + elif self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and self.entity: + # Entity is a member, need to check if already present + if not self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._assign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent, + set_output=True) + elif self.entity_fetcher is not None and self.entity_fetcher.relationship in ['child', 'root'] and not self.entity: + # Entity is not present as a child, creating + if self.module.check_mode: + self.result['changed'] = True + else: + self.entity = self._create_entity(entity_class=self.entity_class, parent=self.parent, properties=self.properties) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + # Checking children + if self.children: + for child in self.children: + self._handle_child(child=child, parent=self.entity) + elif self.entity: + # Need to compare properties in entity and found entity + changed = self._has_changed(entity=self.entity, properties=self.properties) + + if self.module.check_mode: + self.result['changed'] = changed + elif changed: + self.entity = self._save_entity(entity=self.entity) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + else: + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + # Checking children + if self.children: + for child in self.children: + self._handle_child(child=child, parent=self.entity) + elif not self.module.check_mode: + self.module.fail_json(msg='Invalid situation, verify parameters') + + def _handle_get_csp_enterprise(self): + """ + Handles the Ansible task when the command is to get the csp enterprise + """ + self.entity_id = self.parent.enterprise_id + self.entity = VSPK.NUEnterprise(id=self.entity_id) + try: + self.entity.fetch() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to fetch CSP enterprise: {0}'.format(error)) + self.result['id'] = self.entity_id + self.result['entities'].append(self.entity.to_dict()) + + def _handle_wait_for_job(self): + """ + Handles the Ansible task when the command is to wait for a job + """ + # Command wait_for_job + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.module.check_mode: + self.result['changed'] = True + else: + self._wait_for_job(self.entity) + + def _handle_change_password(self): + """ + Handles the Ansible task when the command is to change a password + """ + # Command change_password + self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + if self.module.check_mode: + self.result['changed'] = True + else: + try: + getattr(self.entity, 'password') + except AttributeError: + self.module.fail_json(msg='Entity does not have a password property') + + try: + setattr(self.entity, 'password', self.properties['password']) + except AttributeError: + self.module.fail_json(msg='Password can not be changed for entity') + + self.entity = self._save_entity(entity=self.entity) + self.result['id'] = self.entity.id + self.result['entities'].append(self.entity.to_dict()) + + def _handle_find(self): + """ + Handles the Ansible task when the command is to find an entity + """ + # Command find + entities = self._find_entities(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties, + entity_fetcher=self.entity_fetcher) + self.result['changed'] = False + if entities: + if len(entities) == 1: + self.result['id'] = entities[0].id + for entity in entities: + self.result['entities'].append(entity.to_dict()) + elif not self.module.check_mode: + self.module.fail_json(msg='Unable to find matching entries') + + def _handle_child(self, child, parent): + """ + Handles children of a main entity. Fields are similar to the normal fields + Currently only supported state: present + """ + if 'type' not in list(child.keys()): + self.module.fail_json(msg='Child type unspecified') + elif 'id' not in list(child.keys()) and 'properties' not in list(child.keys()): + self.module.fail_json(msg='Child ID or properties unspecified') + + # Setting intern variables + child_id = None + if 'id' in list(child.keys()): + child_id = child['id'] + child_properties = None + if 'properties' in list(child.keys()): + child_properties = child['properties'] + child_filter = None + if 'match_filter' in list(child.keys()): + child_filter = child['match_filter'] + + # Checking if type exists + entity_class = None + try: + entity_class = getattr(VSPK, 'NU{0:s}'.format(child['type'])) + except AttributeError: + self.module.fail_json(msg='Unrecognised child type specified') + + entity_fetcher = parent.fetcher_for_rest_name(entity_class.rest_name) + if entity_fetcher is None and not child_id and not self.module.check_mode: + self.module.fail_json(msg='Unable to find a fetcher for child, and no ID specified.') + + # Try and find the child + entity = self._find_entity(entity_id=child_id, entity_class=entity_class, match_filter=child_filter, properties=child_properties, + entity_fetcher=entity_fetcher) + + # Determining action to take + if entity_fetcher.relationship == 'member' and not entity: + self.module.fail_json(msg='Trying to assign a child that does not exist') + elif entity_fetcher.relationship == 'member' and entity: + # Entity is a member, need to check if already present + if not self._is_member(entity_fetcher=entity_fetcher, entity=entity): + # Entity is not a member yet + if self.module.check_mode: + self.result['changed'] = True + else: + self._assign_member(entity_fetcher=entity_fetcher, entity=entity, entity_class=entity_class, parent=parent, set_output=False) + elif entity_fetcher.relationship in ['child', 'root'] and not entity: + # Entity is not present as a child, creating + if self.module.check_mode: + self.result['changed'] = True + else: + entity = self._create_entity(entity_class=entity_class, parent=parent, properties=child_properties) + elif entity_fetcher.relationship in ['child', 'root'] and entity: + changed = self._has_changed(entity=entity, properties=child_properties) + + if self.module.check_mode: + self.result['changed'] = changed + elif changed: + entity = self._save_entity(entity=entity) + + if entity: + self.result['entities'].append(entity.to_dict()) + + # Checking children + if 'children' in list(child.keys()) and not self.module.check_mode: + for subchild in child['children']: + self._handle_child(child=subchild, parent=entity) + + def _has_changed(self, entity, properties): + """ + Compares a set of properties with a given entity, returns True in case the properties are different from the + values in the entity + :param entity: The entity to check + :param properties: The properties to check + :return: boolean + """ + # Need to compare properties in entity and found entity + changed = False + if properties: + for property_name in list(properties.keys()): + if property_name == 'password': + continue + entity_value = '' + try: + entity_value = getattr(entity, property_name) + except AttributeError: + self.module.fail_json(msg='Property {0:s} is not valid for this type of entity'.format(property_name)) + + if entity_value != properties[property_name]: + # Difference in values changing property + changed = True + try: + setattr(entity, property_name, properties[property_name]) + except AttributeError: + self.module.fail_json(msg='Property {0:s} can not be changed for this type of entity'.format(property_name)) + return changed + + def _is_member(self, entity_fetcher, entity): + """ + Verifies if the entity is a member of the parent in the fetcher + :param entity_fetcher: The fetcher for the entity type + :param entity: The entity to look for as a member in the entity fetcher + :return: boolean + """ + members = entity_fetcher.get() + for member in members: + if member.id == entity.id: + return True + return False + + def _assign_member(self, entity_fetcher, entity, entity_class, parent, set_output): + """ + Adds the entity as a member to a parent + :param entity_fetcher: The fetcher of the entity type + :param entity: The entity to add as a member + :param entity_class: The class of the entity + :param parent: The parent on which to add the entity as a member + :param set_output: If set to True, sets the Ansible result variables + """ + members = entity_fetcher.get() + members.append(entity) + try: + parent.assign(members, entity_class) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to assign entity as a member: {0}'.format(error)) + self.result['changed'] = True + if set_output: + self.result['id'] = entity.id + self.result['entities'].append(entity.to_dict()) + + def _unassign_member(self, entity_fetcher, entity, entity_class, parent, set_output): + """ + Removes the entity as a member of a parent + :param entity_fetcher: The fetcher of the entity type + :param entity: The entity to remove as a member + :param entity_class: The class of the entity + :param parent: The parent on which to add the entity as a member + :param set_output: If set to True, sets the Ansible result variables + """ + members = [] + for member in entity_fetcher.get(): + if member.id != entity.id: + members.append(member) + try: + parent.assign(members, entity_class) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to remove entity as a member: {0}'.format(error)) + self.result['changed'] = True + if set_output: + self.result['id'] = entity.id + self.result['entities'].append(entity.to_dict()) + + def _create_entity(self, entity_class, parent, properties): + """ + Creates a new entity in the parent, with all properties configured as in the file + :param entity_class: The class of the entity + :param parent: The parent of the entity + :param properties: The set of properties of the entity + :return: The entity + """ + entity = entity_class(**properties) + try: + parent.create_child(entity) + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to create entity: {0}'.format(error)) + self.result['changed'] = True + return entity + + def _save_entity(self, entity): + """ + Updates an existing entity + :param entity: The entity to save + :return: The updated entity + """ + try: + entity.save() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to update entity: {0}'.format(error)) + self.result['changed'] = True + return entity + + def _delete_entity(self, entity): + """ + Deletes an entity + :param entity: The entity to delete + """ + try: + entity.delete() + except BambouHTTPError as error: + self.module.fail_json(msg='Unable to delete entity: {0}'.format(error)) + self.result['changed'] = True + + def _wait_for_job(self, entity): + """ + Waits for a job to finish + :param entity: The job to wait for + """ + running = False + if entity.status == 'RUNNING': + self.result['changed'] = True + running = True + + while running: + time.sleep(1) + entity.fetch() + + if entity.status != 'RUNNING': + running = False + + self.result['entities'].append(entity.to_dict()) + if entity.status == 'ERROR': + self.module.fail_json(msg='Job ended in an error') + + +def main(): + """ + Main method + """ + module = AnsibleModule( + argument_spec=dict( + auth=dict( + required=True, + type='dict', + options=dict( + api_username=dict(required=True, type='str'), + api_enterprise=dict(required=True, type='str'), + api_url=dict(required=True, type='str'), + api_version=dict(required=True, type='str'), + api_password=dict(default=None, required=False, type='str', no_log=True), + api_certificate=dict(default=None, required=False, type='str', no_log=True), + api_key=dict(default=None, required=False, type='str', no_log=True) + ) + ), + type=dict(required=True, type='str'), + id=dict(default=None, required=False, type='str'), + parent_id=dict(default=None, required=False, type='str'), + parent_type=dict(default=None, required=False, type='str'), + state=dict(default=None, choices=['present', 'absent'], type='str'), + command=dict(default=None, choices=SUPPORTED_COMMANDS, type='str'), + match_filter=dict(default=None, required=False, type='str'), + properties=dict(default=None, required=False, type='dict'), + children=dict(default=None, required=False, type='list') + ), + mutually_exclusive=[ + ['command', 'state'] + ], + required_together=[ + ['parent_id', 'parent_type'] + ], + required_one_of=[ + ['command', 'state'] + ], + required_if=[ + ['state', 'present', ['id', 'properties', 'match_filter'], True], + ['state', 'absent', ['id', 'properties', 'match_filter'], True], + ['command', 'change_password', ['id', 'properties']], + ['command', 'wait_for_job', ['id']] + ], + supports_check_mode=True + ) + + if not HAS_BAMBOU: + module.fail_json(msg='bambou is required for this module') + + if not HAS_IMPORTLIB: + module.fail_json(msg='importlib (python 2.7) is required for this module') + + entity_manager = NuageEntityManager(module) + entity_manager.handle_main_entity() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/opx_cps.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/opx_cps.py new file mode 100644 index 00000000..ec5cb6db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/opx_cps.py @@ -0,0 +1,389 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# This file is part of Ansible by Red Hat +# +# 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 . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: opx_cps +author: "Senthil Kumar Ganesan (@skg-net)" +short_description: CPS operations on networking device running Openswitch (OPX) +description: + - Executes the given operation on the YANG object, using CPS API in the + networking device running OpenSwitch (OPX). It uses the YANG models + provided in https://github.com/open-switch/opx-base-model. +options: + module_name: + description: + - Yang path to be configured. + attr_type: + description: + - Attribute Yang type. + attr_data: + description: + - Attribute Yang path and their corresponding data. + operation: + description: + - Operation to be performed on the object. + default: create + choices: ['delete', 'create', 'set', 'action', 'get'] + db: + description: + - Queries/Writes the specified yang path from/to the db. + type: bool + default: 'no' + qualifier: + description: + - A qualifier provides the type of object data to retrieve or act on. + default: target + choices: ['target', 'observed', 'proposed', 'realtime', 'registration', 'running', 'startup'] + commit_event: + description: + - Attempts to force the auto-commit event to the specified yang object. + type: bool + default: 'no' +requirements: + - "cps" + - "cps_object" + - "cps_utils" +''' + +EXAMPLES = """ +- name: Create VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "base-if-vlan/if/interfaces/interface/id": 230, + "if/interfaces/interface/name": "br230", + "if/interfaces/interface/type": "ianaift:l2vlan" + } + operation: "create" +- name: Get VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "if/interfaces/interface/name": "br230", + } + operation: "get" +- name: Modify some attributes in VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "cps/key_data": + { "if/interfaces/interface/name": "br230" }, + "dell-if/if/interfaces/interface/untagged-ports": ["e101-008-0"], + } + operation: "set" +- name: Delete VLAN + community.network.opx_cps: + module_name: "dell-base-if-cmn/if/interfaces/interface" + attr_data: { + "if/interfaces/interface/name": "br230", + } + operation: "delete" +""" + +RETURN = """ +response: + description: Output from the CPS transaction. + Output of CPS Get operation if CPS set/create/delete not done. + returned: when a CPS transaction is successfully performed. + type: list + sample: + [{ + "data": { + "base-if-vlan/if/interfaces/interface/id": 230, + "cps/object-group/return-code": 0, + "dell-base-if-cmn/if/interfaces/interface/if-index": 46, + "if/interfaces/interface/name": "br230", + "if/interfaces/interface/type": "ianaift:l2vlan" + }, + "key": "target/dell-base-if-cmn/if/interfaces/interface" + }] +cps_curr_config: + description: Returns the CPS Get output i.e. the running configuration + before CPS operation of set/delete is performed + returned: when CPS operations set, delete + type: dict + sample: + [{ + "data": { + "base-if-vlan/if/interfaces/interface/id": 230, + "cps/key_data": { + "if/interfaces/interface/name": "br230" + }, + "dell-base-if-cmn/if/interfaces/interface/if-index": 44, + "dell-if/if/interfaces/interface/learning-mode": 1, + "dell-if/if/interfaces/interface/mtu": 1532, + "dell-if/if/interfaces/interface/phys-address": "", + "dell-if/if/interfaces/interface/vlan-type": 1, + "if/interfaces/interface/enabled": 0, + "if/interfaces/interface/type": "ianaift:l2vlan" + }, + "key": "target/dell-base-if-cmn/if/interfaces/interface" + }] +diff: + description: The actual configuration that will be pushed comparing + the running configuration and input attributes + returned: when CPS operations set, delete + type: dict + sample: + { + "cps/key_data": { + "if/interfaces/interface/name": "br230" + }, + "dell-if/if/interfaces/interface/untagged-ports": [ + "e101-007-0" + ] + } +db: + description: Denotes if CPS DB transaction was performed + returned: when db is set to True in module options + type: bool + sample: True +commit_event: + description: Denotes if auto-commit event is set + returned: when commit_event is set to True in module options + type: bool + sample: True +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import dict_diff + +try: + import cps + import cps_object + import cps_utils + HAS_CPS = True +except ImportError: + HAS_CPS = False + + +def convert_cps_raw_list(raw_list): + resp_list = [] + if raw_list: + for raw_elem in raw_list: + processed_element = convert_cps_raw_data(raw_elem) + if processed_element: + raw_key = raw_elem['key'] + individual_element = {} + individual_element['data'] = processed_element + individual_element['key'] = (cps.qual_from_key(raw_key) + "/" + + cps.name_from_key(raw_key, 1)) + resp_list.append(individual_element) + return resp_list + + +def convert_cps_raw_data(raw_elem): + d = {} + obj = cps_object.CPSObject(obj=raw_elem) + for attr in raw_elem['data']: + d[attr] = obj.get_attr_data(attr) + return d + + +def parse_cps_parameters(module_name, qualifier, attr_type, + attr_data, operation=None, db=None, + commit_event=None): + + obj = cps_object.CPSObject(module=module_name, qual=qualifier) + + if operation: + obj.set_property('oper', operation) + + if attr_type: + for key, val in iteritems(attr_type): + cps_utils.cps_attr_types_map.add_type(key, val) + + for key, val in iteritems(attr_data): + + embed_attrs = key.split(',') + embed_attrs_len = len(embed_attrs) + if embed_attrs_len >= 3: + obj.add_embed_attr(embed_attrs, val, embed_attrs_len - 2) + else: + if isinstance(val, str): + val_list = val.split(',') + # Treat as list if value contains ',' but is not + # enclosed within {} + if len(val_list) == 1 or val.startswith('{'): + obj.add_attr(key, val) + else: + obj.add_attr(key, val_list) + else: + obj.add_attr(key, val) + + if db: + cps.set_ownership_type(obj.get_key(), 'db') + obj.set_property('db', True) + else: + obj.set_property('db', False) + + if commit_event: + cps.set_auto_commit_event(obj.get_key(), True) + obj.set_property('commit-event', True) + return obj + + +def cps_get(obj): + + RESULT = dict() + key = obj.get() + l = [] + cps.get([key], l) + + resp_list = convert_cps_raw_list(l) + + RESULT["response"] = resp_list + return RESULT + + +def cps_transaction(obj): + + RESULT = dict() + ch = {'operation': obj.get_property('oper'), 'change': obj.get()} + if cps.transaction([ch]): + RESULT["response"] = convert_cps_raw_list([ch['change']]) + RESULT["changed"] = True + else: + error_msg = "Transaction error while " + obj.get_property('oper') + raise RuntimeError(error_msg) + return RESULT + + +def parse_key_data(attrs): + + res = dict() + for key, val in iteritems(attrs): + if key == 'cps/key_data': + res.update(val) + else: + res[key] = val + return res + + +def main(): + """ + main entry point for module execution + """ + argument_spec = dict( + qualifier=dict(required=False, + default="target", + type='str', + choices=['target', 'observed', 'proposed', 'realtime', + 'registration', 'running', 'startup']), + module_name=dict(required=True, type='str'), + attr_type=dict(required=False, type='dict'), + attr_data=dict(required=True, type='dict'), + operation=dict(required=False, + default="create", + type='str', + choices=['delete', 'create', 'set', 'action', 'get']), + db=dict(required=False, default=False, type='bool'), + commit_event=dict(required=False, default=False, type='bool') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False) + + if not HAS_CPS: + module.fail_json(msg='CPS library required for this module') + + qualifier = module.params['qualifier'] + module_name = module.params['module_name'] + attr_type = module.params["attr_type"] + attr_data = module.params["attr_data"] + operation = module.params['operation'] + db = module.params["db"] + commit_event = module.params["commit_event"] + RESULT = dict(changed=False, db=False, commit_event=False) + + if db: + RESULT['db'] = True + if commit_event: + RESULT['commit_event'] = True + + try: + # First do a CPS get operation + get_obj = parse_cps_parameters(module_name, qualifier, attr_type, + attr_data, 'get', db, commit_event) + curr_config = cps_get(get_obj) + + if operation == 'get': + RESULT.update(curr_config) + else: + diff = attr_data + + # Evaluate the changes in the attributes + cfg = dict() + if curr_config and curr_config['response']: + cfg = curr_config['response'][0]['data'] + key_d = 'cps/key_data' + + # diff computation is not needed for delete + if operation != 'delete': + configs = parse_key_data(cfg) + attributes = parse_key_data(attr_data) + diff = dict_diff(configs, attributes) + # Append diff with any 'cps/key_data' from attr_data + if diff and key_d in attr_data: + diff[key_d] = attr_data[key_d] + + # Append diff with any 'cps/key_data' from curr_config + # Needed for all operations including delete + if diff and key_d in cfg: + if key_d in diff: + diff[key_d].update(cfg[key_d]) + else: + diff[key_d] = cfg[key_d] + + RESULT.update({"diff": diff}) + + # Create object for cps operation + obj = parse_cps_parameters(module_name, qualifier, attr_type, + diff, operation, db, commit_event) + + res = dict() + if operation == "delete": + if cfg: + res = cps_transaction(obj) + else: + if diff: + res = cps_transaction(obj) + + if not res and cfg: + res.update({"response": curr_config['response']}) + else: + res.update({"cps_curr_config": curr_config['response']}) + RESULT.update(res) + + except Exception as e: + module.fail_json(msg=str(type(e).__name__) + ": " + str(e)) + + module.exit_json(**RESULT) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_config.py new file mode 100644 index 00000000..1cc72195 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_config.py @@ -0,0 +1,356 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ordnance_config +author: "Alexander Turner (@alexanderturner) " +short_description: Manage Ordnance configuration sections +description: + - Ordnance router configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with these configuration sections in + a deterministic way. +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the Ordnance router. It specifies the character to use + as the delimiting character. This only applies to the + configuration action + default: "@" + backup: + description: + - 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. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + type: bool + default: 'no' +''' + +EXAMPLES = """ +--- +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: RouterName + password: password + transport: cli + +--- +- name: Configure top level configuration + community.network.ordnance_config: + lines: hostname {{ inventory_hostname }} + provider: "{{ cli }}" + +- name: Configure interface settings + community.network.ordnance_config: + lines: + - description test interface + - ip address 172.31.1.1 255.255.255.0 + parents: interface Ethernet1 + provider: "{{ cli }}" + +- name: Configure bgp router + community.network.ordnance_config: + lines: + - neighbor 1.1.1.1 remote-as 1234 + - network 10.0.0.0/24 + parents: router bgp 65001 + provider: "{{ cli }}" + +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: Only when commands is specified. + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/ordnance_config.2016-07-16@22:28:34 +""" +import re +import time +import traceback + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import NetworkModule, NetworkError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Command +from ansible_collections.community.network.plugins.module_utils.network.ordnance.ordnance import get_config +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_native + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + if module.params['force']: + warnings.append('The force argument is deprecated, please use ' + 'match=none instead. This argument will be ' + 'removed in the future') + + +def extract_banners(config): + banners = {} + banner_cmds = re.findall(r'^banner (\w+)', config, re.M) + for cmd in banner_cmds: + regex = r'banner %s \^C(.+?)(?=\^C)' % cmd + match = re.search(regex, config, re.S) + if match: + key = 'banner %s' % cmd + banners[key] = match.group(1).strip() + + for cmd in banner_cmds: + regex = r'banner %s \^C(.+?)(?=\^C)' % cmd + match = re.search(regex, config, re.S) + if match: + config = config.replace(str(match.group(1)), '') + + config = re.sub(r'banner \w+ \^C\^C', '!! banner removed', config) + return (config, banners) + + +def diff_banners(want, have): + candidate = {} + for key, value in iteritems(want): + if value != have.get(key): + candidate[key] = value + return candidate + + +def load_banners(module, banners): + delimiter = module.params['multiline_delimiter'] + for key, value in iteritems(banners): + key += ' %s' % delimiter + for cmd in ['config terminal', key, value, delimiter, 'end']: + cmd += '\r' + module.connection.shell.shell.sendall(cmd) + time.sleep(1) + module.connection.shell.receive() + + +def get_config(module, result): + contents = module.params['config'] + if not contents: + defaults = module.params['defaults'] + contents = module.config.get_config(include_defaults=defaults) + + contents, banners = extract_banners(contents) + return NetworkConfig(indent=1, contents=contents), banners + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + banners = {} + + if module.params['src']: + src, banners = extract_banners(module.params['src']) + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate, banners + + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate, want_banners = get_candidate(module) + + if match != 'none': + config, have_banners = get_config(module, result) + path = module.params['parents'] + configobjs = candidate.difference(config, path=path, match=match, + replace=replace) + else: + configobjs = candidate.items + have_banners = {} + + banners = diff_banners(want_banners, have_banners) + + if configobjs or banners: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['lines']: + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['updates'] = commands + result['banners'] = banners + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + module.config(commands) + if banners: + load_banners(module, banners) + + result['changed'] = True + + if module.params['save']: + if not module.check_mode: + module.config.save_config() + result['changed'] = True + + +def main(): + """ main entry point for module execution + """ + + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + config=dict(), + defaults=dict(type='bool', default=False), + + backup=dict(type='bool', default=False), + save=dict(default=False, type='bool'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines'])] + + module = NetworkModule(argument_spec=argument_spec, + connect_on_load=False, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + if module.params['force'] is True: + module.params['match'] = 'none' + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + if module.params['backup']: + result['__backup__'] = module.config.get_config() + + try: + run(module, result) + except NetworkError as e: + module.disconnect() + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + module.disconnect() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_facts.py new file mode 100644 index 00000000..04d0776b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/ordnance_facts.py @@ -0,0 +1,288 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ordnance_facts +author: "Alexander Turner (@alexanderturner) " +short_description: Collect facts from Ordnance Virtual Routers over SSH +description: + - Collects a base set of device facts from an Ordnance Virtual + router over SSH. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +--- +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: RouterName + password: ordnance + transport: cli + +--- +- name: Collect all facts from the device + community.network.ordnance_facts: + gather_subset: all + provider: "{{ cli }}" + +- name: Collect only the config and default facts + community.network.ordnance_facts: + gather_subset: + - config + provider: "{{ cli }}" + +- name: Do not collect hardware facts + community.network.ordnance_facts: + gather_subset: + - "!hardware" + provider: "{{ cli }}" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the virtual router + returned: always + type: list + +# config +ansible_net_config: + description: The current active config from the virtual router + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the virtual router + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the virtual router + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the virtual router + returned: when interfaces is configured + type: dict +""" +import re +import traceback + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import NetworkModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip +from ansible.module_utils._text import to_native + + +class FactsBase(object): + + def __init__(self, module): + self.module = module + self.facts = dict() + self.failed_commands = list() + + def run(self, cmd): + try: + return self.module.cli(cmd)[0] + except Exception: + self.failed_commands.append(cmd) + + +class Config(FactsBase): + + def populate(self): + data = self.run('show running-config') + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + def populate(self): + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.run('show interfaces') + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.run('show ipv6 interface') + if data: + data = self.parse_interfaces(data) + self.populate_ipv6_interfaces(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['duplex'] = self.parse_duplex(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s+(.+), subnet', value, re.M) + subnets = re.findall(r', subnet is (.+)$', value, re.M) + for addr, subnet in zip(addresses, subnets): + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_duplex(self, data): + match = re.search(r'(\w+) Duplex', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = NetworkModule(argument_spec=spec, supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + failed_commands = list() + + try: + for inst in instances: + inst.populate() + failed_commands.extend(inst.failed_commands) + facts.update(inst.facts) + except Exception as exc: + module.fail_json(msg=to_native(exc), exception=traceback.format_exc()) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, failed_commands=failed_commands) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admin.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admin.py new file mode 100644 index 00000000..cd0e958b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admin.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_admin +short_description: Add or modify PAN-OS user accounts password. +description: + - PanOS module that allows changes to the user account passwords by doing + API calls to the Firewall using pan-api as the protocol. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + admin_username: + description: + - username for admin user + default: "admin" + admin_password: + description: + - password for admin user + required: true + role: + description: + - role for admin user + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# Set the password of user admin to "badpassword" +# Doesn't commit the candidate config + - name: Set admin password + community.network.panos_admin: + ip_address: "192.168.1.1" + password: "admin" + admin_username: admin + admin_password: "badpassword" + commit: False +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "okey dokey" +''' +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_ADMIN_XPATH = "/config/mgt-config/users/entry[@name='%s']" + + +def admin_exists(xapi, admin_username): + xapi.get(_ADMIN_XPATH % admin_username) + e = xapi.element_root.find('.//entry') + return e + + +def admin_set(xapi, module, admin_username, admin_password, role): + if admin_password is not None: + xapi.op(cmd='request password-hash password "%s"' % admin_password, + cmd_xml=True) + r = xapi.element_root + phash = r.find('.//phash').text + if role is not None: + rbval = "yes" + if role != "superuser" and role != 'superreader': + rbval = "" + + ea = admin_exists(xapi, admin_username) + if ea is not None: + # user exists + changed = False + + if role is not None: + rb = ea.find('.//role-based') + if rb is not None: + if rb[0].tag != role: + changed = True + xpath = _ADMIN_XPATH % admin_username + xpath += '/permissions/role-based/%s' % rb[0].tag + xapi.delete(xpath=xpath) + + xpath = _ADMIN_XPATH % admin_username + xpath += '/permissions/role-based' + xapi.set(xpath=xpath, + element='<%s>%s' % (role, rbval, role)) + + if admin_password is not None: + xapi.edit(xpath=_ADMIN_XPATH % admin_username + '/phash', + element='%s' % phash) + changed = True + + return changed + + # setup the non encrypted part of the monitor + exml = [] + + exml.append('%s' % phash) + exml.append('<%s>%s' + '' % (role, rbval, role)) + + exml = ''.join(exml) + # module.fail_json(msg=exml) + + xapi.set(xpath=_ADMIN_XPATH % admin_username, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(), + password=dict(no_log=True), + username=dict(default='admin'), + admin_username=dict(default='admin'), + admin_password=dict(no_log=True), + role=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + admin_username = module.params['admin_username'] + if admin_username is None: + module.fail_json(msg="admin_username is required") + admin_password = module.params['admin_password'] + role = module.params['role'] + commit = module.params['commit'] + + changed = admin_set(xapi, module, admin_username, admin_password, role) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admpwd.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admpwd.py new file mode 100644 index 00000000..c9c0ea74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_admpwd.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_admpwd +short_description: change admin password of PAN-OS device using SSH with SSH key +description: + - Change the admin password of PAN-OS via SSH using a SSH key for authentication. + - Useful for AWS instances where the first login should be done via SSH. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - paramiko +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device + required: true + username: + description: + - username for initial authentication + required: false + default: "admin" + key_filename: + description: + - filename of the SSH Key to use for authentication + required: true + newpassword: + description: + - password to configure for admin on the PAN-OS device + required: true +''' + +EXAMPLES = ''' +# Tries for 10 times to set the admin password of 192.168.1.1 to "badpassword" +# via SSH, authenticating using key /tmp/ssh.key +- name: Set admin password + community.network.panos_admpwd: + ip_address: "192.168.1.1" + username: "admin" + key_filename: "/tmp/ssh.key" + newpassword: "badpassword" + register: result + until: result is not failed + retries: 10 + delay: 30 +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "Last login: Fri Sep 16 11:09:20 2016 from 10.35.34.56.....Configuration committed successfully" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.paramiko import paramiko +import time +import sys + +_PROMPTBUFF = 4096 + + +def wait_with_timeout(module, shell, prompt, timeout=60): + now = time.time() + result = "" + while True: + if shell.recv_ready(): + result += shell.recv(_PROMPTBUFF) + endresult = result.strip() + if len(endresult) != 0 and endresult[-1] == prompt: + break + + if time.time() - now > timeout: + module.fail_json(msg="Timeout waiting for prompt") + + return result + + +def set_panwfw_password(module, ip_address, key_filename, newpassword, username): + stdout = "" + + ssh = paramiko.SSHClient() + + # add policy to accept all host keys, I haven't found + # a way to retrieve the instance SSH key fingerprint from AWS + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + ssh.connect(ip_address, username=username, key_filename=key_filename) + shell = ssh.invoke_shell() + + # wait for the shell to start + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # step into config mode + shell.send('configure\n') + # wait for the config prompt + buff = wait_with_timeout(module, shell, "#") + stdout += buff + + if module.check_mode: + # exit and close connection + shell.send('exit\n') + ssh.close() + return False, 'Connection test successful. Password left intact.' + + # set admin password + shell.send('set mgt-config users ' + username + ' password\n') + + # wait for the password prompt + buff = wait_with_timeout(module, shell, ":") + stdout += buff + + # enter password for the first time + shell.send(newpassword + '\n') + + # wait for the password prompt + buff = wait_with_timeout(module, shell, ":") + stdout += buff + + # enter password for the second time + shell.send(newpassword + '\n') + + # wait for the config mode prompt + buff = wait_with_timeout(module, shell, "#") + stdout += buff + + # commit ! + shell.send('commit\n') + + # wait for the prompt + buff = wait_with_timeout(module, shell, "#", 120) + stdout += buff + + if 'success' not in buff: + module.fail_json(msg="Error setting " + username + " password: " + stdout) + + # exit + shell.send('exit\n') + + ssh.close() + + return True, stdout + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + username=dict(default='admin'), + key_filename=dict(required=True), + newpassword=dict(no_log=True, required=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + if paramiko is None: + module.fail_json(msg='paramiko is required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + key_filename = module.params["key_filename"] + if not key_filename: + module.fail_json(msg="key_filename should be specified") + newpassword = module.params["newpassword"] + if not newpassword: + module.fail_json(msg="newpassword is required") + username = module.params['username'] + + try: + changed, stdout = set_panwfw_password(module, ip_address, key_filename, newpassword, username) + module.exit_json(changed=changed, stdout=stdout) + except Exception: + x = sys.exc_info()[1] + module.fail_json(msg=x) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_cert_gen_ssh.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_cert_gen_ssh.py new file mode 100644 index 00000000..61326154 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_cert_gen_ssh.py @@ -0,0 +1,192 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_cert_gen_ssh +short_description: generates a self-signed certificate using SSH protocol with SSH key +description: + - This module generates a self-signed certificate that can be used by GlobalProtect client, SSL connector, or + - otherwise. Root certificate must be preset on the system first. This module depends on paramiko for ssh. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - paramiko +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + key_filename: + description: + - Location of the filename that is used for the auth. Either I(key_filename) or I(password) is required. + required: true + password: + description: + - Password credentials to use for auth. Either I(key_filename) or I(password) is required. + required: true + cert_friendly_name: + description: + - Human friendly certificate name (not CN but just a friendly name). + required: true + cert_cn: + description: + - Certificate CN (common name) embedded in the certificate signature. + required: true + signed_by: + description: + - Undersigning authority (CA) that MUST already be presents on the device. + required: true + rsa_nbits: + description: + - Number of bits used by the RSA algorithm for the certificate generation. + default: "2048" +''' + +EXAMPLES = ''' +# Generates a new self-signed certificate using ssh +- name: Generate self signed certificate + community.network.panos_cert_gen_ssh: + ip_address: "192.168.1.1" + password: "paloalto" + cert_cn: "1.1.1.1" + cert_friendly_name: "test123" + signed_by: "root-ca" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.compat.paramiko import paramiko +import time + +_PROMPTBUFF = 4096 + + +def wait_with_timeout(module, shell, prompt, timeout=60): + now = time.time() + result = "" + while True: + if shell.recv_ready(): + result += shell.recv(_PROMPTBUFF) + endresult = result.strip() + if len(endresult) != 0 and endresult[-1] == prompt: + break + + if time.time() - now > timeout: + module.fail_json(msg="Timeout waiting for prompt") + + return result + + +def generate_cert(module, ip_address, key_filename, password, + cert_cn, cert_friendly_name, signed_by, rsa_nbits): + stdout = "" + + client = paramiko.SSHClient() + + # add policy to accept all host keys, I haven't found + # a way to retrieve the instance SSH key fingerprint from AWS + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + if not key_filename: + client.connect(ip_address, username="admin", password=password) + else: + client.connect(ip_address, username="admin", key_filename=key_filename) + + shell = client.invoke_shell() + # wait for the shell to start + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # generate self-signed certificate + if isinstance(cert_cn, list): + cert_cn = cert_cn[0] + cmd = 'request certificate generate signed-by {0} certificate-name {1} name {2} algorithm RSA rsa-nbits {3}\n'.format( + signed_by, cert_friendly_name, cert_cn, rsa_nbits) + shell.send(cmd) + + # wait for the shell to complete + buff = wait_with_timeout(module, shell, ">") + stdout += buff + + # exit + shell.send('exit\n') + + if 'Success' not in buff: + module.fail_json(msg="Error generating self signed certificate: " + stdout) + + client.close() + return stdout + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + key_filename=dict(), + password=dict(no_log=True), + cert_cn=dict(required=True), + cert_friendly_name=dict(required=True), + rsa_nbits=dict(default='2048'), + signed_by=dict(required=True) + + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['key_filename', 'password']]) + if paramiko is None: + module.fail_json(msg='paramiko is required for this module') + + ip_address = module.params["ip_address"] + key_filename = module.params["key_filename"] + password = module.params["password"] + cert_cn = module.params["cert_cn"] + cert_friendly_name = module.params["cert_friendly_name"] + signed_by = module.params["signed_by"] + rsa_nbits = module.params["rsa_nbits"] + + try: + stdout = generate_cert(module, + ip_address, + key_filename, + password, + cert_cn, + cert_friendly_name, + signed_by, + rsa_nbits) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_check.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_check.py new file mode 100644 index 00000000..0d007f3c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_check.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_check +short_description: check if PAN-OS device is ready for configuration +description: + - Check if PAN-OS device is ready for being configured (no pending jobs). + - The check could be done once or multiple times until the device is ready. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + timeout: + description: + - timeout of API calls + required: false + default: 0 + interval: + description: + - time waited between checks + required: false + default: 0 +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# single check on 192.168.1.1 with credentials admin/admin +- name: Check if ready + community.network.panos_check: + ip_address: "192.168.1.1" + password: "admin" + +# check for 10 times, every 30 seconds, if device 192.168.1.1 +# is ready, using credentials admin/admin +- name: Wait for reboot + community.network.panos_check: + ip_address: "192.168.1.1" + password: "admin" + register: result + until: result is not failed + retries: 10 + delay: 30 +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +import time + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def check_jobs(jobs, module): + job_check = False + for j in jobs: + status = j.find('.//status') + if status is None: + return False + if status.text != 'FIN': + return False + job_check = True + return job_check + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + timeout=dict(default=0, type='int'), + interval=dict(default=0, type='int') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + timeout = module.params['timeout'] + interval = module.params['interval'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password, + timeout=60 + ) + + checkpnt = time.time() + timeout + while True: + try: + xapi.op(cmd="show jobs all", cmd_xml=True) + except Exception: + pass + else: + jobs = xapi.element_root.findall('.//job') + if check_jobs(jobs, module): + module.exit_json(changed=True, msg="okey dokey") + + if time.time() > checkpnt: + break + + time.sleep(interval) + + module.fail_json(msg="Timeout") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_commit.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_commit.py new file mode 100644 index 00000000..e049d7e3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_commit.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2019, Tomi Raittinen +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_commit +short_description: commit firewall's candidate configuration +description: + - PanOS module that will commit firewall's candidate configuration on + - the device. The new configuration will become active immediately. +author: + - Luigi Mori (@jtschichold) + - Ivan Bojer (@ivanbojer) + - Tomi Raittinen (@traittinen) +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device. + required: true + password: + description: + - Password for authentication. If the value is not specified in the + task, the value of environment variable C(ANSIBLE_NET_PASSWORD) + will be used instead. + required: true + username: + description: + - Username for authentication. If the value is not specified in the + task, the value of environment variable C(ANSIBLE_NET_USERNAME) + will be used instead if defined. C(admin) will be used if nothing + above is defined. + default: admin + interval: + description: + - interval for checking commit job + default: 0.5 + timeout: + description: + - timeout for commit job + sync: + description: + - if commit should be synchronous + type: bool + default: 'yes' + description: + description: + - Commit description/comment + type: str + commit_changes_by: + description: + - Commit changes made by specified admin + type: list + commit_vsys: + description: + - Commit changes for specified VSYS + type: list +''' + +EXAMPLES = ''' +- name: Commit candidate config on 192.168.1.1 in sync mode + community.network.panos_commit: + ip_address: "192.168.1.1" + username: "admin" + password: "admin" +''' + +RETURN = ''' +panos_commit: + description: Information about commit job. + returned: always + type: complex + contains: + job_id: + description: Palo Alto job ID for the commit operation. Only returned if commit job is launched on device. + returned: always + type: str + sample: "139" + status_code: + description: Palo Alto API status code. Null if commit is successful. + returned: always + type: str + sample: 19 + status_detail: + description: Palo Alto API detailed status message. + returned: always + type: str + sample: Configuration committed successfully + status_text: + description: Palo Alto API status text. + returned: always + type: str + sample: success +''' + +from ansible.module_utils.basic import AnsibleModule, env_fallback +import xml.etree.ElementTree as etree + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True, type='str'), + password=dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), default="admin"), + interval=dict(default=0.5), + timeout=dict(), + sync=dict(type='bool', default=True), + description=dict(type='str'), + commit_changes_by=dict(type='list'), + commit_vsys=dict(type='list') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + + username = module.params['username'] + if not username: + module.fail_json(msg="username is required") + + interval = module.params['interval'] + timeout = module.params['timeout'] + sync = module.params['sync'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + cmd = "" + + description = module.params["description"] + if description: + cmd += "" + description + "" + + commit_changes_by = module.params["commit_changes_by"] + commit_vsys = module.params["commit_vsys"] + + if commit_changes_by or commit_vsys: + + cmd += "" + + if commit_changes_by: + cmd += "" + for admin in commit_changes_by: + cmd += "" + admin + "" + cmd += "" + + if commit_vsys: + cmd += "" + for vsys in commit_vsys: + cmd += "" + vsys + "" + cmd += "" + + cmd += "" + + cmd += "" + + xapi.commit( + cmd=cmd, + sync=sync, + interval=interval, + timeout=timeout + ) + + try: + result = xapi.xml_root().encode('utf-8') + root = etree.fromstring(result) + job_id = root.find('./result/job/id').text + except AttributeError: + job_id = None + + panos_commit_details = dict( + status_text=xapi.status, + status_code=xapi.status_code, + status_detail=xapi.status_detail, + job_id=job_id + ) + + if "Commit failed" in xapi.status_detail: + module.fail_json(msg=xapi.status_detail, panos_commit=panos_commit_details) + + if job_id: + module.exit_json(changed=True, msg="Commit successful.", panos_commit=panos_commit_details) + else: + module.exit_json(changed=False, msg="No changes to commit.", panos_commit=panos_commit_details) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag.py new file mode 100644 index 00000000..c824d69b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_dag +short_description: create a dynamic address group +description: + - Create a dynamic address group object in the firewall used for policy rules +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + dag_name: + description: + - name of the dynamic address group + required: true + dag_filter: + description: + - dynamic filter user by the dynamic address group + required: true + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Dag + community.network.panos_dag: + ip_address: "192.168.1.1" + password: "admin" + dag_name: "dag-1" + dag_filter: "'aws-tag.aws:cloudformation:logical-id.ServerInstance' and 'instanceState.running'" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_ADDRGROUP_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry[@name='vsys1']/address-group/entry[@name='%s']" + + +def addressgroup_exists(xapi, group_name): + xapi.get(_ADDRGROUP_XPATH % group_name) + e = xapi.element_root.find('.//entry') + if e is None: + return False + return True + + +def add_dag(xapi, dag_name, dag_filter): + if addressgroup_exists(xapi, dag_name): + return False + + # setup the non encrypted part of the monitor + exml = [] + + exml.append('') + exml.append('%s' % dag_filter) + exml.append('') + + exml = ''.join(exml) + xapi.set(xpath=_ADDRGROUP_XPATH % dag_name, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + dag_name=dict(required=True), + dag_filter=dict(required=True), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + dag_name = module.params['dag_name'] + dag_filter = module.params['dag_filter'] + commit = module.params['commit'] + + changed = add_dag(xapi, dag_name, dag_filter) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag_tags.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag_tags.py new file mode 100644 index 00000000..5e2d1223 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_dag_tags.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_dag_tags +short_description: Create tags for DAG's on PAN-OS devices. +description: + - Create the ip address to tag associations. Tags will in turn be used to create DAG's +author: "Vinay Venkataraghavan (@vinayvenkat)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is not supported. +options: + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + description: + description: + - The purpose / objective of the static Address Group + commit: + description: + - commit if changed + default: true + type: bool + devicegroup: + description: > + - Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama. + If device group is not define we assume that we are contacting Firewall. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + tag_names: + description: + - The list of the tags that will be added or removed from the IP address. + ip_to_register: + description: + - IP that will be registered with the given tag names. +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Create the tags to map IP addresses + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + ip_to_register: "{{ ip_to_register }}" + tag_names: "{{ tag_names }}" + description: "Tags to allow certain IP's to access various SaaS Applications" + operation: 'add' + tags: "adddagip" + +- name: List the IP address to tag mapping + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + tag_names: "{{ tag_names }}" + description: "List the IP address to tag mapping" + operation: 'list' + tags: "listdagip" + +- name: Unregister an IP address from a tag mapping + community.network.panos_dag_tags: + ip_address: "{{ ip_address }}" + password: "{{ password }}" + ip_to_register: "{{ ip_to_register }}" + tag_names: "{{ tag_names }}" + description: "Unregister IP address from tag mappings" + operation: 'delete' + tags: "deletedagip" +''' + +RETURN = ''' +# Default return values +''' + +try: + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + + from pan.xapi import PanXapiError + + HAS_LIB = True +except ImportError: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def register_ip_to_tag_map(device, ip_addresses, tag): + exc = None + try: + device.userid.register(ip_addresses, tag) + except PanXapiError as exc: + return False, exc + + return True, exc + + +def get_all_address_group_mapping(device): + exc = None + ret = None + try: + ret = device.userid.get_registered_ip() + except PanXapiError as exc: + return False, exc + + return ret, exc + + +def delete_address_from_mapping(device, ip_address, tags): + exc = None + try: + ret = device.userid.unregister(ip_address, tags) + except PanXapiError as exc: + return False, exc + + return True, exc + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + devicegroup=dict(default=None), + description=dict(default=None), + ip_to_register=dict(type='str', required=False), + tag_names=dict(type='list', required=True), + commit=dict(type='bool', default=True), + operation=dict(type='str', required=True) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + commit = module.params['commit'] + devicegroup = module.params['devicegroup'] + operation = module.params['operation'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + result = None + if operation == 'add': + result, exc = register_ip_to_tag_map(device, + ip_addresses=module.params.get('ip_to_register', None), + tag=module.params.get('tag_names', None) + ) + elif operation == 'list': + result, exc = get_all_address_group_mapping(device) + elif operation == 'delete': + result, exc = delete_address_from_mapping(device, + ip_address=module.params.get('ip_to_register', None), + tags=module.params.get('tag_names', []) + ) + else: + module.fail_json(msg="Unsupported option") + + if not result: + module.fail_json(msg=exc.message) + + if commit: + try: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg=result) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_import.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_import.py new file mode 100644 index 00000000..af7453e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_import.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_import +short_description: import file on PAN-OS devices +description: + - Import file on PAN-OS device +notes: + - API reference documentation can be read from the C(/api/) directory of your appliance + - Certificate validation is enabled by default as of Ansible 2.6. This may break existing playbooks but should be disabled with caution. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python + - requests + - requests_toolbelt +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + category: + description: + - Category of file uploaded. The default is software. + - See API > Import section of the API reference for category options. + default: software + file: + description: + - Location of the file to import into device. + url: + description: + - URL of the file that will be imported to device. + validate_certs: + description: + - If C(no), SSL certificates will not be validated. Disabling certificate validation is not recommended. + default: yes + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# import software image PanOS_vm-6.1.1 on 192.168.1.1 +- name: Import software image into PAN-OS + community.network.panos_import: + ip_address: 192.168.1.1 + username: admin + password: admin + file: /tmp/PanOS_vm-6.1.1 + category: software +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +import os.path +import xml.etree +import tempfile +import shutil +import os + +try: + import pan.xapi + import requests + import requests_toolbelt + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def import_file(xapi, module, ip_address, file_, category): + xapi.keygen() + + params = { + 'type': 'import', + 'category': category, + 'key': xapi.api_key + } + + filename = os.path.basename(file_) + + mef = requests_toolbelt.MultipartEncoder( + fields={ + 'file': (filename, open(file_, 'rb'), 'application/octet-stream') + } + ) + + r = requests.post( + 'https://' + ip_address + '/api/', + verify=module.params['validate_certs'], + params=params, + headers={'Content-Type': mef.content_type}, + data=mef + ) + + # if something goes wrong just raise an exception + r.raise_for_status() + + resp = xml.etree.ElementTree.fromstring(r.content) + + if resp.attrib['status'] == 'error': + module.fail_json(msg=r.content) + + return True, filename + + +def download_file(url): + r = requests.get(url, stream=True) + fo = tempfile.NamedTemporaryFile(prefix='ai', delete=False) + shutil.copyfileobj(r.raw, fo) + fo.close() + + return fo.name + + +def delete_file(path): + os.remove(path) + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + category=dict(default='software'), + file=dict(), + url=dict(), + validate_certs=dict(type='bool', default=True), + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_one_of=[['file', 'url']]) + if not HAS_LIB: + module.fail_json(msg='pan-python, requests, and requests_toolbelt are required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + file_ = module.params['file'] + url = module.params['url'] + + category = module.params['category'] + + # we can get file from URL or local storage + if url is not None: + file_ = download_file(url) + + try: + changed, filename = import_file(xapi, module, ip_address, file_, category) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + # cleanup and delete file if local + if url is not None: + delete_file(file_) + + module.exit_json(changed=changed, filename=filename, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_interface.py new file mode 100644 index 00000000..63dc366e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_interface.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_interface +short_description: configure data-port network interface for DHCP +description: + - Configure data-port (DP) network interface for DHCP. By default DP interfaces are static. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. +options: + if_name: + description: + - Name of the interface to configure. + required: true + zone_name: + description: > + Name of the zone for the interface. If the zone does not exist it is created but if the zone exists and + it is not of the layer3 type the operation will fail. + required: true + create_default_route: + description: + - Whether or not to add default route with router learned via DHCP. + default: "false" + type: bool + commit: + description: + - Commit if changed + default: true + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Enable DHCP client on ethernet1/1 in zone public + interface: + password: "admin" + ip_address: "192.168.1.1" + if_name: "ethernet1/1" + zone_name: "public" + create_default_route: "yes" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_IF_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/network/interface/ethernet/entry[@name='%s']" + +_ZONE_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry/zone/entry" +_ZONE_XPATH_QUERY = _ZONE_XPATH + "[network/layer3/member/text()='%s']" +_ZONE_XPATH_IF = _ZONE_XPATH + "[@name='%s']/network/layer3/member[text()='%s']" +_VR_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/network/virtual-router/entry" + + +def add_dhcp_if(xapi, if_name, zone_name, create_default_route): + if_xml = [ + '', + '', + '', + '%s', + '' + '' + '' + ] + cdr = 'yes' + if not create_default_route: + cdr = 'no' + if_xml = (''.join(if_xml)) % (if_name, cdr) + xapi.edit(xpath=_IF_XPATH % if_name, element=if_xml) + + xapi.set(xpath=_ZONE_XPATH + "[@name='%s']/network/layer3" % zone_name, + element='%s' % if_name) + xapi.set(xpath=_VR_XPATH + "[@name='default']/interface", + element='%s' % if_name) + + return True + + +def if_exists(xapi, if_name): + xpath = _IF_XPATH % if_name + xapi.get(xpath=xpath) + network = xapi.element_root.find('.//layer3') + return (network is not None) + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + if_name=dict(required=True), + zone_name=dict(required=True), + create_default_route=dict(type='bool', default=False), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + if_name = module.params['if_name'] + zone_name = module.params['zone_name'] + create_default_route = module.params['create_default_route'] + commit = module.params['commit'] + + ifexists = if_exists(xapi, if_name) + + if ifexists: + module.exit_json(changed=False, msg="interface exists, not changed") + + try: + changed = add_dhcp_if(xapi, if_name, zone_name, create_default_route) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_lic.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_lic.py new file mode 100644 index 00000000..6a40216d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_lic.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_lic +short_description: apply authcode to a device/instance +description: + - Apply an authcode to a device. + - The authcode should have been previously registered on the Palo Alto Networks support portal. + - The device should have Internet access. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + auth_code: + description: + - authcode to be applied + required: true + force: + description: + - whether to apply authcode even if device is already licensed + required: false + default: "false" + type: bool +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' + - hosts: localhost + connection: local + tasks: + - name: Fetch license + community.network.panos_lic: + ip_address: "192.168.1.1" + password: "paloalto" + auth_code: "IBADCODE" + register: result + - name: Display serialnumber (if already registered) + ansible.builtin.debug: + var: "{{result.serialnumber}}" +''' + +RETURN = ''' +serialnumber: + description: serialnumber of the device in case that it has been already registered + returned: success + type: str + sample: 007200004214 +''' + + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_serial(xapi, module): + xapi.op(cmd="show system info", cmd_xml=True) + r = xapi.element_root + serial = r.find('.//serial') + if serial is None: + module.fail_json(msg="No tag in show system info") + + serial = serial.text + + return serial + + +def apply_authcode(xapi, module, auth_code): + try: + xapi.op(cmd='request license fetch auth-code "%s"' % auth_code, + cmd_xml=True) + except pan.xapi.PanXapiError: + if hasattr(xapi, 'xml_document'): + if 'Successfully' in xapi.xml_document: + return + + if 'Invalid Auth Code' in xapi.xml_document: + module.fail_json(msg="Invalid Auth Code") + + raise + + return + + +def fetch_authcode(xapi, module): + try: + xapi.op(cmd='request license fetch', cmd_xml=True) + except pan.xapi.PanXapiError: + if hasattr(xapi, 'xml_document'): + if 'Successfully' in xapi.xml_document: + return + + if 'Invalid Auth Code' in xapi.xml_document: + module.fail_json(msg="Invalid Auth Code") + + raise + + return + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + auth_code=dict(), + username=dict(default='admin'), + force=dict(type='bool', default=False) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + auth_code = module.params["auth_code"] + force = module.params['force'] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + if not force: + serialnumber = get_serial(xapi, module) + if serialnumber != 'unknown': + return module.exit_json(changed=False, serialnumber=serialnumber) + if auth_code: + apply_authcode(xapi, module, auth_code) + else: + fetch_authcode(xapi, module) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_loadcfg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_loadcfg.py new file mode 100644 index 00000000..6e34e2d8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_loadcfg.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_loadcfg +short_description: load configuration on PAN-OS device +description: + - Load configuration on PAN-OS device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + file: + description: + - configuration file to load + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +# Import and load config file from URL + - name: Import configuration + panos_import: + ip_address: "192.168.1.1" + password: "admin" + url: "{{ConfigURL}}" + category: "configuration" + register: result + - name: Load configuration + community.network.panos_loadcfg: + ip_address: "192.168.1.1" + password: "admin" + file: "{{result.filename}}" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def load_cfgfile(xapi, module, ip_address, file_): + # load configuration file + cmd = '%s' %\ + file_ + + xapi.op(cmd=cmd) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + file=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + file_ = module.params['file'] + commit = module.params['commit'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + changed = load_cfgfile(xapi, module, ip_address, file_) + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_match_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_match_rule.py new file mode 100644 index 00000000..14d68b65 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_match_rule.py @@ -0,0 +1,388 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_match_rule +short_description: Test for match against a security rule on PAN-OS devices or Panorama management console. +description: + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. +author: "Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama NOT is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + rule_type: + description: + - Type of rule. Valid types are I(security) or I(nat). + required: true + choices: + - security + - nat + source_zone: + description: + - The source zone. + source_ip: + description: + - The source IP address. + required: true + source_port: + description: + - The source port. + source_user: + description: + - The source user or group. + to_interface: + description: + - The inbound interface in a NAT rule. + destination_zone: + description: + - The destination zone. + destination_ip: + description: + - The destination IP address. + destination_port: + description: + - The destination port. + application: + description: + - The application. + protocol: + description: + - The IP protocol number from 1 to 255. + category: + description: + - URL category + vsys_id: + description: + - ID of the VSYS object. + default: "vsys1" + required: true +''' + +EXAMPLES = ''' +- name: Check security rules for Google DNS + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'security' + source_ip: '10.0.0.0' + destination_ip: '8.8.8.8' + application: 'dns' + destination_port: '53' + protocol: '17' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check security rules inbound SSH with user match + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'security' + source_ip: '0.0.0.0' + source_user: 'mydomain\\jsmith' + destination_ip: '192.168.100.115' + destination_port: '22' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check NAT rules for source NAT + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'nat' + source_zone: 'Prod-DMZ' + source_ip: '10.10.118.50' + to_interface: 'ethernet1/2' + destination_zone: 'Internet' + destination_ip: '0.0.0.0' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check NAT rules for inbound web + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_type: 'nat' + source_zone: 'Internet' + source_ip: '0.0.0.0' + to_interface: 'ethernet1/1' + destination_zone: 'Prod DMZ' + destination_ip: '192.168.118.50' + destination_port: '80' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +- name: Check security rules for outbound POP3 in vsys4 + community.network.panos_match_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + vsys_id: 'vsys4' + rule_type: 'security' + source_ip: '10.0.0.0' + destination_ip: '4.3.2.1' + application: 'pop3' + destination_port: '110' + protocol: '6' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + from pan.xapi import PanXapiError + from pan.xapi import PanXapiError + from pandevice import base + from pandevice import policies + from pandevice import panorama + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def create_security_test(**kwargs): + security_test = 'test security-policy-match' + + # Add the source IP (required) + if kwargs['source_ip']: + security_test += ' source \"%s\"' % kwargs['source_ip'] + + # Add the source user (optional) + if kwargs['source_user']: + security_test += ' source-user \"%s\"' % kwargs['source_user'] + + # Add the destination IP (required) + if kwargs['destination_ip']: + security_test += ' destination \"%s\"' % kwargs['destination_ip'] + + # Add the application (optional) + if kwargs['application']: + security_test += ' application \"%s\"' % kwargs['application'] + + # Add the destination port (required) + if kwargs['destination_port']: + security_test += ' destination-port \"%s\"' % kwargs['destination_port'] + + # Add the IP protocol number (required) + if kwargs['protocol']: + security_test += ' protocol \"%s\"' % kwargs['protocol'] + + # Add the URL category (optional) + if kwargs['category']: + security_test += ' category \"%s\"' % kwargs['category'] + + # Return the resulting string + return security_test + + +def create_nat_test(**kwargs): + nat_test = 'test nat-policy-match' + + # Add the source zone (optional) + if kwargs['source_zone']: + nat_test += ' from \"%s\"' % kwargs['source_zone'] + + # Add the source IP (required) + if kwargs['source_ip']: + nat_test += ' source \"%s\"' % kwargs['source_ip'] + + # Add the source user (optional) + if kwargs['source_port']: + nat_test += ' source-port \"%s\"' % kwargs['source_port'] + + # Add inbound interface (optional) + if kwargs['to_interface']: + nat_test += ' to-interface \"%s\"' % kwargs['to_interface'] + + # Add the destination zone (optional) + if kwargs['destination_zone']: + nat_test += ' to \"%s\"' % kwargs['destination_zone'] + + # Add the destination IP (required) + if kwargs['destination_ip']: + nat_test += ' destination \"%s\"' % kwargs['destination_ip'] + + # Add the destination port (optional) + if kwargs['destination_port']: + nat_test += ' destination-port \"%s\"' % kwargs['destination_port'] + + # Add the IP protocol number (required) + if kwargs['protocol']: + nat_test += ' protocol \"%s\"' % kwargs['protocol'] + + # Return the resulting string + return nat_test + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + vsys_id=dict(default='vsys1'), + rule_type=dict(required=True, choices=['security', 'nat']), + source_zone=dict(default=None), + source_ip=dict(default=None), + source_user=dict(default=None), + source_port=dict(default=None, type=int), + to_interface=dict(default=None), + destination_zone=dict(default=None), + category=dict(default=None), + application=dict(default=None), + protocol=dict(default=None, type=int), + destination_ip=dict(default=None), + destination_port=dict(default=None, type=int) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + vsys_id = module.params['vsys_id'] + rule_type = module.params['rule_type'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_user = module.params['source_user'] + source_port = module.params['source_port'] + to_interface = module.params['to_interface'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + destination_port = module.params['destination_port'] + category = module.params['category'] + application = module.params['application'] + protocol = module.params['protocol'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # Fail the module if this is a Panorama instance + if isinstance(device, panorama.Panorama): + module.fail_json( + failed=1, + msg='Panorama is not supported.' + ) + + # Create and attach security and NAT rulebases. Then populate them. + sec_rule_base = nat_rule_base = policies.Rulebase() + device.add(sec_rule_base) + device.add(nat_rule_base) + policies.SecurityRule.refreshall(sec_rule_base) + policies.NatRule.refreshall(nat_rule_base) + + # Which action shall we take on the object? + if rule_type == 'security': + # Search for the object + test_string = create_security_test( + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + destination_port=destination_port, + application=application, + protocol=protocol, + category=category + ) + elif rule_type == 'nat': + test_string = create_nat_test( + source_zone=source_zone, + source_ip=source_ip, + source_port=source_port, + to_interface=to_interface, + destination_zone=destination_zone, + destination_ip=destination_ip, + destination_port=destination_port, + protocol=protocol + ) + + # Submit the op command with the appropriate test string + try: + response = device.op(cmd=test_string, vsys=vsys_id) + except PanXapiError as exc: + module.fail_json(msg=exc.message) + + if response.find('result/rules').__len__() == 1: + rule_name = response.find('result/rules/entry').text.split(';')[0] + elif rule_type == 'nat': + module.exit_json(msg='No matching NAT rule.') + else: + module.fail_json(msg='Rule match failed. Please check playbook syntax.') + + if rule_type == 'security': + rule_match = sec_rule_base.find(rule_name, policies.SecurityRule) + elif rule_type == 'nat': + rule_match = nat_rule_base.find(rule_name, policies.NatRule) + + # Print out the rule + module.exit_json( + stdout_lines=json.dumps(xmltodict.parse(rule_match.element_str()), indent=2), + msg='Rule matched' + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_mgtconfig.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_mgtconfig.py new file mode 100644 index 00000000..aedb2dc5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_mgtconfig.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_mgtconfig +short_description: configure management settings of device +description: + - Configure management settings of device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + dns_server_primary: + description: + - address of primary DNS server + dns_server_secondary: + description: + - address of secondary DNS server + panorama_primary: + description: + - address of primary Panorama server + panorama_secondary: + description: + - address of secondary Panorama server + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Set dns and panorama + community.network.panos_mgtconfig: + ip_address: "192.168.1.1" + password: "admin" + dns_server_primary: "1.1.1.1" + dns_server_secondary: "1.1.1.2" + panorama_primary: "1.1.1.3" + panorama_secondary: "1.1.1.4" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_XPATH_DNS_SERVERS = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/deviceconfig/system/dns-setting/servers" +_XPATH_PANORAMA_SERVERS = "/config" +\ + "/devices/entry[@name='localhost.localdomain']" +\ + "/deviceconfig/system" + + +def set_dns_server(xapi, new_dns_server, primary=True): + if primary: + tag = "primary" + else: + tag = "secondary" + xpath = _XPATH_DNS_SERVERS + "/" + tag + + # check the current element value + xapi.get(xpath) + val = xapi.element_root.find(".//" + tag) + if val is not None: + # element exists + val = val.text + if val == new_dns_server: + return False + + element = "<%(tag)s>%(value)s" %\ + dict(tag=tag, value=new_dns_server) + xapi.edit(xpath, element) + + return True + + +def set_panorama_server(xapi, new_panorama_server, primary=True): + if primary: + tag = "panorama-server" + else: + tag = "panorama-server-2" + xpath = _XPATH_PANORAMA_SERVERS + "/" + tag + + # check the current element value + xapi.get(xpath) + val = xapi.element_root.find(".//" + tag) + if val is not None: + # element exists + val = val.text + if val == new_panorama_server: + return False + + element = "<%(tag)s>%(value)s" %\ + dict(tag=tag, value=new_panorama_server) + xapi.edit(xpath, element) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + dns_server_primary=dict(), + dns_server_secondary=dict(), + panorama_primary=dict(), + panorama_secondary=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + dns_server_primary = module.params['dns_server_primary'] + dns_server_secondary = module.params['dns_server_secondary'] + panorama_primary = module.params['panorama_primary'] + panorama_secondary = module.params['panorama_secondary'] + commit = module.params['commit'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + changed = False + try: + if dns_server_primary is not None: + changed |= set_dns_server(xapi, dns_server_primary, primary=True) + if dns_server_secondary is not None: + changed |= set_dns_server(xapi, dns_server_secondary, primary=False) + if panorama_primary is not None: + changed |= set_panorama_server(xapi, panorama_primary, primary=True) + if panorama_secondary is not None: + changed |= set_panorama_server(xapi, panorama_secondary, primary=False) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_nat_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_nat_rule.py new file mode 100644 index 00000000..23f7ae7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_nat_rule.py @@ -0,0 +1,467 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, techbizdev +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: panos_nat_rule +short_description: create a policy NAT rule +description: > + - Create a policy nat rule. Keep in mind that we can either end up configuring source NAT, destination NAT, or + both. Instead of splitting it into two we will make a fair attempt to determine which one the user wants. +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + required: true + choices: + - add + - update + - delete + - find + devicegroup: + description: + - If Panorama, the device group to put this rule in. + rule_name: + description: + - name of the SNAT rule + required: true + description: + description: + - The description + source_zone: + description: + - list of source zones + required: true + destination_zone: + description: + - destination zone + required: true + source_ip: + description: + - list of source addresses + default: ["any"] + destination_ip: + description: + - list of destination addresses + default: ["any"] + service: + description: + - service + default: "any" + snat_type: + description: + - type of source translation + choices: + - static-ip + - dynamic-ip-and-port + - dynamic-ip + snat_address_type: + description: + - type of source translation. Supported values are I(translated-address)/I(translated-address). + default: 'interface-address' + choices: + - interface-address + - translated-address + snat_static_address: + description: + - Source NAT translated address. Used with Static-IP translation. + snat_dynamic_address: + description: + - Source NAT translated address. Used with Dynamic-IP and Dynamic-IP-and-Port. + snat_interface: + description: + - snat interface + snat_interface_address: + description: + - snat interface address + snat_bidirectional: + description: + - bidirectional flag + type: bool + default: 'no' + dnat_address: + description: + - dnat translated address + dnat_port: + description: + - dnat translated port + tag_name: + description: + - Tag for the NAT rule. + to_interface: + description: + - Destination interface. + default: 'any' + commit: + description: + - Commit configuration if changed. + type: bool + default: 'yes' +''' + +EXAMPLES = ''' +# Create a source and destination nat rule + - name: Create NAT SSH rule for 10.0.1.101 + community.network.panos_nat_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + rule_name: "Web SSH" + source_zone: ["external"] + destination_zone: "external" + source: ["any"] + destination: ["10.0.0.100"] + service: "service-tcp-221" + snat_type: "dynamic-ip-and-port" + snat_interface: "ethernet1/2" + dnat_address: "10.0.1.101" + dnat_port: "22" +''' + +RETURN = ''' +# Default return values +''' + +# import pydevd +# pydevd.settrace('localhost', port=60374, stdoutToServer=True, stderrToServer=True) +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, pandevice.firewall.Firewall): + rulebase = pandevice.policies.Rulebase() + device.add(rulebase) + elif isinstance(device, pandevice.panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.NatRule.refreshall(rulebase) + return rulebase + + +def find_rule(rulebase, rule_name): + # Search for the rule name + rule = rulebase.find(rule_name) + if rule: + return rule + else: + return False + + +def create_nat_rule(**kwargs): + nat_rule = policies.NatRule( + name=kwargs['rule_name'], + description=kwargs['description'], + fromzone=kwargs['source_zone'], + source=kwargs['source_ip'], + tozone=kwargs['destination_zone'], + destination=kwargs['destination_ip'], + service=kwargs['service'], + to_interface=kwargs['to_interface'], + nat_type=kwargs['nat_type'] + ) + + # Source translation: Static IP + if kwargs['snat_type'] in ['static-ip'] and kwargs['snat_static_address']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_static_translated_address = kwargs['snat_static_address'] + # Bi-directional flag set? + if kwargs['snat_bidirectional']: + nat_rule.source_translation_static_bi_directional = kwargs['snat_bidirectional'] + + # Source translation: Dynamic IP and port + elif kwargs['snat_type'] in ['dynamic-ip-and-port']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_address_type = kwargs['snat_address_type'] + # Interface address? + if kwargs['snat_interface']: + nat_rule.source_translation_interface = kwargs['snat_interface'] + # Interface IP? + if kwargs['snat_interface_address']: + nat_rule.source_translation_ip_address = kwargs['snat_interface_address'] + else: + nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address'] + + # Source translation: Dynamic IP + elif kwargs['snat_type'] in ['dynamic-ip']: + if kwargs['snat_dynamic_address']: + nat_rule.source_translation_type = kwargs['snat_type'] + nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address'] + else: + return False + + # Destination translation + if kwargs['dnat_address']: + nat_rule.destination_translated_address = kwargs['dnat_address'] + if kwargs['dnat_port']: + nat_rule.destination_translated_port = kwargs['dnat_port'] + + # Any tags? + if 'tag_name' in kwargs: + nat_rule.tag = kwargs['tag_name'] + + return nat_rule + + +def add_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.create() + return True + else: + return False + + +def update_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.apply() + return True + else: + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + username=dict(default='admin'), + password=dict(required=True, no_log=True), + api_key=dict(no_log=True), + operation=dict(required=True, choices=['add', 'update', 'delete', 'find']), + rule_name=dict(required=True), + description=dict(), + tag_name=dict(), + source_zone=dict(type='list'), + source_ip=dict(type='list', default=['any']), + destination_zone=dict(), + destination_ip=dict(type='list', default=['any']), + service=dict(default='any'), + to_interface=dict(default='any'), + snat_type=dict(choices=['static-ip', 'dynamic-ip-and-port', 'dynamic-ip']), + snat_address_type=dict(choices=['interface-address', 'translated-address'], default='interface-address'), + snat_static_address=dict(), + snat_dynamic_address=dict(type='list'), + snat_interface=dict(), + snat_interface_address=dict(), + snat_bidirectional=dict(type='bool', default=False), + dnat_address=dict(), + dnat_port=dict(), + devicegroup=dict(), + commit=dict(type='bool', default=True) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + rule_name = module.params['rule_name'] + description = module.params['description'] + tag_name = module.params['tag_name'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + service = module.params['service'] + to_interface = module.params['to_interface'] + nat_type = 'ipv4' + snat_type = module.params['snat_type'] + snat_address_type = module.params['snat_address_type'] + snat_static_address = module.params['snat_static_address'] + snat_dynamic_address = module.params['snat_dynamic_address'] + snat_interface = module.params['snat_interface'] + snat_interface_address = module.params['snat_interface_address'] + snat_bidirectional = module.params['snat_bidirectional'] + dnat_address = module.params['dnat_address'] + dnat_port = module.params['dnat_port'] + devicegroup = module.params['devicegroup'] + + commit = module.params['commit'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # Get the rulebase + rulebase = get_rulebase(device, dev_group) + + # Which action shall we take on the object? + if operation == "find": + # Search for the rule + match = find_rule(rulebase, rule_name) + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Rule matched' + ) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "delete": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, delete it + if match: + try: + match.delete() + if commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted.' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "add": + # Look for required parameters + if source_zone and destination_zone and nat_type: + pass + else: + module.fail_json(msg='Missing parameter. Required: source_zone, destination_zone, nat_type') + # Search for the rule. Fail if found. + match = find_rule(rulebase, rule_name) + if match: + module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name) + else: + try: + new_rule = create_nat_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + destination_ip=destination_ip, + service=service, + to_interface=to_interface, + nat_type=nat_type, + snat_type=snat_type, + snat_address_type=snat_address_type, + snat_static_address=snat_static_address, + snat_dynamic_address=snat_dynamic_address, + snat_interface=snat_interface, + snat_interface_address=snat_interface_address, + snat_bidirectional=snat_bidirectional, + dnat_address=dnat_address, + dnat_port=dnat_port + ) + changed = add_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully added.' % rule_name) + elif operation == 'update': + # Search for the rule. Update if found. + match = find_rule(rulebase, rule_name) + if match: + try: + new_rule = create_nat_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + destination_ip=destination_ip, + service=service, + to_interface=to_interface, + nat_type=nat_type, + snat_type=snat_type, + snat_address_type=snat_address_type, + snat_static_address=snat_static_address, + snat_dynamic_address=snat_dynamic_address, + snat_interface=snat_interface, + snat_interface_address=snat_interface_address, + snat_bidirectional=snat_bidirectional, + dnat_address=dnat_address, + dnat_port=dnat_port + ) + changed = update_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated.' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_object.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_object.py new file mode 100644 index 00000000..9c3628f2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_object.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_object +short_description: create/read/update/delete object in PAN-OS or Panorama +description: + - Policy objects form the match criteria for policy rules and many other functions in PAN-OS. These may include + address object, address groups, service objects, service groups, and tag. +author: "Bob Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device or Panorama management console being configured. + required: true + username: + description: + - Username credentials to use for authentication. + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The operation to be performed. Supported values are I(add)/I(delete)/I(find). + required: true + choices: + - add + - update + - delete + - find + addressobject: + description: + - The name of the address object. + address: + description: + - The IP address of the host or network in CIDR notation. + address_type: + description: + - The type of address object definition. Valid types are I(ip-netmask) and I(ip-range). + default: 'ip-netmask' + choices: + - ip-netmask + - ip-range + - fqdn + addressgroup: + description: + - A static group of address objects or dynamic address group. + static_value: + description: + - A group of address objects to be used in an addressgroup definition. + dynamic_value: + description: + - The filter match criteria to be used in a dynamic addressgroup definition. + serviceobject: + description: + - The name of the service object. + source_port: + description: + - The source port to be used in a service object definition. + destination_port: + description: + - The destination port to be used in a service object definition. + protocol: + description: + - The IP protocol to be used in a service object definition. Valid values are I(tcp) or I(udp). + choices: + - tcp + - udp + servicegroup: + description: + - A group of service objects. + services: + description: + - The group of service objects used in a servicegroup definition. + description: + description: + - The description of the object. + tag_name: + description: + - The name of an object or rule tag. + color: + description: > + - The color of the tag object. Valid values are I(red, green, blue, yellow, copper, orange, purple, gray, + light green, cyan, light gray, blue gray, lime, black, gold, and brown). + choices: + - red + - green + - blue + - yellow + - copper + - orange + - purple + - gray + - light green + - cyan + - light gray + - blue gray + - lime + - black + - gold + - brown + devicegroup: + description: > + - The name of the Panorama device group. The group must exist on Panorama. If device group is not defined it + is assumed that we are contacting a firewall. +''' + +EXAMPLES = ''' +- name: Search for shared address object + community.network.panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'find' + address: 'DevNet' + +- name: Create an address group in devicegroup using API key + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + addressgroup: 'Prod_DB_Svrs' + static_value: ['prod-db1', 'prod-db2', 'prod-db3'] + description: 'Production DMZ database servers' + tag_name: 'DMZ' + devicegroup: 'DMZ Firewalls' + +- name: Create a global service for TCP 3306 + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + serviceobject: 'mysql-3306' + destination_port: '3306' + protocol: 'tcp' + description: 'MySQL on tcp/3306' + +- name: Create a global tag + community.network.panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + tag_name: 'ProjectX' + color: 'yellow' + description: 'Associated with Project X' + +- name: Delete an address object from a devicegroup using API key + community.network.panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'delete' + addressobject: 'Win2K test' +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def find_object(device, dev_group, obj_name, obj_type): + # Get the firewall objects + obj_type.refreshall(device) + if isinstance(device, pandevice.firewall.Firewall): + addr = device.find(obj_name, obj_type) + return addr + elif isinstance(device, pandevice.panorama.Panorama): + addr = device.find(obj_name, obj_type) + if addr is None: + if dev_group: + device.add(dev_group) + obj_type.refreshall(dev_group) + addr = dev_group.find(obj_name, obj_type) + return addr + else: + return False + + +def create_object(**kwargs): + if kwargs['addressobject']: + newobject = objects.AddressObject( + name=kwargs['addressobject'], + value=kwargs['address'], + type=kwargs['address_type'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.type and newobject.value: + return newobject + else: + return False + elif kwargs['addressgroup']: + newobject = objects.AddressGroup( + name=kwargs['addressgroup'], + static_value=kwargs['static_value'], + dynamic_value=kwargs['dynamic_value'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.static_value or newobject.dynamic_value: + return newobject + else: + return False + elif kwargs['serviceobject']: + newobject = objects.ServiceObject( + name=kwargs['serviceobject'], + protocol=kwargs['protocol'], + source_port=kwargs['source_port'], + destination_port=kwargs['destination_port'], + tag=kwargs['tag_name'] + ) + if newobject.protocol and newobject.destination_port: + return newobject + else: + return False + elif kwargs['servicegroup']: + newobject = objects.ServiceGroup( + name=kwargs['servicegroup'], + value=kwargs['services'], + tag=kwargs['tag_name'] + ) + if newobject.value: + return newobject + else: + return False + elif kwargs['tag_name']: + newobject = objects.Tag( + name=kwargs['tag_name'], + color=kwargs['color'], + comments=kwargs['description'] + ) + if newobject.name: + return newobject + else: + return False + else: + return False + + +def add_object(device, dev_group, new_object): + if dev_group: + dev_group.add(new_object) + else: + device.add(new_object) + new_object.create() + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + operation=dict(required=True, choices=['add', 'update', 'delete', 'find']), + addressobject=dict(default=None), + addressgroup=dict(default=None), + serviceobject=dict(default=None), + servicegroup=dict(default=None), + address=dict(default=None), + address_type=dict(default='ip-netmask', choices=['ip-netmask', 'ip-range', 'fqdn']), + static_value=dict(type='list', default=None), + dynamic_value=dict(default=None), + protocol=dict(default=None, choices=['tcp', 'udp']), + source_port=dict(default=None), + destination_port=dict(default=None), + services=dict(type='list', default=None), + description=dict(default=None), + tag_name=dict(default=None), + color=dict(default=None, choices=['red', 'green', 'blue', 'yellow', 'copper', 'orange', 'purple', + 'gray', 'light green', 'cyan', 'light gray', 'blue gray', + 'lime', 'black', 'gold', 'brown']), + devicegroup=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']], + mutually_exclusive=[['addressobject', 'addressgroup', + 'serviceobject', 'servicegroup', + 'tag_name']] + ) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + addressobject = module.params['addressobject'] + addressgroup = module.params['addressgroup'] + serviceobject = module.params['serviceobject'] + servicegroup = module.params['servicegroup'] + address = module.params['address'] + address_type = module.params['address_type'] + static_value = module.params['static_value'] + dynamic_value = module.params['dynamic_value'] + protocol = module.params['protocol'] + source_port = module.params['source_port'] + destination_port = module.params['destination_port'] + services = module.params['services'] + description = module.params['description'] + tag_name = module.params['tag_name'] + color = module.params['color'] + devicegroup = module.params['devicegroup'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # What type of object are we talking about? + if addressobject: + obj_name = addressobject + obj_type = objects.AddressObject + elif addressgroup: + obj_name = addressgroup + obj_type = objects.AddressGroup + elif serviceobject: + obj_name = serviceobject + obj_type = objects.ServiceObject + elif servicegroup: + obj_name = servicegroup + obj_type = objects.ServiceGroup + elif tag_name: + obj_name = tag_name + obj_type = objects.Tag + else: + module.fail_json(msg='No object type defined!') + + # Which operation shall we perform on the object? + if operation == "find": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Object matched' + ) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "delete": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, delete it + if match: + try: + match.delete() + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Object \'%s\' successfully deleted' % obj_name) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "add": + # Search for the object. Fail if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + module.fail_json(msg='Object \'%s\' already exists. Use operation: \'update\' to change it.' % obj_name) + else: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Object \'%s\' successfully added' % obj_name) + elif operation == "update": + # Search for the object. Update if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Object \'%s\' successfully updated.' % obj_name) + else: + module.fail_json(msg='Object \'%s\' does not exist. Use operation: \'add\' to add it.' % obj_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_op.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_op.py new file mode 100644 index 00000000..0d027962 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_op.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_op +short_description: execute arbitrary OP commands on PANW devices (e.g. show interface all) +description: This module will allow user to pass and execute any supported OP command on the PANW device. +author: "Ivan Bojer (@ivanbojer)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is NOT supported. + - Panorama is NOT supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device or Panorama management console being configured. + required: true + username: + description: + - Username credentials to use for authentication. + required: false + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + cmd: + description: + - The OP command to be performed. + required: true +''' + +EXAMPLES = ''' +- name: Show list of all interfaces + community.network.panos_op: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + cmd: 'show interfaces all' + +- name: Show system info + community.network.panos_op: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + cmd: 'show system info' +''' + +RETURN = ''' +stdout: + description: output of the given OP command as JSON formatted string + returned: success + type: str + sample: "{system: {app-release-date: 2017/05/01 15:09:12}}" + +stdout_xml: + description: output of the given OP command as JSON formatted string + returned: success + type: str + sample: "fw2" +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + cmd=dict(required=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + cmd = module.params['cmd'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + changed = False + try: + xml_output = device.op(cmd, xml=True) + changed = True + except PanXapiError as exc: + if 'non NULL value' in exc.message: + # rewrap and call again + cmd_array = cmd.split() + cmd_array_len = len(cmd_array) + cmd_array[cmd_array_len - 1] = '\"' + cmd_array[cmd_array_len - 1] + '\"' + cmd2 = ' '.join(cmd_array) + try: + xml_output = device.op(cmd2, xml=True) + changed = True + except PanXapiError as exc: + module.fail_json(msg=exc.message) + + obj_dict = xmltodict.parse(xml_output) + json_output = json.dumps(obj_dict) + + module.exit_json(changed=changed, msg="Done", stdout=json_output, stdout_xml=xml_output) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_pg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_pg.py new file mode 100644 index 00000000..c8397e9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_pg.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_pg +short_description: create a security profiles group +description: + - Create a security profile group +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + pg_name: + description: + - name of the security profile group + required: true + data_filtering: + description: + - name of the data filtering profile + file_blocking: + description: + - name of the file blocking profile + spyware: + description: + - name of the spyware profile + url_filtering: + description: + - name of the url filtering profile + virus: + description: + - name of the anti-virus profile + vulnerability: + description: + - name of the vulnerability profile + wildfire: + description: + - name of the wildfire analysis profile + commit: + description: + - commit if changed + type: bool + default: 'yes' +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Setup security profile group + community.network.panos_pg: + ip_address: "192.168.1.1" + password: "admin" + username: "admin" + pg_name: "pg-default" + virus: "default" + spyware: "default" + vulnerability: "default" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +try: + import pan.xapi + from pan.xapi import PanXapiError + HAS_LIB = True +except ImportError: + HAS_LIB = False + +_PG_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ + "/vsys/entry[@name='vsys1']" +\ + "/profile-group/entry[@name='%s']" + + +def pg_exists(xapi, pg_name): + xapi.get(_PG_XPATH % pg_name) + e = xapi.element_root.find('.//entry') + if e is None: + return False + return True + + +def add_pg(xapi, pg_name, data_filtering, file_blocking, spyware, + url_filtering, virus, vulnerability, wildfire): + if pg_exists(xapi, pg_name): + return False + + exml = [] + + if data_filtering is not None: + exml.append('%s' % + data_filtering) + if file_blocking is not None: + exml.append('%s' % + file_blocking) + if spyware is not None: + exml.append('%s' % + spyware) + if url_filtering is not None: + exml.append('%s' % + url_filtering) + if virus is not None: + exml.append('%s' % + virus) + if vulnerability is not None: + exml.append('%s' % + vulnerability) + if wildfire is not None: + exml.append('%s' % + wildfire) + + exml = ''.join(exml) + xapi.set(xpath=_PG_XPATH % pg_name, element=exml) + + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + pg_name=dict(required=True), + data_filtering=dict(), + file_blocking=dict(), + spyware=dict(), + url_filtering=dict(), + virus=dict(), + vulnerability=dict(), + wildfire=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + pg_name = module.params['pg_name'] + data_filtering = module.params['data_filtering'] + file_blocking = module.params['file_blocking'] + spyware = module.params['spyware'] + url_filtering = module.params['url_filtering'] + virus = module.params['virus'] + vulnerability = module.params['vulnerability'] + wildfire = module.params['wildfire'] + commit = module.params['commit'] + + try: + changed = add_pg(xapi, pg_name, data_filtering, file_blocking, + spyware, url_filtering, virus, vulnerability, wildfire) + + if changed and commit: + xapi.commit(cmd="", sync=True, interval=1) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=changed, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_query_rules.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_query_rules.py new file mode 100644 index 00000000..e7ebd769 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_query_rules.py @@ -0,0 +1,494 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_query_rules +short_description: PANOS module that allows search for security rules in PANW NGFW devices. +description: > + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. The + policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the + traffic is applied, the more specific rules must precede the more general ones. +author: "Bob Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtains from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS firewall or Panorama management console being queried. + required: true + username: + description: + - Username credentials to use for authentication. + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + application: + description: + - Name of the application or application group to be queried. + source_zone: + description: + - Name of the source security zone to be queried. + source_ip: + description: + - The source IP address to be queried. + source_port: + description: + - The source port to be queried. + destination_zone: + description: + - Name of the destination security zone to be queried. + destination_ip: + description: + - The destination IP address to be queried. + destination_port: + description: + - The destination port to be queried. + protocol: + description: + - The protocol used to be queried. Must be either I(tcp) or I(udp). + choices: + - tcp + - udp + tag_name: + description: + - Name of the rule tag to be queried. + devicegroup: + description: + - The Panorama device group in which to conduct the query. +''' + +EXAMPLES = ''' +- name: Search for rules with tcp/3306 + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + source_zone: 'DevNet' + destination_zone: 'DevVPC' + destination_port: '3306' + protocol: 'tcp' + +- name: Search devicegroup for inbound rules to dmz host + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + destination_zone: 'DMZ' + destination_ip: '10.100.42.18' + address: 'DeviceGroupA' + +- name: Search for rules containing a specified rule tag + community.network.panos_query_rules: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + tag_name: 'ProjectX' +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import ipaddress + import xmltodict + import json + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, firewall.Firewall): + rulebase = policies.Rulebase() + device.add(rulebase) + elif isinstance(device, panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.SecurityRule.refreshall(rulebase) + return rulebase + + +def get_object(device, dev_group, obj_name): + # Search global address objects + match = device.find(obj_name, objects.AddressObject) + if match: + return match + + # Search global address groups + match = device.find(obj_name, objects.AddressGroup) + if match: + return match + + # Search Panorama device group + if isinstance(device, pandevice.panorama.Panorama): + # Search device group address objects + match = dev_group.find(obj_name, objects.AddressObject) + if match: + return match + + # Search device group address groups + match = dev_group.find(obj_name, objects.AddressGroup) + if match: + return match + return False + + +def addr_in_obj(addr, obj): + ip = ipaddress.ip_address(addr) + # Process address objects + if isinstance(obj, objects.AddressObject): + if obj.type == 'ip-netmask': + net = ipaddress.ip_network(obj.value) + if ip in net: + return True + if obj.type == 'ip-range': + ip_range = obj.value.split('-') + lower = ipaddress.ip_address(ip_range[0]) + upper = ipaddress.ip_address(ip_range[1]) + if lower < ip < upper: + return True + return False + + +def get_services(device, dev_group, svc_list, obj_list): + for svc in svc_list: + + # Search global address objects + global_obj_match = device.find(svc, objects.ServiceObject) + if global_obj_match: + obj_list.append(global_obj_match) + + # Search global address groups + global_grp_match = device.find(svc, objects.ServiceGroup) + if global_grp_match: + get_services(device, dev_group, global_grp_match.value, obj_list) + + # Search Panorama device group + if isinstance(device, pandevice.panorama.Panorama): + + # Search device group address objects + dg_obj_match = dev_group.find(svc, objects.ServiceObject) + if dg_obj_match: + obj_list.append(dg_obj_match) + + # Search device group address groups + dg_grp_match = dev_group.find(svc, objects.ServiceGroup) + if dg_grp_match: + get_services(device, dev_group, dg_grp_match.value, obj_list) + + return obj_list + + +def port_in_svc(orientation, port, protocol, obj): + # Process address objects + if orientation == 'source': + for x in obj.source_port.split(','): + if '-' in x: + port_range = x.split('-') + lower = int(port_range[0]) + upper = int(port_range[1]) + if (lower <= int(port) <= upper) and (obj.protocol == protocol): + return True + else: + if port == x and obj.protocol == protocol: + return True + elif orientation == 'destination': + for x in obj.destination_port.split(','): + if '-' in x: + port_range = x.split('-') + lower = int(port_range[0]) + upper = int(port_range[1]) + if (lower <= int(port) <= upper) and (obj.protocol == protocol): + return True + else: + if port == x and obj.protocol == protocol: + return True + return False + + +def get_tag(device, dev_group, tag_name): + # Search global address objects + match = device.find(tag_name, objects.Tag) + if match: + return match + # Search Panorama device group + if isinstance(device, panorama.Panorama): + # Search device group address objects + match = dev_group.find(tag_name, objects.Tag) + if match: + return match + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + application=dict(default=None), + source_zone=dict(default=None), + destination_zone=dict(default=None), + source_ip=dict(default=None), + destination_ip=dict(default=None), + source_port=dict(default=None), + destination_port=dict(default=None), + protocol=dict(default=None, choices=['tcp', 'udp']), + tag_name=dict(default=None), + devicegroup=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']] + ) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + application = module.params['application'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_port = module.params['source_port'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + destination_port = module.params['destination_port'] + protocol = module.params['protocol'] + tag_name = module.params['tag_name'] + devicegroup = module.params['devicegroup'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # Grab the global objects + objects.AddressObject.refreshall(device) + objects.AddressGroup.refreshall(device) + objects.ServiceObject.refreshall(device) + objects.ServiceGroup.refreshall(device) + objects.Tag.refreshall(device) + + # If Panorama, validate the devicegroup and grab the devicegroup objects + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + objects.AddressObject.refreshall(dev_group) + objects.AddressGroup.refreshall(dev_group) + objects.ServiceObject.refreshall(dev_group) + objects.ServiceGroup.refreshall(dev_group) + objects.Tag.refreshall(dev_group) + else: + module.fail_json( + failed=1, + msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup + ) + + # Build the rulebase and produce list + rulebase = get_rulebase(device, dev_group) + rulelist = rulebase.children + hitbase = policies.Rulebase() + loose_match = True + + # Process each rule + for rule in rulelist: + hitlist = [] + + if source_zone: + source_zone_match = False + if loose_match and 'any' in rule.fromzone: + source_zone_match = True + else: + for object_string in rule.fromzone: + if object_string == source_zone: + source_zone_match = True + hitlist.append(source_zone_match) + + if destination_zone: + destination_zone_match = False + if loose_match and 'any' in rule.tozone: + destination_zone_match = True + else: + for object_string in rule.tozone: + if object_string == destination_zone: + destination_zone_match = True + hitlist.append(destination_zone_match) + + if source_ip: + source_ip_match = False + if loose_match and 'any' in rule.source: + source_ip_match = True + else: + for object_string in rule.source: + # Get a valid AddressObject or AddressGroup + obj = get_object(device, dev_group, object_string) + # Otherwise the object_string is not an object and should be handled differently + if obj is False: + if '-' in object_string: + obj = ipaddress.ip_address(source_ip) + source_range = object_string.split('-') + source_lower = ipaddress.ip_address(source_range[0]) + source_upper = ipaddress.ip_address(source_range[1]) + if source_lower <= obj <= source_upper: + source_ip_match = True + else: + if source_ip == object_string: + source_ip_match = True + if isinstance(obj, objects.AddressObject) and addr_in_obj(source_ip, obj): + source_ip_match = True + elif isinstance(obj, objects.AddressGroup) and obj.static_value: + for member_string in obj.static_value: + member = get_object(device, dev_group, member_string) + if addr_in_obj(source_ip, member): + source_ip_match = True + hitlist.append(source_ip_match) + + if destination_ip: + destination_ip_match = False + if loose_match and 'any' in rule.destination: + destination_ip_match = True + else: + for object_string in rule.destination: + # Get a valid AddressObject or AddressGroup + obj = get_object(device, dev_group, object_string) + # Otherwise the object_string is not an object and should be handled differently + if obj is False: + if '-' in object_string: + obj = ipaddress.ip_address(destination_ip) + destination_range = object_string.split('-') + destination_lower = ipaddress.ip_address(destination_range[0]) + destination_upper = ipaddress.ip_address(destination_range[1]) + if destination_lower <= obj <= destination_upper: + destination_ip_match = True + else: + if destination_ip == object_string: + destination_ip_match = True + if isinstance(obj, objects.AddressObject) and addr_in_obj(destination_ip, obj): + destination_ip_match = True + elif isinstance(obj, objects.AddressGroup) and obj.static_value: + for member_string in obj.static_value: + member = get_object(device, dev_group, member_string) + if addr_in_obj(destination_ip, member): + destination_ip_match = True + hitlist.append(destination_ip_match) + + if source_port: + source_port_match = False + orientation = 'source' + if loose_match and (rule.service[0] == 'any'): + source_port_match = True + elif rule.service[0] == 'application-default': + source_port_match = False # Fix this once apps are supported + else: + service_list = [] + service_list = get_services(device, dev_group, rule.service, service_list) + for obj in service_list: + if port_in_svc(orientation, source_port, protocol, obj): + source_port_match = True + break + hitlist.append(source_port_match) + + if destination_port: + destination_port_match = False + orientation = 'destination' + if loose_match and (rule.service[0] == 'any'): + destination_port_match = True + elif rule.service[0] == 'application-default': + destination_port_match = False # Fix this once apps are supported + else: + service_list = [] + service_list = get_services(device, dev_group, rule.service, service_list) + for obj in service_list: + if port_in_svc(orientation, destination_port, protocol, obj): + destination_port_match = True + break + hitlist.append(destination_port_match) + + if tag_name: + tag_match = False + if rule.tag: + for object_string in rule.tag: + obj = get_tag(device, dev_group, object_string) + if obj and (obj.name == tag_name): + tag_match = True + hitlist.append(tag_match) + + # Add to hit rulebase + if False not in hitlist: + hitbase.add(rule) + + # Dump the hit rulebase + if hitbase.children: + output_string = xmltodict.parse(hitbase.element_str()) + module.exit_json( + stdout_lines=json.dumps(output_string, indent=2), + msg='%s of %s rules matched' % (hitbase.children.__len__(), rulebase.children.__len__()) + ) + else: + module.fail_json(msg='No matching rules found.') + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_restart.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_restart.py new file mode 100644 index 00000000..af2d835b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_restart.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_restart +short_description: restart a device +description: + - Restart a device +author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" +requirements: + - pan-python +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Restart a device + community.network.panos_restart: + ip_address: "192.168.1.1" + username: "admin" + password: "admin" +''' + +RETURN = ''' +status: + description: success status + returned: success + type: str + sample: "okey dokey" +''' + +import sys +import traceback + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +def main(): + argument_spec = dict( + ip_address=dict(), + password=dict(no_log=True), + username=dict(default='admin') + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_LIB: + module.fail_json(msg='pan-python required for this module') + + ip_address = module.params["ip_address"] + if not ip_address: + module.fail_json(msg="ip_address should be specified") + password = module.params["password"] + if not password: + module.fail_json(msg="password is required") + username = module.params['username'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password + ) + + try: + xapi.op(cmd="") + except Exception as e: + if 'succeeded' in to_native(e): + module.exit_json(changed=True, msg=to_native(e)) + else: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + module.exit_json(changed=True, msg="okey dokey") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_sag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_sag.py new file mode 100644 index 00000000..d2a899a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_sag.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_sag +short_description: Create a static address group. +description: + - Create a static address group object in the firewall used for policy rules. +author: "Vinay Venkataraghavan (@vinayvenkat)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +options: + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + sag_name: + description: + - name of the dynamic address group + required: true + sag_match_filter: + description: + - Static filter user by the address group + type: list + devicegroup: + description: > + - The name of the Panorama device group. The group must exist on Panorama. If device group is not defined + it is assumed that we are contacting a firewall. + description: + description: + - The purpose / objective of the static Address Group + tags: + description: + - Tags to be associated with the address group + commit: + description: + - commit if changed + type: bool + default: 'yes' + operation: + description: + - The operation to perform Supported values are I(add)/I(list)/I(delete). + required: true + choices: + - add + - list + - delete +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' +- name: Sag + community.network.panos_sag: + ip_address: "192.168.1.1" + password: "admin" + sag_name: "sag-1" + static_value: ['test-addresses', ] + description: "A description for the static address group" + tags: ["tags to be associated with the group", ] +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def find_object(device, dev_group, obj_name, obj_type): + # Get the firewall objects + obj_type.refreshall(device) + if isinstance(device, firewall.Firewall): + addr = device.find(obj_name, obj_type) + return addr + elif isinstance(device, panorama.Panorama): + addr = device.find(obj_name, obj_type) + if addr is None: + if dev_group: + device.add(dev_group) + obj_type.refreshall(dev_group) + addr = dev_group.find(obj_name, obj_type) + return addr + else: + return False + + +def create_address_group_object(**kwargs): + """ + Create an Address object + + :param kwargs: key word arguments to instantiate AddressGroup object + @return False or ```objects.AddressObject``` + """ + ad_object = objects.AddressGroup( + name=kwargs['address_gp_name'], + static_value=kwargs['sag_match_filter'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if ad_object.static_value or ad_object.dynamic_value: + return ad_object + else: + return None + + +def add_address_group(device, dev_group, ag_object): + """ + Create a new dynamic address group object on the + PAN FW. + + :param device: Firewall Handle + :param dev_group: Panorama device group + :param ag_object: Address group object + """ + + if dev_group: + dev_group.add(ag_object) + else: + device.add(ag_object) + + exc = None + try: + ag_object.create() + except Exception as exc: + return False, exc + + return True, exc + + +def delete_address_group(device, dev_group, obj_name): + """ + + :param device: + :param dev_group: + :param obj_name: + :return: + """ + static_obj = find_object(device, dev_group, obj_name, objects.AddressGroup) + # If found, delete it + + if static_obj: + try: + static_obj.delete() + except Exception as exc: + return False, exc + return True, None + else: + return False, None + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + sag_match_filter=dict(type='list', required=False), + sag_name=dict(required=True), + commit=dict(type='bool', default=True), + devicegroup=dict(default=None), + description=dict(default=None), + tags=dict(type='list', default=[]), + operation=dict(type='str', required=True, choices=['add', 'list', 'delete']) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + + ag_object = create_address_group_object(address_gp_name=module.params.get('sag_name', None), + sag_match_filter=module.params.get('sag_match_filter', None), + description=module.params.get('description', None), + tag_name=module.params.get('tags', None) + ) + commit = module.params['commit'] + + devicegroup = module.params['devicegroup'] + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + if operation == 'add': + result, exc = add_address_group(device, dev_group, ag_object) + + if result and commit: + try: + device.commit(sync=True) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + elif operation == 'delete': + obj_name = module.params.get('sag_name', None) + result, exc = delete_address_group(device, dev_group, obj_name) + if not result and exc: + module.fail_json(msg=exc.message) + elif not result: + module.fail_json(msg="Specified object not found.") + else: + module.fail_json(changed=False, msg="Unsupported option.") + + module.exit_json(changed=True, msg="Address Group Operation Completed.") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_security_rule.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_security_rule.py new file mode 100644 index 00000000..25167d70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_security_rule.py @@ -0,0 +1,572 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, techbizdev +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: panos_security_rule +short_description: Create security rule policy on PAN-OS devices or Panorama management console. +description: + - Security policies allow you to enforce rules and take action, and can be as general or specific as needed. + The policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the traffic is applied, + the more specific rules must precede the more general ones. +author: "Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)" +requirements: + - pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/) + - pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/) + - xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/) +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device being configured. + required: true + username: + description: + - Username credentials to use for auth unless I(api_key) is set. + default: "admin" + password: + description: + - Password credentials to use for auth unless I(api_key) is set. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete). + default: 'add' + choices: + - add + - update + - delete + - find + category: + description: + - The category. + type: list + default: ['any'] + rule_name: + description: + - Name of the security rule. + required: true + rule_type: + description: + - Type of security rule (version 6.1 of PanOS and above). + default: "universal" + description: + description: + - Description for the security rule. + tag_name: + description: + - Administrative tags that can be added to the rule. Note, tags must be already defined. + source_zone: + description: + - List of source zones. + default: "any" + destination_zone: + description: + - List of destination zones. + default: "any" + source_ip: + description: + - List of source addresses. + default: "any" + source_user: + description: + - Use users to enforce policy for individual users or a group of users. + default: "any" + hip_profiles: + description: > + - If you are using GlobalProtect with host information profile (HIP) enabled, you can also base the policy + on information collected by GlobalProtect. For example, the user access level can be determined HIP that + notifies the firewall about the user's local configuration. + default: "any" + destination_ip: + description: + - List of destination addresses. + default: "any" + application: + description: + - List of applications. + default: "any" + service: + description: + - List of services. + default: "application-default" + log_start: + description: + - Whether to log at session start. + type: bool + default: false + log_end: + description: + - Whether to log at session end. + default: true + type: bool + action: + description: + - Action to apply once rules maches. + default: "allow" + group_profile: + description: > + - Security profile group that is already defined in the system. This property supersedes antivirus, + vulnerability, spyware, url_filtering, file_blocking, data_filtering, and wildfire_analysis properties. + antivirus: + description: + - Name of the already defined antivirus profile. + vulnerability: + description: + - Name of the already defined vulnerability profile. + spyware: + description: + - Name of the already defined spyware profile. + url_filtering: + description: + - Name of the already defined url_filtering profile. + file_blocking: + description: + - Name of the already defined file_blocking profile. + data_filtering: + description: + - Name of the already defined data_filtering profile. + wildfire_analysis: + description: + - Name of the already defined wildfire_analysis profile. + devicegroup: + description: > + - Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama. + If device group is not define we assume that we are contacting Firewall. + commit: + description: + - Commit configuration if changed. + type: bool + default: 'yes' +''' + +EXAMPLES = ''' +- name: Add an SSH inbound rule to devicegroup + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + rule_name: 'SSH permit' + description: 'SSH rule test' + tag_name: ['ProjectX'] + source_zone: ['public'] + destination_zone: ['private'] + source_ip: ['any'] + source_user: ['any'] + destination_ip: ['1.1.1.1'] + category: ['any'] + application: ['ssh'] + service: ['application-default'] + hip_profiles: ['any'] + action: 'allow' + devicegroup: 'Cloud Edge' + +- name: Add a rule to allow HTTP multimedia only from CDNs + community.network.panos_security_rule: + ip_address: '10.5.172.91' + username: 'admin' + password: 'paloalto' + operation: 'add' + rule_name: 'HTTP Multimedia' + description: 'Allow HTTP multimedia only to host at 1.1.1.1' + source_zone: ['public'] + destination_zone: ['private'] + source_ip: ['any'] + source_user: ['any'] + destination_ip: ['1.1.1.1'] + category: ['content-delivery-networks'] + application: ['http-video', 'http-audio'] + service: ['service-http', 'service-https'] + hip_profiles: ['any'] + action: 'allow' + +- name: Add a more complex rule that uses security profiles + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + rule_name: 'Allow HTTP w profile' + log_start: false + log_end: true + action: 'allow' + antivirus: 'default' + vulnerability: 'default' + spyware: 'default' + url_filtering: 'default' + wildfire_analysis: 'default' + +- name: Delete a devicegroup security rule + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'delete' + rule_name: 'Allow telnet' + devicegroup: 'DC Firewalls' + +- name: Find a specific security rule + community.network.panos_security_rule: + ip_address: '{{ ip_address }}' + password: '{{ password }}' + operation: 'find' + rule_name: 'Allow RDP to DCs' + register: result +- ansible.builtin.debug: msg='{{result.stdout_lines}}' + +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + from pandevice import policies + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def get_rulebase(device, devicegroup): + # Build the rulebase + if isinstance(device, pandevice.firewall.Firewall): + rulebase = pandevice.policies.Rulebase() + device.add(rulebase) + elif isinstance(device, pandevice.panorama.Panorama): + dg = panorama.DeviceGroup(devicegroup) + device.add(dg) + rulebase = policies.PreRulebase() + dg.add(rulebase) + else: + return False + policies.SecurityRule.refreshall(rulebase) + return rulebase + + +def find_rule(rulebase, rule_name): + # Search for the rule name + rule = rulebase.find(rule_name) + if rule: + return rule + else: + return False + + +def rule_is_match(propose_rule, current_rule): + + match_check = ['name', 'description', 'group_profile', 'antivirus', 'vulnerability', + 'spyware', 'url_filtering', 'file_blocking', 'data_filtering', + 'wildfire_analysis', 'type', 'action', 'tag', 'log_start', 'log_end'] + list_check = ['tozone', 'fromzone', 'source', 'source_user', 'destination', 'category', + 'application', 'service', 'hip_profiles'] + + for check in match_check: + propose_check = getattr(propose_rule, check, None) + current_check = getattr(current_rule, check, None) + if propose_check != current_check: + return False + for check in list_check: + propose_check = getattr(propose_rule, check, []) + current_check = getattr(current_rule, check, []) + if set(propose_check) != set(current_check): + return False + return True + + +def create_security_rule(**kwargs): + security_rule = policies.SecurityRule( + name=kwargs['rule_name'], + description=kwargs['description'], + fromzone=kwargs['source_zone'], + source=kwargs['source_ip'], + source_user=kwargs['source_user'], + hip_profiles=kwargs['hip_profiles'], + tozone=kwargs['destination_zone'], + destination=kwargs['destination_ip'], + application=kwargs['application'], + service=kwargs['service'], + category=kwargs['category'], + log_start=kwargs['log_start'], + log_end=kwargs['log_end'], + action=kwargs['action'], + type=kwargs['rule_type'] + ) + + if 'tag_name' in kwargs: + security_rule.tag = kwargs['tag_name'] + + # profile settings + if 'group_profile' in kwargs: + security_rule.group = kwargs['group_profile'] + else: + if 'antivirus' in kwargs: + security_rule.virus = kwargs['antivirus'] + if 'vulnerability' in kwargs: + security_rule.vulnerability = kwargs['vulnerability'] + if 'spyware' in kwargs: + security_rule.spyware = kwargs['spyware'] + if 'url_filtering' in kwargs: + security_rule.url_filtering = kwargs['url_filtering'] + if 'file_blocking' in kwargs: + security_rule.file_blocking = kwargs['file_blocking'] + if 'data_filtering' in kwargs: + security_rule.data_filtering = kwargs['data_filtering'] + if 'wildfire_analysis' in kwargs: + security_rule.wildfire_analysis = kwargs['wildfire_analysis'] + return security_rule + + +def add_rule(rulebase, sec_rule): + if rulebase: + rulebase.add(sec_rule) + sec_rule.create() + return True + else: + return False + + +def update_rule(rulebase, nat_rule): + if rulebase: + rulebase.add(nat_rule) + nat_rule.apply() + return True + else: + return False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + operation=dict(default='add', choices=['add', 'update', 'delete', 'find']), + rule_name=dict(required=True), + description=dict(default=''), + tag_name=dict(type='list'), + destination_zone=dict(type='list', default=['any']), + source_zone=dict(type='list', default=['any']), + source_ip=dict(type='list', default=["any"]), + source_user=dict(type='list', default=['any']), + destination_ip=dict(type='list', default=["any"]), + category=dict(type='list', default=['any']), + application=dict(type='list', default=['any']), + service=dict(type='list', default=['application-default']), + hip_profiles=dict(type='list', default=['any']), + group_profile=dict(), + antivirus=dict(), + vulnerability=dict(), + spyware=dict(), + url_filtering=dict(), + file_blocking=dict(), + data_filtering=dict(), + wildfire_analysis=dict(), + log_start=dict(type='bool', default=False), + log_end=dict(type='bool', default=True), + rule_type=dict(default='universal'), + action=dict(default='allow'), + devicegroup=dict(), + commit=dict(type='bool', default=True) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']]) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + rule_name = module.params['rule_name'] + description = module.params['description'] + tag_name = module.params['tag_name'] + source_zone = module.params['source_zone'] + source_ip = module.params['source_ip'] + source_user = module.params['source_user'] + hip_profiles = module.params['hip_profiles'] + destination_zone = module.params['destination_zone'] + destination_ip = module.params['destination_ip'] + application = module.params['application'] + service = module.params['service'] + category = module.params['category'] + log_start = module.params['log_start'] + log_end = module.params['log_end'] + action = module.params['action'] + group_profile = module.params['group_profile'] + antivirus = module.params['antivirus'] + vulnerability = module.params['vulnerability'] + spyware = module.params['spyware'] + url_filtering = module.params['url_filtering'] + file_blocking = module.params['file_blocking'] + data_filtering = module.params['data_filtering'] + wildfire_analysis = module.params['wildfire_analysis'] + rule_type = module.params['rule_type'] + devicegroup = module.params['devicegroup'] + + commit = module.params['commit'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # Get the rulebase + rulebase = get_rulebase(device, dev_group) + + # Which action shall we take on the object? + if operation == "find": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Rule matched' + ) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "delete": + # Search for the object + match = find_rule(rulebase, rule_name) + # If found, delete it + if match: + try: + if commit: + match.delete() + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + + module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name) + elif operation == "add": + new_rule = create_security_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + category=category, + application=application, + service=service, + hip_profiles=hip_profiles, + group_profile=group_profile, + antivirus=antivirus, + vulnerability=vulnerability, + spyware=spyware, + url_filtering=url_filtering, + file_blocking=file_blocking, + data_filtering=data_filtering, + wildfire_analysis=wildfire_analysis, + log_start=log_start, + log_end=log_end, + rule_type=rule_type, + action=action + ) + # Search for the rule. Fail if found. + match = find_rule(rulebase, rule_name) + if match: + if rule_is_match(match, new_rule): + module.exit_json(changed=False, msg='Rule \'%s\' is already in place' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name) + else: + try: + changed = add_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully added' % rule_name) + elif operation == 'update': + # Search for the rule. Update if found. + match = find_rule(rulebase, rule_name) + if match: + try: + new_rule = create_security_rule( + rule_name=rule_name, + description=description, + tag_name=tag_name, + source_zone=source_zone, + destination_zone=destination_zone, + source_ip=source_ip, + source_user=source_user, + destination_ip=destination_ip, + category=category, + application=application, + service=service, + hip_profiles=hip_profiles, + group_profile=group_profile, + antivirus=antivirus, + vulnerability=vulnerability, + spyware=spyware, + url_filtering=url_filtering, + file_blocking=file_blocking, + data_filtering=data_filtering, + wildfire_analysis=wildfire_analysis, + log_start=log_start, + log_end=log_end, + rule_type=rule_type, + action=action + ) + changed = update_rule(rulebase, new_rule) + if changed and commit: + device.commit(sync=True) + except PanXapiError as exc: + module.fail_json(msg=to_native(exc)) + module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated' % rule_name) + else: + module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_set.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_set.py new file mode 100644 index 00000000..3491d64f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/panos_set.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2018, Jasper Mackenzie +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: panos_set +short_description: Execute arbitrary commands on a PAN-OS device using XPath and element +description: + - Run an arbitrary 'xapi' command taking an XPath (i.e get) or XPath and element (i.e set). + - See https://github.com/kevinsteves/pan-python/blob/master/doc/pan.xapi.rst for details + - Runs a 'set' command by default + - This should support _all_ commands that your PAN-OS device accepts vi it's cli + - cli commands are found as + - Once logged in issue 'debug cli on' + - Enter configuration mode by issuing 'configure' + - Enter your set (or other) command, for example 'set deviceconfig system timezone Australia/Melbourne' + - returns + - > + "Australia/Melbourne + - The 'xpath' is "/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system" + - The 'element' is "Australia/Melbourne" +author: "Jasper Mackenzie (@spmp)" +deprecated: + alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead. + removed_in: 2.0.0 # was Ansible 2.12 + why: Consolidating code base. +requirements: + - pan-python +options: + ip_address: + description: + - IP address or host FQDN of the target PAN-OS NVA + required: true + username: + description: + - User name for a user with admin rights on the PAN-OS NVA + default: admin + password: + description: + - Password for the given 'username' + required: true + command: + description: + - Xapi method name which supports 'xpath' or 'xpath' and 'element' + choices: + - set + - edit + - delete + - get + - show + - override + default: set + xpath: + description: + - The 'xpath' for the commands configurable + required: true + element: + description: + - The 'element' for the 'xpath' if required +extends_documentation_fragment: +- community.network.panos + +''' + +EXAMPLES = ''' + +- name: Set timezone on PA NVA + community.network.panos_set: + ip_address: "192.168.1.1" + username: "my-random-admin" + password: "admin1234" + xpath: "/config/devices/entry/deviceconfig/system" + element: "Australia/Melbourne" + +- name: Commit configuration + panos_commit: + ip_address: "192.168.1.1" + username: "my-random-admin" + password: "admin1234" +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule + +try: + import pan.xapi + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(required=True, no_log=True), + username=dict(default='admin'), + command=dict(default='set', choices=['set', 'edit', 'delete', 'get', 'show', 'override']), + xpath=dict(required=True), + element=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if not HAS_LIB: + module.fail_json(msg='pan-python is required for this module') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + xpath = module.params['xpath'] + element = module.params['element'] + xcommand = module.params['command'] + + xapi = pan.xapi.PanXapi( + hostname=ip_address, + api_username=username, + api_password=password, + timeout=60 + ) + + if element is None: + # Issue command with no `element` + try: + getattr(xapi, xcommand)(xpath=xpath) + except Exception as e: + raise Exception("Failed to run '%s' with xpath: '%s' with the following error: %s" % + (xcommand, xpath, e)) + else: + # Issue command with `element` + try: + getattr(xapi, xcommand)(xpath=xpath, element=element) + except Exception as e: + raise Exception("Failed to run '%s' with xpath: '%s' and element '%s' with the following error: %s" % + (xcommand, xpath, element, e)) + + module.exit_json( + status="success" + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list.py new file mode 100644 index 00000000..8b923544 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_access_list +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete access-list +description: + - This module can be used to create and delete an access list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use 'present' to create access-list and + 'absent' to delete access-list. + required: True + choices: [ "present", "absent"] + pn_name: + description: + - Access List Name. + required: false + type: str + pn_scope: + description: + - 'scope. Available valid values - local or fabric.' + required: false + choices: ['local', 'fabric'] +''' + +EXAMPLES = """ +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "present" + +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "absent" + +- name: Access list functionality + community.network.pn_access_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "fabric" + state: "present" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the access-list command. + returned: always + type: list +stderr: + description: set of error responses from the access-list command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the access-list-show command. + If a list with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + list_name = module.params['pn_name'] + + cli += ' access-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if list_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='access-list-create', + absent='access-list-delete', + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_name=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + list_name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + ACC_LIST_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, list_name) + + if command == 'access-list-delete': + if ACC_LIST_EXISTS is False: + module.exit_json( + skipped=True, + msg='access-list with name %s does not exist' % list_name + ) + else: + if command == 'access-list-create': + if ACC_LIST_EXISTS is True: + module.exit_json( + skipped=True, + msg='access list with name %s already exists' % list_name + ) + cli += ' scope %s ' % scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list_ip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list_ip.py new file mode 100644 index 00000000..78371d8b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_access_list_ip.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_access_list_ip +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove access-list-ip +description: + - This modules can be used to add and remove IPs associated with access list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use 'present' to add access-list-ip and + 'absent' to remove access-list-ip. + required: True + choices: ["present", "absent"] + pn_ip: + description: + - IP associated with the access list. + required: False + default: '::' + type: str + pn_name: + description: + - Access List Name. + required: False + type: str +''' + +EXAMPLES = """ +- name: Access list ip functionality + community.network.pn_access_list_ip: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ip: "172.16.3.1" + state: "present" + +- name: Access list ip functionality + community.network.pn_access_list_ip: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ip: "172.16.3.1" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the access-list-ip command. + returned: always + type: list +stderr: + description: set of error responses from the access-list-ip command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the access-list-ip-show command. + If ip exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + ip = module.params['pn_ip'] + clicopy = cli + + cli += ' access-list-show name %s no-show-headers ' % name + out = run_commands(module, cli)[1] + + if name not in out: + module.fail_json( + failed=True, + msg='access-list with name %s does not exist' % name + ) + + cli = clicopy + cli += ' access-list-ip-show name %s format ip no-show-headers' % name + + out = run_commands(module, cli)[1] + out = out.split() + return True if ip in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='access-list-ip-add', + absent='access-list-ip-remove', + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_ip=dict(required=False, type='str', default='::'), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name"]], + ["state", "absent", ["pn_name", "pn_ip"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ip = module.params['pn_ip'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + IP_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'access-list-ip-remove': + if IP_EXISTS is False: + module.exit_json( + skipped=True, + msg='access-list with ip %s does not exist' % ip + ) + if ip: + cli += ' ip ' + ip + else: + if command == 'access-list-ip-add': + if IP_EXISTS is True: + module.exit_json( + skipped=True, + msg='access list with ip %s already exists' % ip + ) + if ip: + cli += ' ip ' + ip + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_service.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_service.py new file mode 100644 index 00000000..3ce9ce6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_service.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_service +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify admin-service +description: + - This module is used to modify services on the server-switch. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the admin-service. + required: True + type: str + choices: ['update'] + pn_web: + description: + - Web (HTTP) to enable or disable. + required: False + type: bool + pn_web_ssl: + description: + - Web SSL (HTTPS) to enable or disable. + required: False + type: bool + pn_snmp: + description: + - Simple Network Monitoring Protocol (SNMP) to enable or disable. + required: False + type: bool + pn_web_port: + description: + - Web (HTTP) port to enable or disable. + required: False + type: str + pn_web_ssl_port: + description: + - Web SSL (HTTPS) port to enable or disable. + required: False + type: str + pn_nfs: + description: + - Network File System (NFS) to enable or disable. + required: False + type: bool + pn_ssh: + description: + - Secure Shell to enable or disable. + required: False + type: bool + pn_web_log: + description: + - Web logging to enable or disable. + required: False + type: bool + pn__if: + description: + - administrative service interface. + required: False + type: str + choices: ['mgmt', 'data'] + pn_icmp: + description: + - Internet Message Control Protocol (ICMP) to enable or disable. + required: False + type: bool + pn_net_api: + description: + - Netvisor API to enable or disable APIs. + required: False + type: bool +''' + +EXAMPLES = """ +- name: Admin service functionality + community.network.pn_admin_service: + pn_cliswitch: "sw01" + state: "update" + pn__if: "mgmt" + pn_web: False + pn_icmp: True + +- name: Admin service functionality + community.network.pn_admin_service: + pn_cliswitch: "sw01" + state: "update" + pn_web: False + pn__if: "mgmt" + pn_snmp: True + pn_net_api: True + pn_ssh: True +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-service command. + returned: always + type: list +stderr: + description: set of error responses from the admin-service command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, booleanArgs, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='admin-service-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_web=dict(required=False, type='bool'), + pn_web_ssl=dict(required=False, type='bool'), + pn_snmp=dict(required=False, type='bool'), + pn_web_port=dict(required=False, type='str'), + pn_web_ssl_port=dict(required=False, type='str'), + pn_nfs=dict(required=False, type='bool'), + pn_ssh=dict(required=False, type='bool'), + pn_web_log=dict(required=False, type='bool'), + pn__if=dict(required=False, type='str', choices=['mgmt', 'data']), + pn_icmp=dict(required=False, type='bool'), + pn_net_api=dict(required=False, type='bool'), + ), + required_if=([['state', 'update', ['pn__if']]]), + required_one_of=[['pn_web', 'pn_web_ssl', 'pn_snmp', + 'pn_web_port', 'pn_web_ssl_port', 'pn_nfs', + 'pn_ssh', 'pn_web_log', 'pn_icmp', 'pn_net_api']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + web = module.params['pn_web'] + web_ssl = module.params['pn_web_ssl'] + snmp = module.params['pn_snmp'] + web_port = module.params['pn_web_port'] + web_ssl_port = module.params['pn_web_ssl_port'] + nfs = module.params['pn_nfs'] + ssh = module.params['pn_ssh'] + web_log = module.params['pn_web_log'] + _if = module.params['pn__if'] + icmp = module.params['pn_icmp'] + net_api = module.params['pn_net_api'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'admin-service-modify': + cli += ' %s ' % command + + if _if: + cli += ' if ' + _if + if web_port: + cli += ' web-port ' + web_port + if web_ssl_port: + cli += ' web-ssl-port ' + web_ssl_port + + cli += booleanArgs(web, 'web', 'no-web') + cli += booleanArgs(web_ssl, 'web-ssl', 'no-web-ssl') + cli += booleanArgs(snmp, 'snmp', 'no-snmp') + cli += booleanArgs(nfs, 'nfs', 'no-nfs') + cli += booleanArgs(ssh, 'ssh', 'no-ssh') + cli += booleanArgs(icmp, 'icmp', 'no-icmp') + cli += booleanArgs(net_api, 'net-api', 'no-net-api') + cli += booleanArgs(web_log, 'web-log', 'no-web-log') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_session_timeout.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_session_timeout.py new file mode 100644 index 00000000..0c69c88c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_session_timeout.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_session_timeout +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify admin-session-timeout +description: + - This module can be used to modify admin session timeout. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. + C(update) to modify the admin-session-timeout. + required: True + type: str + choices: ['update'] + pn_timeout: + description: + - Maximum time to wait for user activity before + terminating login session. Minimum should be 60s. + required: False + type: str +''' + +EXAMPLES = """ +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "61s" + +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "1d" + +- name: Admin session timeout functionality + community.network.pn_admin_session_timeout: + pn_cliswitch: "sw01" + state: "update" + pn_timeout: "10d20m3h15s" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-session-timeout command. + returned: always + type: list +stderr: + description: set of error responses from the admin-session-timeout command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='admin-session-timeout-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_timeout=dict(required=False, type='str'), + ), + required_together=[['state', 'pn_timeout']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + timeout = module.params['pn_timeout'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + if command == 'admin-session-timeout-modify': + cli += ' %s ' % command + if timeout: + cli += ' timeout ' + timeout + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_syslog.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_syslog.py new file mode 100644 index 00000000..06821213 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_admin_syslog.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_admin_syslog +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete admin-syslog +description: + - This module can be used to create the scope and other parameters of syslog event collection. + - This module can be used to modify parameters of syslog event collection. + - This module can be used to delete the scope and other parameters of syslog event collection. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create admin-syslog and + C(absent) to delete admin-syslog C(update) to modify the admin-syslog. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - Scope of the system log. + required: False + type: str + choices: ['local', 'fabric'] + pn_host: + description: + - Hostname to log system events. + required: False + type: str + pn_port: + description: + - Host port. + required: False + type: str + pn_transport: + description: + - Transport for log events - tcp/tls or udp. + required: False + type: str + choices: ['tcp-tls', 'udp'] + default: 'udp' + pn_message_format: + description: + - message-format for log events - structured or legacy. + required: False + choices: ['structured', 'legacy'] + type: str + pn_name: + description: + - name of the system log. + required: False + type: str +''' + +EXAMPLES = """ +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + pn_scope: "local" + +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "present" + pn_name: "foo" + pn_scope: "local" + pn_host: "166.68.224.46" + pn_message_format: "structured" + +- name: Admin-syslog functionality + community.network.pn_admin_syslog: + pn_cliswitch: "sw01" + state: "update" + pn_name: "foo" + pn_host: "166.68.224.10" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the admin-syslog command. + returned: always + type: list +stderr: + description: set of error responses from the admin-syslog command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the admin-syslog-show command. + If a user with given name exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + + name = module.params['pn_name'] + + cli += ' admin-syslog-show format name no-show-headers' + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='admin-syslog-create', + absent='admin-syslog-delete', + update='admin-syslog-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_host=dict(required=False, type='str'), + pn_port=dict(required=False, type='str'), + pn_transport=dict(required=False, type='str', + choices=['tcp-tls', 'udp'], default='udp'), + pn_message_format=dict(required=False, type='str', + choices=['structured', 'legacy']), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_name', 'pn_host', 'pn_scope']], + ['state', 'absent', ['pn_name']], + ['state', 'update', ['pn_name']] + ), + required_one_of=[['pn_port', 'pn_message_format', + 'pn_host', 'pn_transport', 'pn_scope']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + host = module.params['pn_host'] + port = module.params['pn_port'] + transport = module.params['pn_transport'] + message_format = module.params['pn_message_format'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + SYSLOG_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'admin-syslog-modify': + if SYSLOG_EXISTS is False: + module.fail_json( + failed=True, + msg='admin syslog with name %s does not exist' % name + ) + + if command == 'admin-syslog-delete': + if SYSLOG_EXISTS is False: + module.exit_json( + skipped=True, + msg='admin syslog with name %s does not exist' % name + ) + + if command == 'admin-syslog-create': + if SYSLOG_EXISTS is True: + module.exit_json( + skipped=True, + msg='admin syslog user with name %s already exists' % name + ) + + if command == 'admin-syslog-create': + if scope: + cli += ' scope ' + scope + + if command != 'admin-syslog-delete': + if host: + cli += ' host ' + host + if port: + cli += ' port ' + port + if transport: + cli += ' transport ' + transport + if message_format: + cli += ' message-format ' + message_format + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cluster.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cluster.py new file mode 100644 index 00000000..c0d43802 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cluster.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +""" PN CLI cluster-create/cluster-delete """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_cluster +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete a cluster. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute cluster-create or cluster-delete command. + - A cluster allows two switches to cooperate in high-availability (HA) + deployments. The nodes that form the cluster must be members of the same + fabric. Clusters are typically used in conjunction with a virtual link + aggregation group (VLAG) that allows links physically connected to two + separate switches appear as a single trunk to a third device. The third + device can be a switch,server, or any Ethernet device. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the cli on. + required: False + default: 'local' + state: + description: + - Specify action to perform. Use 'present' to create cluster and 'absent' + to delete cluster. + required: true + choices: ['present', 'absent'] + pn_name: + description: + - Specify the name of the cluster. + required: true + pn_cluster_node1: + description: + - Specify the name of the first switch in the cluster. + - Required for 'cluster-create'. + pn_cluster_node2: + description: + - Specify the name of the second switch in the cluster. + - Required for 'cluster-create'. + pn_validate: + description: + - Validate the inter-switch links and state of switches in the cluster. + type: bool +''' + +EXAMPLES = """ +- name: Create spine cluster + community.network.pn_cluster: + state: 'present' + pn_name: 'spine-cluster' + pn_cluster_node1: 'spine01' + pn_cluster_node2: 'spine02' + pn_validate: True + pn_quiet: True + +- name: Delete spine cluster + community.network.pn_cluster: + state: 'absent' + pn_name: 'spine-cluster' + pn_quiet: True +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the cluster command. + returned: always + type: list +stderr: + description: The set of error responses from the cluster command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +NAME_EXISTS = None +NODE1_EXISTS = None +NODE2_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the cluster-show command. + If a cluster with given name exists, return NAME_EXISTS as True else False. + If the given cluster-node-1 is already a part of another cluster, return + NODE1_EXISTS as True else False. + If the given cluster-node-2 is already a part of another cluster, return + NODE2_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: NAME_EXISTS, NODE1_EXISTS, NODE2_EXISTS + """ + name = module.params['pn_name'] + node1 = module.params['pn_cluster_node1'] + node2 = module.params['pn_cluster_node2'] + + show = cli + ' cluster-show format name,cluster-node-1,cluster-node-2 ' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global NAME_EXISTS, NODE1_EXISTS, NODE2_EXISTS + + if name in out: + NAME_EXISTS = True + else: + NAME_EXISTS = False + if node1 in out: + NODE1_EXISTS = True + else: + NODE2_EXISTS = False + if node2 in out: + NODE2_EXISTS = True + else: + NODE2_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'cluster-create' + if state == 'absent': + command = 'cluster-delete' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_name=dict(required=True, type='str'), + pn_cluster_node1=dict(type='str'), + pn_cluster_node2=dict(type='str'), + pn_validate=dict(type='bool') + ), + required_if=( + ["state", "present", + ["pn_name", "pn_cluster_node1", "pn_cluster_node2"]], + ["state", "absent", ["pn_name"]] + ) + ) + + # Accessing the parameters + state = module.params['state'] + name = module.params['pn_name'] + cluster_node1 = module.params['pn_cluster_node1'] + cluster_node2 = module.params['pn_cluster_node2'] + validate = module.params['pn_validate'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'cluster-create': + + check_cli(module, cli) + + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='Cluster with name %s already exists' % name + ) + if NODE1_EXISTS is True: + module.exit_json( + skipped=True, + msg='Node %s already part of a cluster' % cluster_node1 + ) + if NODE2_EXISTS is True: + module.exit_json( + skipped=True, + msg='Node %s already part of a cluster' % cluster_node2 + ) + + cli += ' %s name %s ' % (command, name) + cli += 'cluster-node-1 %s cluster-node-2 %s ' % (cluster_node1, + cluster_node2) + if validate is True: + cli += ' validate ' + if validate is False: + cli += ' no-validate ' + + if command == 'cluster-delete': + + check_cli(module, cli) + + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='Cluster with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_connection_stats_settings.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_connection_stats_settings.py new file mode 100644 index 00000000..580ad005 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_connection_stats_settings.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_connection_stats_settings +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify connection-stats-settings +description: + - This module can be used to modify the settings for collecting statistical + data about connections. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the + connection-stats-settings. + required: True + type: str + choices: ['update'] + pn_enable: + description: + - Enable or disable collecting connections statistics. + required: False + type: bool + pn_connection_backup_enable: + description: + - Enable backup for connection statistics collection. + required: False + type: bool + pn_client_server_stats_max_memory: + description: + - maximum memory for client server statistics. + required: False + type: str + pn_connection_stats_log_disk_space: + description: + - disk-space allocated for statistics (including rotated log files). + required: False + type: str + pn_client_server_stats_log_enable: + description: + - Enable or disable statistics. + required: False + type: bool + pn_service_stat_max_memory: + description: + - maximum memory allowed for service statistics. + required: False + type: str + pn_connection_stats_log_interval: + description: + - interval to collect statistics. + required: False + type: str + pn_fabric_connection_backup_interval: + description: + - backup interval for fabric connection statistics collection. + required: False + type: str + pn_connection_backup_interval: + description: + - backup interval for connection statistics collection. + required: False + type: str + pn_connection_stats_log_enable: + description: + - enable or disable statistics. + required: False + type: bool + pn_fabric_connection_max_memory: + description: + - maximum memory allowed for fabric connection statistics. + required: False + type: str + pn_fabric_connection_backup_enable: + description: + - enable backup for fabric connection statistics collection. + required: False + type: bool + pn_client_server_stats_log_disk_space: + description: + - disk-space allocated for statistics (including rotated log files). + required: False + type: str + pn_connection_max_memory: + description: + - maximum memory allowed for connection statistics. + required: False + type: str + pn_connection_stats_max_memory: + description: + - maximum memory allowed for connection statistics. + required: False + type: str + pn_client_server_stats_log_interval: + description: + - interval to collect statistics. + required: False + type: str +''' + +EXAMPLES = """ +- name: "Modify connection stats settings" + community.network.pn_connection_stats_settings: + pn_cliswitch: "sw01" + state: "update" + pn_enable: False + pn_fabric_connection_max_memory: "1000" + +- name: "Modify connection stats settings" + community.network.pn_connection_stats_settings: + pn_cliswitch: "sw01" + state: "update" + pn_enable: True +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the connection-stats-settings command. + returned: always + type: list +stderr: + description: set of error responses from the connection-stats-settings command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='connection-stats-settings-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_enable=dict(required=False, type='bool'), + pn_connection_backup_enable=dict(required=False, type='bool'), + pn_client_server_stats_max_memory=dict(required=False, type='str'), + pn_connection_stats_log_disk_space=dict(required=False, + type='str'), + pn_client_server_stats_log_enable=dict(required=False, + type='bool'), + pn_service_stat_max_memory=dict(required=False, type='str'), + pn_connection_stats_log_interval=dict(required=False, type='str'), + pn_fabric_connection_backup_interval=dict(required=False, + type='str'), + pn_connection_backup_interval=dict(required=False, type='str'), + pn_connection_stats_log_enable=dict(required=False, type='bool'), + pn_fabric_connection_max_memory=dict(required=False, type='str'), + pn_fabric_connection_backup_enable=dict(required=False, + type='bool'), + pn_client_server_stats_log_disk_space=dict(required=False, + type='str'), + pn_connection_max_memory=dict(required=False, type='str'), + pn_connection_stats_max_memory=dict(required=False, type='str'), + pn_client_server_stats_log_interval=dict(required=False, + type='str'), + ), + required_one_of=[['pn_enable', 'pn_connection_backup_enable', + 'pn_client_server_stats_max_memory', + 'pn_connection_stats_log_disk_space', + 'pn_client_server_stats_log_enable', + 'pn_service_stat_max_memory', + 'pn_connection_stats_log_interval', + 'pn_connection_backup_interval', + 'pn_connection_stats_log_enable', + 'pn_fabric_connection_max_memory', + 'pn_fabric_connection_backup_enable', + 'pn_client_server_stats_log_disk_space', + 'pn_connection_max_memory', + 'pn_connection_stats_max_memory', + 'pn_client_server_stats_log_interval']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + enable = module.params['pn_enable'] + connection_backup_enable = module.params['pn_connection_backup_enable'] + client_server_stats_max_memory = module.params['pn_client_server_stats_max_memory'] + connection_stats_log_disk_space = module.params['pn_connection_stats_log_disk_space'] + client_server_stats_log_enable = module.params['pn_client_server_stats_log_enable'] + service_stat_max_memory = module.params['pn_service_stat_max_memory'] + connection_stats_log_interval = module.params['pn_connection_stats_log_interval'] + fabric_connection_backup_interval = module.params['pn_fabric_connection_backup_interval'] + connection_backup_interval = module.params['pn_connection_backup_interval'] + connection_stats_log_enable = module.params['pn_connection_stats_log_enable'] + fabric_connection_max_memory = module.params['pn_fabric_connection_max_memory'] + fabric_connection_backup_enable = module.params['pn_fabric_connection_backup_enable'] + client_server_stats_log_disk_space = module.params['pn_client_server_stats_log_disk_space'] + connection_max_memory = module.params['pn_connection_max_memory'] + connection_stats_max_memory = module.params['pn_connection_stats_max_memory'] + client_server_stats_log_interval = module.params['pn_client_server_stats_log_interval'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'connection-stats-settings-modify': + cli += ' %s ' % command + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(connection_backup_enable, 'connection-backup-enable', 'connection-backup-disable') + cli += booleanArgs(client_server_stats_log_enable, 'client-server-stats-log-enable', 'client-server-stats-log-disable') + cli += booleanArgs(connection_stats_log_enable, 'connection-stats-log-enable', 'connection-stats-log-disable') + cli += booleanArgs(fabric_connection_backup_enable, 'fabric-connection-backup-enable', 'fabric-connection-backup-disable') + + if client_server_stats_max_memory: + cli += ' client-server-stats-max-memory ' + client_server_stats_max_memory + if connection_stats_log_disk_space: + cli += ' connection-stats-log-disk-space ' + connection_stats_log_disk_space + if service_stat_max_memory: + cli += ' service-stat-max-memory ' + service_stat_max_memory + if connection_stats_log_interval: + cli += ' connection-stats-log-interval ' + connection_stats_log_interval + if fabric_connection_backup_interval: + cli += ' fabric-connection-backup-interval ' + fabric_connection_backup_interval + if connection_backup_interval: + cli += ' connection-backup-interval ' + connection_backup_interval + if fabric_connection_max_memory: + cli += ' fabric-connection-max-memory ' + fabric_connection_max_memory + if client_server_stats_log_disk_space: + cli += ' client-server-stats-log-disk-space ' + client_server_stats_log_disk_space + if connection_max_memory: + cli += ' connection-max-memory ' + connection_max_memory + if connection_stats_max_memory: + cli += ' connection-stats-max-memory ' + connection_stats_max_memory + if client_server_stats_log_interval: + cli += ' client-server-stats-log-interval ' + client_server_stats_log_interval + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_class.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_class.py new file mode 100644 index 00000000..3ef89c81 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_class.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_cpu_class +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete cpu-class +description: + - This module can be used to create, modify and delete CPU class information. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create cpu-class and + C(absent) to delete cpu-class C(update) to modify the cpu-class. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - scope for CPU class. + required: false + choices: ['local', 'fabric'] + pn_hog_protect: + description: + - enable host-based hog protection. + required: False + type: str + choices: ['disable', 'enable', 'enable-and-drop'] + pn_rate_limit: + description: + - rate-limit for CPU class. + required: False + type: str + pn_name: + description: + - name for the CPU class. + required: False + type: str +''' + +EXAMPLES = """ +- name: Create cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'present' + pn_name: 'icmp' + pn_rate_limit: '1000' + pn_scope: 'local' + +- name: Delete cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'icmp' + + +- name: Modify cpu class + community.network.pn_cpu_class: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'icmp' + pn_rate_limit: '2000' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the cpu-class command. + returned: always + type: list +stderr: + description: set of error responses from the cpu-class command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the cpu-class-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + clicopy = cli + + cli += ' system-settings-show format cpu-class-enable no-show-headers' + out = run_commands(module, cli)[1] + out = out.split() + + if 'on' not in out: + module.fail_json( + failed=True, + msg='Enable CPU class before creating or deleting' + ) + + cli = clicopy + cli += ' cpu-class-show format name no-show-headers' + out = run_commands(module, cli)[1] + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='cpu-class-create', + absent='cpu-class-delete', + update='cpu-class-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_hog_protect=dict(required=False, type='str', + choices=['disable', 'enable', + 'enable-and-drop']), + pn_rate_limit=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_name', 'pn_scope', 'pn_rate_limit']], + ['state', 'absent', ['pn_name']], + ['state', 'update', ['pn_name']], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + hog_protect = module.params['pn_hog_protect'] + rate_limit = module.params['pn_rate_limit'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'cpu-class-modify': + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='cpu class with name %s does not exist' % name + ) + + if command == 'cpu-class-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='cpu class with name %s does not exist' % name + ) + + if command == 'cpu-class-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='cpu class with name %s already exists' % name + ) + if scope: + cli += ' scope %s ' % scope + + if command != 'cpu-class-delete': + if hog_protect: + cli += ' hog-protect %s ' % hog_protect + if rate_limit: + cli += ' rate-limit %s ' % rate_limit + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_mgmt_class.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_mgmt_class.py new file mode 100644 index 00000000..cf00bc3b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_cpu_mgmt_class.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_cpu_mgmt_class +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify cpu-mgmt-class +description: + - This module can we used to update mgmt port ingress policers. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to modify cpu-mgmt-class. + type: str + required: true + choices: ['update'] + pn_burst_size: + description: + - ingress traffic burst size (bytes) or default. + required: false + type: str + pn_name: + description: + - mgmt port ingress traffic class. + type: str + required: false + choices: ['arp', 'icmp', 'ssh', 'snmp', 'fabric', 'bcast', 'nfs', + 'web', 'web-ssl', 'net-api'] + pn_rate_limit: + description: + - ingress rate limit on mgmt port(bps) or unlimited. + type: str + required: false +''' + +EXAMPLES = """ +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "icmp" + pn_rate_limit: "10000" + pn_burst_size: "14000" + +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "snmp" + pn_burst_size: "8000" + pn_rate_limit: "100000" + +- name: Cpu mgmt class modify ingress policers + community.network.pn_cpu_mgmt_class: + pn_cliswitch: "sw01" + state: "update" + pn_name: "web" + pn_rate_limit: "10000" + pn_burst_size: "1000" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the cpu-mgmt-class command. + returned: always + type: list +stderr: + description: set of error responses from the cpu-mgmt-class command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='cpu-mgmt-class-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', choices=state_map.keys()), + pn_burst_size=dict(required=False, type='str'), + pn_name=dict(required=False, type='str', + choices=['arp', 'icmp', 'ssh', 'snmp', + 'fabric', 'bcast', 'nfs', 'web', + 'web-ssl', 'net-api']), + pn_rate_limit=dict(required=False, type='str'), + ), + required_if=([['state', 'update', ['pn_name', 'pn_burst_size', 'pn_rate_limit']]]), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + burst_size = module.params['pn_burst_size'] + name = module.params['pn_name'] + rate_limit = module.params['pn_rate_limit'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'cpu-mgmt-class-modify': + cli += ' %s name %s ' % (command, name) + cli += ' burst-size %s rate-limit %s' % (burst_size, rate_limit) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dhcp_filter.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dhcp_filter.py new file mode 100644 index 00000000..d876d77c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dhcp_filter.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dhcp_filter +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete dhcp-filter +description: + - This module can be used to create, delete and modify a DHCP filter config. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create dhcp-filter and + C(absent) to delete dhcp-filter C(update) to modify the dhcp-filter. + required: True + type: str + choices: ['present', 'absent', 'update'] + pn_trusted_ports: + description: + - trusted ports of dhcp config. + required: False + type: str + pn_name: + description: + - name of the DHCP filter. + required: false + type: str +''' + +EXAMPLES = """ +- name: Dhcp filter create + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "present" + pn_trusted_ports: "1" + +- name: Dhcp filter delete + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" + pn_trusted_ports: "1" + +- name: Dhcp filter modify + community.network.pn_dhcp_filter: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "update" + pn_trusted_ports: "1,2" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dhcp-filter command. + returned: always + type: list +stderr: + description: set of error responses from the dhcp-filter command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dhcp-filter-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + user_name = module.params['pn_name'] + + cli += ' dhcp-filter-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if user_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='dhcp-filter-create', + absent='dhcp-filter-delete', + update='dhcp-filter-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_trusted_ports=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + ), + required_if=[ + ["state", "present", ["pn_name", "pn_trusted_ports"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name", "pn_trusted_ports"]] + ] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + trusted_ports = module.params['pn_trusted_ports'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'dhcp-filter-modify': + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='dhcp-filter with name %s does not exist' % name + ) + if command == 'dhcp-filter-delete': + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='dhcp-filter with name %s does not exist' % name + ) + if command == 'dhcp-filter-create': + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='dhcp-filter with name %s already exists' % name + ) + if command != 'dhcp-filter-delete': + if trusted_ports: + cli += ' trusted-ports ' + trusted_ports + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map.py new file mode 100644 index 00000000..d5753fe6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dscp_map +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete dscp-map +description: + - This module can be used to create a DSCP priority mapping table. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(present) to create dscp-map and + C(absent) to delete. + required: True + type: str + choices: ["present", "absent"] + pn_name: + description: + - Name for the DSCP map. + required: False + type: str + pn_scope: + description: + - Scope for dscp map. + required: False + choices: ["local", "fabric"] +''' + +EXAMPLES = """ +- name: Dscp map create + community.network.pn_dscp_map: + pn_cliswitch: "sw01" + state: "present" + pn_name: "foo" + pn_scope: "local" + +- name: Dscp map delete + community.network.pn_dscp_map: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dscp-map command. + returned: always + type: list +stderr: + description: set of error responses from the dscp-map command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' dscp-map-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='dscp-map-create', + absent='dscp-map-delete' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_name=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'dscp-map-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='dscp map with name %s does not exist' % name + ) + else: + if command == 'dscp-map-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='dscp map with name %s already exists' % name + ) + + if scope: + cli += ' scope ' + scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map_pri_map.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map_pri_map.py new file mode 100644 index 00000000..a87b64ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_dscp_map_pri_map.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_dscp_map_pri_map +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify dscp-map-pri-map +description: + - This module can be used to update priority mappings in tables. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the dscp-map-pri-map. + required: True + type: str + choices: ['update'] + pn_pri: + description: + - CoS priority. + required: False + type: str + pn_name: + description: + - Name for the DSCP map. + required: False + type: str + pn_dsmap: + description: + - DSCP value(s). + required: False + type: str +''' + +EXAMPLES = """ +- name: Dscp map pri map modify + community.network.pn_dscp_map_pri_map: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_pri: '0' + pn_dsmap: '40' + +- name: Dscp map pri map modify + community.network.pn_dscp_map_pri_map: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_pri: '1' + pn_dsmap: '8,10,12,14' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the dscp-map-pri-map command. + returned: always + type: list +stderr: + description: set of error responses from the dscp-map-pri-map command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' dscp-map-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='dscp-map-pri-map-modify' + ) + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_pri=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + pn_dsmap=dict(required=False, type='str'), + ), + required_if=( + ['state', 'update', ['pn_name', 'pn_pri']], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + pri = module.params['pn_pri'] + name = module.params['pn_name'] + dsmap = module.params['pn_dsmap'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + if command == 'dscp-map-pri-map-modify': + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='Create dscp map with name %s before updating' % name + ) + cli += ' %s ' % command + if pri: + cli += ' pri ' + pri + if name: + cli += ' name ' + name + if dsmap: + cli += ' dsmap ' + dsmap + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_fabric_local.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_fabric_local.py new file mode 100644 index 00000000..e2e3e863 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_fabric_local.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_fabric_local +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify fabric-local +description: + - This module can be used to modify fabric local information. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: true + type: str + state: + description: + - State the action to perform. Use C(update) to modify the fabric-local. + required: false + type: str + choices: ['update'] + default: 'update' + pn_fabric_network: + description: + - fabric administration network. + required: false + choices: ['in-band', 'mgmt', 'vmgmt'] + default: 'mgmt' + pn_vlan: + description: + - VLAN assigned to fabric. + required: false + type: str + pn_control_network: + description: + - control plane network. + required: false + choices: ['in-band', 'mgmt', 'vmgmt'] + pn_fabric_advertisement_network: + description: + - network to send fabric advertisements on. + required: false + choices: ['inband-mgmt', 'inband-only', 'inband-vmgmt', 'mgmt-only'] +''' + +EXAMPLES = """ +- name: Fabric local module + community.network.pn_fabric_local: + pn_cliswitch: "sw01" + pn_vlan: "500" + +- name: Fabric local module + community.network.pn_fabric_local: + pn_cliswitch: "sw01" + pn_fabric_advertisement_network: "mgmt-only" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the fabric-local command. + returned: always + type: list +stderr: + description: set of error responses from the fabric-local command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='fabric-local-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=True, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='update'), + pn_fabric_network=dict(required=False, type='str', + choices=['mgmt', 'in-band', 'vmgmt'], default='mgmt'), + pn_vlan=dict(required=False, type='str'), + pn_control_network=dict(required=False, type='str', + choices=['in-band', 'mgmt', 'vmgmt']), + pn_fabric_advertisement_network=dict(required=False, type='str', + choices=['inband-mgmt', 'inband-only', 'inband-vmgmt', 'mgmt-only']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[['pn_fabric_network', 'pn_vlan', + 'pn_control_network', + 'pn_fabric_advertisement_network']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + fabric_network = module.params['pn_fabric_network'] + vlan = module.params['pn_vlan'] + control_network = module.params['pn_control_network'] + fabric_adv_network = module.params['pn_fabric_advertisement_network'] + + command = state_map[state] + + if vlan: + if int(vlan) < 1 or int(vlan) > 4092: + module.fail_json( + failed=True, + msg='Valid vlan range is 1 to 4092' + ) + cli = pn_cli(module, cliswitch) + cli += ' vlan-show format id no-show-headers' + out = run_commands(module, cli)[1].split() + + if vlan in out and vlan != '1': + module.fail_json( + failed=True, + msg='vlan %s is already in used. Specify unused vlan' % vlan + ) + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'fabric-local-modify': + cli += ' %s ' % command + + if fabric_network: + cli += ' fabric-network ' + fabric_network + + if vlan: + cli += ' vlan ' + vlan + + if control_network: + cli += ' control-network ' + control_network + + if fabric_adv_network: + cli += ' fabric-advertisement-network ' + fabric_adv_network + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_igmp_snooping.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_igmp_snooping.py new file mode 100644 index 00000000..a2a1ce74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_igmp_snooping.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_igmp_snooping +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify igmp-snooping +description: + - This module can be used to modify Internet Group Management Protocol (IGMP) snooping. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the igmp-snooping. + required: True + type: str + choices: ['update'] + pn_enable: + description: + - enable or disable IGMP snooping. + required: False + type: bool + pn_query_interval: + description: + - IGMP query interval in seconds. + required: False + type: str + pn_igmpv2_vlans: + description: + - VLANs on which to use IGMPv2 protocol. + required: False + type: str + pn_igmpv3_vlans: + description: + - VLANs on which to use IGMPv3 protocol. + required: False + type: str + pn_enable_vlans: + description: + - enable per VLAN IGMP snooping. + required: False + type: str + pn_vxlan: + description: + - enable or disable IGMP snooping on vxlans. + required: False + type: bool + pn_query_max_response_time: + description: + - maximum response time, in seconds, advertised in IGMP queries. + required: False + type: str + pn_scope: + description: + - IGMP snooping scope - fabric or local. + required: False + choices: ['local', 'fabric'] + pn_no_snoop_linklocal_vlans: + description: + - Remove snooping of link-local groups(224.0.0.0/24) on these vlans. + required: False + type: str + pn_snoop_linklocal_vlans: + description: + - Allow snooping of link-local groups(224.0.0.0/24) on these vlans. + required: False + type: str +''' + +EXAMPLES = """ +- name: 'Modify IGMP Snooping' + community.network.pn_igmp_snooping: + pn_cliswitch: 'sw01' + state: 'update' + pn_vxlan: True + pn_enable_vlans: '1-399,401-4092' + pn_no_snoop_linklocal_vlans: 'none' + pn_igmpv3_vlans: '1-399,401-4092' + +- name: 'Modify IGMP Snooping' + community.network.pn_igmp_snooping: + pn_cliswitch: 'sw01' + state: 'update' + pn_vxlan: False + pn_enable_vlans: '1-399' + pn_no_snoop_linklocal_vlans: 'none' + pn_igmpv3_vlans: '1-399' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the igmp-snooping command. + returned: always + type: list +stderr: + description: set of error responses from the igmp-snooping command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='igmp-snooping-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_enable=dict(required=False, type='bool'), + pn_query_interval=dict(required=False, type='str'), + pn_igmpv2_vlans=dict(required=False, type='str'), + pn_igmpv3_vlans=dict(required=False, type='str'), + pn_enable_vlans=dict(required=False, type='str'), + pn_vxlan=dict(required=False, type='bool'), + pn_query_max_response_time=dict(required=False, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_no_snoop_linklocal_vlans=dict(required=False, type='str'), + pn_snoop_linklocal_vlans=dict(required=False, type='str'), + ), + required_one_of=[['pn_enable', 'pn_query_interval', + 'pn_igmpv2_vlans', + 'pn_igmpv3_vlans', + 'pn_enable_vlans', + 'pn_vxlan', + 'pn_query_max_response_time', + 'pn_scope', + 'pn_no_snoop_linklocal_vlans', + 'pn_snoop_linklocal_vlans']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + enable = module.params['pn_enable'] + query_interval = module.params['pn_query_interval'] + igmpv2_vlans = module.params['pn_igmpv2_vlans'] + igmpv3_vlans = module.params['pn_igmpv3_vlans'] + enable_vlans = module.params['pn_enable_vlans'] + vxlan = module.params['pn_vxlan'] + query_max_response_time = module.params['pn_query_max_response_time'] + scope = module.params['pn_scope'] + no_snoop_linklocal_vlans = module.params['pn_no_snoop_linklocal_vlans'] + snoop_linklocal_vlans = module.params['pn_snoop_linklocal_vlans'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'igmp-snooping-modify': + cli += ' %s ' % command + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(vxlan, 'vxlan', 'no-vxlan') + + if query_interval: + cli += ' query-interval ' + query_interval + if igmpv2_vlans: + cli += ' igmpv2-vlans ' + igmpv2_vlans + if igmpv3_vlans: + cli += ' igmpv3-vlans ' + igmpv3_vlans + if enable_vlans: + cli += ' enable-vlans ' + enable_vlans + if query_max_response_time: + cli += ' query-max-response-time ' + query_max_response_time + if scope: + cli += ' scope ' + scope + if no_snoop_linklocal_vlans: + cli += ' no-snoop-linklocal-vlans ' + no_snoop_linklocal_vlans + if snoop_linklocal_vlans: + cli += ' snoop-linklocal-vlans ' + snoop_linklocal_vlans + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard.py new file mode 100644 index 00000000..6100480c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete ipv6security-raguard +description: + - This module can be used to add ipv6 RA Guard Policy, Update ipv6 RA guard Policy and Remove ipv6 RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard configuration command. + required: false + choices: ['present', 'update', 'absent'] + type: str + default: 'present' + pn_device: + description: + - RA Guard Device. host or router. + required: false + choices: ['host', 'router'] + type: str + pn_access_list: + description: + - RA Guard Access List of Source IPs. + required: false + type: str + pn_prefix_list: + description: + - RA Guard Prefix List. + required: false + type: str + pn_router_priority: + description: + - RA Guard Router Priority. + required: false + type: str + choices: ['low', 'medium', 'high'] + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security ragurad create + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_device: "host" + +- name: Ipv6 security ragurad create + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo1" + pn_device: "host" + pn_access_list: "sample" + pn_prefix_list: "sample" + pn_router_priority: "low" + +- name: Ipv6 security ragurad modify + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo1" + pn_device: "router" + pn_router_priority: "medium" + state: "update" + +- name: Ipv6 security ragurad delete + community.network.pn_ipv6security_raguard: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module): + """ + This method checks for idempotency using the ipv6security-raguard-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli = 'ipv6security-raguard-show format name parsable-delim ,' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def check_list(module, list_name, command): + """ + This method checks for idempotency using provided command. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + + cli = '%s format name no-show-headers' % command + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + if list_name not in out: + module.fail_json( + failed=True, + msg='%s name %s does not exists' % (command, list_name) + ) + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-create', + absent='ipv6security-raguard-delete', + update='ipv6security-raguard-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_device=dict(required=False, type='str', choices=['host', 'router']), + pn_access_list=dict(required=False, type='str'), + pn_prefix_list=dict(required=False, type='str'), + pn_router_priority=dict(required=False, type='str', choices=['low', 'medium', 'high']), + pn_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ['pn_device']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + device = module.params['pn_device'] + access_list = module.params['pn_access_list'] + prefix_list = module.params['pn_prefix_list'] + router_priority = module.params['pn_router_priority'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module) + + if command == 'ipv6security-raguard-modify': + if not device and not access_list and not prefix_list and not router_priority: + module.fail_json( + failed=True, + msg='required one of device, access_list, prefix_list or router_priority' + ) + + if command == 'ipv6security-raguard-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='ipv6 security raguard with name %s already exists' % name + ) + + if command != 'ipv6security-raguard-create': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='ipv6 security raguard with name %s does not exist' % name + ) + + cli += ' %s name %s ' % (command, name) + if command != 'ipv6security-raguard-delete': + if device == 'router': + cli += ' device ' + device + if access_list: + check_list(module, access_list, 'access-list-show') + cli += ' access-list ' + access_list + if prefix_list: + check_list(module, prefix_list, 'prefix-list-show') + cli += ' prefix-list ' + prefix_list + if router_priority: + cli += ' router-priority ' + router_priority + if device == 'host': + cli += ' device ' + device + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_port.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_port.py new file mode 100644 index 00000000..10b467d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_port.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard_port +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove ipv6security-raguard-port +description: + - This module can be used to add ports to RA Guard Policy and remove ports to RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard-port configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str + pn_ports: + description: + - Ports attached to RA Guard Policy. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security raguard port add + community.network.pn_ipv6security_raguard_port: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_ports: "1" + +- name: Ipv6 security raguard port remove + community.network.pn_ipv6security_raguard_port: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" + pn_ports: "1" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard-port command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard-port command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module): + """ + This method checks for idempotency using the ipv6security-raguard-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli = 'ipv6security-raguard-show format name parsable-delim ,' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-port-add', + absent='ipv6security-raguard-port-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_name=dict(required=True, type='str'), + pn_ports=dict(required=True, type='str') + ) + + module = AnsibleModule( + argument_spec=argument_spec + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + ports = module.params['pn_ports'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module) + + if command: + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='ipv6 security raguard with name %s does not exist to add ports' % name + ) + + cli += ' %s name %s ports %s' % (command, name, ports) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_vlan.py new file mode 100644 index 00000000..fe87d52f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ipv6security_raguard_vlan.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_ipv6security_raguard_vlan +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove ipv6security-raguard-vlan +description: + - This module can be used to Add vlans to RA Guard Policy and Remove vlans to RA Guard Policy. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - ipv6security-raguard-vlan configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_vlans: + description: + - Vlans attached to RA Guard Policy. + required: true + type: str + pn_name: + description: + - RA Guard Policy Name. + required: true + type: str +''' + +EXAMPLES = """ +- name: Ipv6 security raguard vlan add + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100-105" + +- name: Ipv6 security raguard vlan add + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100" + +- name: Ipv6 security raguard vlan remove + community.network.pn_ipv6security_raguard_vlan: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_vlans: "100-105" + state: 'absent' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the ipv6security-raguard-vlan command. + returned: always + type: list +stderr: + description: set of error responses from the ipv6security-raguard-vlan command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the ipv6-security-reguard command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + vlans = module.params['pn_vlans'] + show = cli + + cli += ' ipv6security-raguard-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + NAME_EXISTS = True if name in out else False + + show += ' vlan-show format id no-show-headers' + out = run_commands(module, show)[1] + if out: + out = out.split() + + if vlans and '-' in vlans: + vlan_list = list() + vlans = vlans.strip().split('-') + for vlan in range(int(vlans[0]), int(vlans[1]) + 1): + vlan_list.append(str(vlan)) + + for vlan in vlan_list: + if vlan not in out: + module.fail_json( + failed=True, + msg='vlan id %s does not exist. Make sure you create vlan before adding it' % vlan + ) + else: + if vlans not in out: + module.fail_json( + failed=True, + msg='vlan id %s does not exist. Make sure you create vlan before adding it' % vlans + ) + + return NAME_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='ipv6security-raguard-vlan-add', + absent='ipv6security-raguard-vlan-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_vlans=dict(required=True, type='str'), + pn_name=dict(required=True, type='str'), + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + vlans = module.params['pn_vlans'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command: + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='ipv6security raguard with name %s does not exist' % name + ) + if vlans: + cli += ' vlans ' + vlans + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_log_audit_exception.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_log_audit_exception.py new file mode 100644 index 00000000..cae5b15e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_log_audit_exception.py @@ -0,0 +1,198 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/license/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_log_audit_exception +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete an audit exception +description: + - This module can be used to create an audit exception and delete an audit exception. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + pn_audit_type: + description: + - Specify the type of audit exception. + required: false + type: str + choices: ['cli', 'shell', 'vtysh'] + state: + description: + - State the action to perform. Use 'present' to create audit-exception and + 'absent' to delete audit-exception. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_pattern: + description: + - Specify a regular expression to match exceptions. + required: false + type: str + pn_scope: + description: + - scope - local or fabric. + required: false + type: str + choices: ['local', 'fabric'] + pn_access: + description: + - Specify the access type to match exceptions. + required: true + type: str + choices: ['any', 'read-only', 'read-write'] +''' + +EXAMPLES = """ +- name: Create a log-audit-exception + community.network.pn_log_audit_exception: + pn_audit_type: "cli" + pn_pattern: "test" + state: "present" + pn_access: "any" + pn_scope: "local" + +- name: Delete a log-audit-exception + community.network.pn_log_audit_exception: + pn_audit_type: "shell" + pn_pattern: "test" + state: "absent" + pn_access: "any" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the pn_log_audit_exceptions command. + returned: always + type: list +stderr: + description: set of error responses from the log_audit_exceptions command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the log-audit-exception command. + If a list with given name exists, return exists as True else False. + :param module: The Ansible module to fetch input parameters. + :return Booleans: True or False. + """ + state = module.params['state'] + audit_type = module.params['pn_audit_type'] + pattern = module.params['pn_pattern'] + access = module.params['pn_access'] + scope = module.params['pn_scope'] + cli += ' log-audit-exception-show' + cli += ' no-show-headers format ' + cli += ' type,pattern,access,scope parsable-delim DELIM' + + stdout = run_commands(module, cli)[1] + + if stdout: + linelist = stdout.strip().split('\n') + for line in linelist: + wordlist = line.split('DELIM') + count = 0 + + if wordlist[0] == audit_type: + count += 1 + if wordlist[1] == pattern: + count += 1 + if wordlist[2] == access: + count += 1 + if state == 'present' and wordlist[3] == scope: + count += 1 + elif state == 'absent' and count == 3: + return True + if state == 'present' and count == 4: + return True + + return False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='log-audit-exception-create', + absent='log-audit-exception-delete', + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + pn_pattern=dict(required=True, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_access=dict(required=True, type='str', choices=['any', 'read-only', 'read-write']), + pn_audit_type=dict(required=True, type='str', choices=['cli', 'shell', 'vtysh']), + pn_scope=dict(required=False, type='str', choices=['local', 'fabric']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_scope"]], + ), + ) + + # Accessing the arguments + + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + access = module.params['pn_access'] + audit_type = module.params['pn_audit_type'] + pattern = module.params['pn_pattern'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + audit_log_exists = check_cli(module, cli) + + cli += ' %s %s pattern %s %s' % (command, audit_type, pattern, access) + + if state == 'absent': + if audit_log_exists is False: + module.exit_json( + skipped=True, + msg='This audit log exception entry does not exist' + ) + run_cli(module, cli, state_map) + + elif state == 'present': + if audit_log_exists is True: + module.exit_json( + skipped=True, + msg='This audit log exception entry already exists' + ) + cli += ' scope %s ' % scope + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospf.py new file mode 100644 index 00000000..4ee2567e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospf.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +""" PN-CLI vrouter-ospf-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_ospf +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove ospf protocol to a vRouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-ospf-add, vrouter-ospf-remove command. + - This command adds/removes Open Shortest Path First(OSPF) routing + protocol to a virtual router(vRouter) service. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + default: 'local' + state: + description: + - Assert the state of the ospf. Use 'present' to add ospf + and 'absent' to remove ospf. + required: True + default: present + choices: ['present', 'absent'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: True + pn_network_ip: + description: + - Specify the network IP (IPv4 or IPv6) address. + required: True + pn_ospf_area: + description: + - Stub area number for the configuration. Required for vrouter-ospf-add. +''' + +EXAMPLES = """ +- name: "Add OSPF to vrouter" + community.network.pn_ospf: + state: present + pn_vrouter_name: name-string + pn_network_ip: 192.168.11.2/24 + pn_ospf_area: 1.0.0.0 + +- name: "Remove OSPF from vrouter" + community.network.pn_ospf: + state: absent + pn_vrouter_name: name-string +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the ospf command. + returned: always + type: list +stderr: + description: The set of error responses from the ospf command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +NETWORK_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-ospf-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If an OSPF network with the given ip exists on the given vRouter, + return NETWORK_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, NETWORK_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + network_ip = module.params['pn_network_ip'] + # Global flags + global VROUTER_EXISTS, NETWORK_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for OSPF networks + show = cli + ' vrouter-ospf-show vrouter-name %s ' % vrouter_name + show += 'format network no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if network_ip in out: + NETWORK_EXISTS = True + else: + NETWORK_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + cmd = shlex.split(cli) + + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-ospf-add' + if state == 'absent': + command = 'vrouter-ospf-remove' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(type='str', default='present', choices=['present', + 'absent']), + pn_vrouter_name=dict(required=True, type='str'), + pn_network_ip=dict(required=True, type='str'), + pn_ospf_area=dict(type='str') + ), + required_if=( + ['state', 'present', + ['pn_network_ip', 'pn_ospf_area']], + ['state', 'absent', ['pn_network_ip']] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + network_ip = module.params['pn_network_ip'] + ospf_area = module.params['pn_ospf_area'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + check_cli(module, cli) + + if state == 'present': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg=('OSPF with network ip %s already exists on %s' + % (network_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s network %s ospf-area %s' + % (command, vrouter_name, network_ip, ospf_area)) + + if state == 'absent': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg=('OSPF with network ip %s already exists on %s' + % (network_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s network %s' + % (command, vrouter_name, network_ip)) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospfarea.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospfarea.py new file mode 100644 index 00000000..305b9649 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_ospfarea.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +""" PN-CLI vrouter-ospf-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_ospfarea +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove ospf area to/from a vrouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-ospf-add, vrouter-ospf-remove command. + - This command adds/removes Open Shortest Path First(OSPF) area to/from + a virtual router(vRouter) service. +options: + pn_cliusername: + description: + - Login username. + required: true + pn_clipassword: + description: + - Login password. + required: true + pn_cliswitch: + description: + - Target switch(es) to run the CLI on. + required: False + state: + description: + - State the action to perform. Use 'present' to add ospf-area, 'absent' + to remove ospf-area and 'update' to modify ospf-area. + required: true + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: true + pn_ospf_area: + description: + - Specify the OSPF area number. + required: true + pn_stub_type: + description: + - Specify the OSPF stub type. + choices: ['none', 'stub', 'stub-no-summary', 'nssa', 'nssa-no-summary'] + pn_prefix_listin: + description: + - OSPF prefix list for filtering incoming packets. + pn_prefix_listout: + description: + - OSPF prefix list for filtering outgoing packets. + pn_quiet: + description: + - Enable/disable system information. + required: false + type: bool + default: true +''' + +EXAMPLES = """ +- name: "Add OSPF area to vrouter" + community.network.pn_ospfarea: + state: present + pn_cliusername: admin + pn_clipassword: admin + pn_ospf_area: 1.0.0.0 + pn_stub_type: stub + +- name: "Remove OSPF from vrouter" + pn_ospf: + state: absent + pn_cliusername: admin + pn_clipassword: admin + pn_vrouter_name: name-string + pn_ospf_area: 1.0.0.0 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the ospf command. + returned: always + type: list +stderr: + description: The set of error responses from the ospf command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-ospf-area-add' + if state == 'absent': + command = 'vrouter-ospf-area-remove' + if state == 'update': + command = 'vrouter-ospf-area-modify' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=True, type='str'), + pn_clipassword=dict(required=True, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_ospf_area=dict(required=True, type='str'), + pn_stub_type=dict(type='str', choices=['none', 'stub', 'nssa', + 'stub-no-summary', + 'nssa-no-summary']), + pn_prefix_listin=dict(type='str'), + pn_prefix_listout=dict(type='str'), + pn_quiet=dict(type='bool', default='True') + ) + ) + + # Accessing the arguments + cliusername = module.params['pn_cliusername'] + clipassword = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + ospf_area = module.params['pn_ospf_area'] + stub_type = module.params['pn_stub_type'] + prefix_listin = module.params['pn_prefix_listin'] + prefix_listout = module.params['pn_prefix_listout'] + quiet = module.params['pn_quiet'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = '/usr/bin/cli' + + if quiet is True: + cli += ' --quiet ' + + cli += ' --user %s:%s ' % (cliusername, clipassword) + + if cliswitch: + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + + cli += ' %s vrouter-name %s area %s ' % (command, vrouter_name, ospf_area) + + if stub_type: + cli += ' stub-type ' + stub_type + + if prefix_listin: + cli += ' prefix-list-in ' + prefix_listin + + if prefix_listout: + cli += ' prefix-list-out ' + prefix_listout + + # Run the CLI command + ospfcommand = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(ospfcommand) + + # Response in JSON format + if result != 0: + module.exit_json( + command=cli, + stderr=err.rstrip("\r\n"), + changed=False + ) + + else: + module.exit_json( + command=cli, + stdout=out.rstrip("\r\n"), + changed=True + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_config.py new file mode 100644 index 00000000..31f024b4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_config.py @@ -0,0 +1,377 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_config +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-config +description: + - This module can be used to modify a port configuration. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the port-config. + required: True + type: str + choices: ['update'] + pn_intf: + description: + - physical interface. + required: False + type: str + pn_crc_check_enable: + description: + - CRC check on ingress and rewrite on egress. + required: False + type: bool + pn_dscp_map: + description: + - DSCP map name to enable on port. + required: False + type: str + pn_autoneg: + description: + - physical port autonegotiation. + required: False + type: bool + pn_speed: + description: + - physical port speed. + required: False + choices: ['disable', '10m', '100m', '1g', + '2.5g', '10g', '25g', '40g', '50g', '100g'] + pn_port: + description: + - physical port. + required: False + type: str + pn_vxlan_termination: + description: + - physical port vxlan termination setting. + required: False + type: bool + pn_pause: + description: + - physical port pause. + required: False + type: bool + pn_loopback: + description: + - physical port loopback. + required: False + type: bool + pn_loop_vlans: + description: + - looping vlans. + required: False + type: str + pn_routing: + description: + - routing. + required: False + type: bool + pn_edge_switch: + description: + - physical port edge switch. + required: False + type: bool + pn_enable: + description: + - physical port enable. + required: False + type: bool + pn_description: + description: + - physical port description. + required: False + type: str + pn_host_enable: + description: + - Host facing port control setting. + required: False + type: bool + pn_allowed_tpid: + description: + - Allowed TPID in addition to 0x8100 on Vlan header. + required: False + type: str + choices: ['vlan', 'q-in-q', 'q-in-q-old'] + pn_mirror_only: + description: + - physical port mirror only. + required: False + type: bool + pn_reflect: + description: + - physical port reflection. + required: False + type: bool + pn_jumbo: + description: + - jumbo frames on physical port. + required: False + type: bool + pn_egress_rate_limit: + description: + - max egress port data rate limit. + required: False + type: str + pn_eth_mode: + description: + - physical Ethernet mode. + required: False + choices: ['1000base-x', 'sgmii', 'disabled', 'GMII'] + pn_fabric_guard: + description: + - Fabric guard configuration. + required: False + type: bool + pn_local_switching: + description: + - no-local-switching port cannot bridge traffic to + another no-local-switching port. + required: False + type: bool + pn_lacp_priority: + description: + - LACP priority from 1 to 65535. + required: False + type: str + pn_send_port: + description: + - send port. + required: False + type: str + pn_port_mac_address: + description: + - physical port MAC Address. + required: False + type: str + pn_defer_bringup: + description: + - defer port bringup. + required: False + type: bool +''' + +EXAMPLES = """ +- name: Port config modify + community.network.pn_port_config: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_dscp_map: "foo" + +- name: Port config modify + community.network.pn_port_config: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_host_enable: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-config command. + returned: always + type: list +stderr: + description: set of error responses from the port-config command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the dscp-map-show name command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_dscp_map'] + + cli += ' dscp-map-show name %s format name no-show-headers' % name + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out[-1] else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-config-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['update']), + pn_intf=dict(required=False, type='str'), + pn_crc_check_enable=dict(required=False, type='bool'), + pn_dscp_map=dict(required=False, type='str'), + pn_autoneg=dict(required=False, type='bool'), + pn_speed=dict(required=False, type='str', + choices=['disable', '10m', '100m', + '1g', '2.5g', '10g', '25g', + '40g', '50g', '100g']), + pn_port=dict(required=False, type='str'), + pn_vxlan_termination=dict(required=False, type='bool'), + pn_pause=dict(required=False, type='bool'), + pn_loopback=dict(required=False, type='bool'), + pn_loop_vlans=dict(required=False, type='str'), + pn_routing=dict(required=False, type='bool'), + pn_edge_switch=dict(required=False, type='bool'), + pn_enable=dict(required=False, type='bool'), + pn_description=dict(required=False, type='str'), + pn_host_enable=dict(required=False, type='bool'), + pn_allowed_tpid=dict(required=False, type='str', + choices=['vlan', 'q-in-q', 'q-in-q-old']), + pn_mirror_only=dict(required=False, type='bool'), + pn_reflect=dict(required=False, type='bool'), + pn_jumbo=dict(required=False, type='bool'), + pn_egress_rate_limit=dict(required=False, type='str'), + pn_eth_mode=dict(required=False, type='str', + choices=['1000base-x', 'sgmii', + 'disabled', 'GMII']), + pn_fabric_guard=dict(required=False, type='bool'), + pn_local_switching=dict(required=False, type='bool'), + pn_lacp_priority=dict(required=False, type='str'), + pn_send_port=dict(required=False, type='str'), + pn_port_mac_address=dict(required=False, type='str'), + pn_defer_bringup=dict(required=False, type='bool'), + ), + required_if=( + ['state', 'update', ['pn_port']], + ), + required_one_of=[['pn_intf', 'pn_crc_check_enable', 'pn_dscp_map', + 'pn_speed', 'pn_autoneg', + 'pn_vxlan_termination', 'pn_pause', + 'pn_fec', 'pn_loopback', 'pn_loop_vlans', + 'pn_routing', 'pn_edge_switch', + 'pn_enable', 'pn_description', + 'pn_host_enable', 'pn_allowed_tpid', + 'pn_mirror_only', 'pn_reflect', + 'pn_jumbo', 'pn_egress_rate_limit', + 'pn_eth_mode', 'pn_fabric_guard', + 'pn_local_switching', 'pn_lacp_priority', + 'pn_send_port', 'pn_port_mac_address', + 'pn_defer_bringup']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + intf = module.params['pn_intf'] + crc_check_enable = module.params['pn_crc_check_enable'] + dscp_map = module.params['pn_dscp_map'] + autoneg = module.params['pn_autoneg'] + speed = module.params['pn_speed'] + port = module.params['pn_port'] + vxlan_termination = module.params['pn_vxlan_termination'] + pause = module.params['pn_pause'] + loopback = module.params['pn_loopback'] + loop_vlans = module.params['pn_loop_vlans'] + routing = module.params['pn_routing'] + edge_switch = module.params['pn_edge_switch'] + enable = module.params['pn_enable'] + description = module.params['pn_description'] + host_enable = module.params['pn_host_enable'] + allowed_tpid = module.params['pn_allowed_tpid'] + mirror_only = module.params['pn_mirror_only'] + reflect = module.params['pn_reflect'] + jumbo = module.params['pn_jumbo'] + egress_rate_limit = module.params['pn_egress_rate_limit'] + eth_mode = module.params['pn_eth_mode'] + fabric_guard = module.params['pn_fabric_guard'] + local_switching = module.params['pn_local_switching'] + lacp_priority = module.params['pn_lacp_priority'] + send_port = module.params['pn_send_port'] + port_mac_address = module.params['pn_port_mac_address'] + defer_bringup = module.params['pn_defer_bringup'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if dscp_map: + NAME_EXISTS = check_cli(module, cli) + + if command == 'port-config-modify': + cli += ' %s ' % command + if dscp_map: + if NAME_EXISTS is False: + module.fail_json( + failed=True, + msg='Create dscp map with name %s before updating' % dscp_map + ) + + cli += ' dscp-map ' + dscp_map + if intf: + cli += ' intf ' + intf + if speed: + cli += ' speed ' + speed + if port: + cli += ' port ' + port + if allowed_tpid: + cli += ' allowed-tpid ' + allowed_tpid + if egress_rate_limit: + cli += ' egress-rate-limit ' + egress_rate_limit + if eth_mode: + cli += ' eth-mode ' + eth_mode + if lacp_priority: + cli += ' lacp-priority ' + lacp_priority + if send_port: + cli += ' send-port ' + send_port + if port_mac_address: + cli += ' port-mac-address ' + port_mac_address + + cli += booleanArgs(crc_check_enable, 'crc-check-enable', 'crc-check-disable') + cli += booleanArgs(autoneg, 'autoneg', 'no-autoneg') + cli += booleanArgs(vxlan_termination, 'vxlan-termination', 'no-vxlan-termination') + cli += booleanArgs(pause, 'pause', 'no-pause') + cli += booleanArgs(loopback, 'loopback', 'no-loopback') + cli += booleanArgs(routing, 'routing', 'no-routing') + cli += booleanArgs(edge_switch, 'edge-switch', 'no-edge-switch') + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(host_enable, 'host-enable', 'host-disable') + cli += booleanArgs(mirror_only, 'mirror-only', 'no-mirror-receive-only') + cli += booleanArgs(reflect, 'reflect', 'no-reflect') + cli += booleanArgs(jumbo, 'jumbo', 'no-jumbo') + cli += booleanArgs(fabric_guard, 'fabric-guard', 'no-fabric-guard') + cli += booleanArgs(local_switching, 'local-switching', 'no-local-switching') + cli += booleanArgs(defer_bringup, 'defer-bringup', 'no-defer-bringup') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_bw.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_bw.py new file mode 100644 index 00000000..11578dc8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_bw.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_cos_bw +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-cos-bw +description: + - This module can be used to update bw settings for CoS queues. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: False + type: str + state: + description: + - State the action to perform. Use C(update) to modify the port-cos-bw. + required: True + type: str + choices: ['update'] + pn_max_bw_limit: + description: + - Maximum b/w in percentage. + required: False + type: str + pn_cos: + description: + - CoS priority. + required: False + type: str + pn_port: + description: + - physical port number. + required: False + type: str + pn_weight: + description: + - Scheduling weight (1 to 127) after b/w guarantee met. + required: False + type: str + choices: ['priority', 'no-priority'] + pn_min_bw_guarantee: + description: + - Minimum b/w in percentage. + required: False + type: str +''' + +EXAMPLES = """ +- name: Port cos bw modify + community.network.pn_port_cos_bw: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_cos: "0" + pn_min_bw_guarantee: "60" + +- name: Port cos bw modify + community.network.pn_port_cos_bw: + pn_cliswitch: "sw01" + state: "update" + pn_port: "all" + pn_cos: "0" + pn_weight: "priority" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-cos-bw command. + returned: always + type: list +stderr: + description: set of error responses from the port-cos-bw command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-cos-bw-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_max_bw_limit=dict(required=False, type='str'), + pn_cos=dict(required=False, type='str'), + pn_port=dict(required=False, type='str'), + pn_weight=dict(required=False, type='str', + choices=['priority', 'no-priority']), + pn_min_bw_guarantee=dict(required=False, type='str'), + ), + required_if=( + ['state', 'update', ['pn_cos', 'pn_port']], + ), + required_one_of=[['pn_max_bw_limit', 'pn_min_bw_guarantee', 'pn_weight']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + max_bw_limit = module.params['pn_max_bw_limit'] + cos = module.params['pn_cos'] + port = module.params['pn_port'] + weight = module.params['pn_weight'] + min_bw_guarantee = module.params['pn_min_bw_guarantee'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'port-cos-bw-modify': + cli += ' %s ' % command + if max_bw_limit: + cli += ' max-bw-limit ' + max_bw_limit + if cos: + cli += ' cos ' + cos + if port: + cli += ' port ' + port + if weight: + cli += ' weight ' + weight + if min_bw_guarantee: + cli += ' min-bw-guarantee ' + min_bw_guarantee + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_rate_setting.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_rate_setting.py new file mode 100644 index 00000000..fbe0b9f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_port_cos_rate_setting.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_port_cos_rate_setting +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify port-cos-rate-setting +description: + - This modules can be used to update the port cos rate limit. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the port-cos-rate-setting. + required: true + type: str + choices: ['update'] + pn_cos0_rate: + description: + - cos0 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos1_rate: + description: + - cos1 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos2_rate: + description: + - cos2 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos3_rate: + description: + - cos3 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos4_rate: + description: + - cos4 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos5_rate: + description: + - cos5 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos6_rate: + description: + - cos6 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_cos7_rate: + description: + - cos7 rate limit (pps) unlimited or 0 to 10000000. + required: false + type: str + pn_port: + description: + - port. + required: false + type: str + choices: ['control-port', 'data-port', 'span-ports'] +''' + +EXAMPLES = """ +- name: Port cos rate modify + community.network.pn_port_cos_rate_setting: + pn_cliswitch: "sw01" + state: "update" + pn_port: "control-port" + pn_cos1_rate: "1000" + pn_cos5_rate: "1000" + pn_cos2_rate: "1000" + pn_cos0_rate: "1000" + +- name: Port cos rate modify + community.network.pn_port_cos_rate_setting: + pn_cliswitch: "sw01" + state: "update" + pn_port: "data-port" + pn_cos1_rate: "2000" + pn_cos5_rate: "2000" + pn_cos2_rate: "2000" + pn_cos0_rate: "2000" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the port-cos-rate-setting command. + returned: always + type: list +stderr: + description: set of error responses from the port-cos-rate-setting command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='port-cos-rate-setting-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_cos1_rate=dict(required=False, type='str'), + pn_cos5_rate=dict(required=False, type='str'), + pn_cos2_rate=dict(required=False, type='str'), + pn_cos0_rate=dict(required=False, type='str'), + pn_cos6_rate=dict(required=False, type='str'), + pn_cos3_rate=dict(required=False, type='str'), + pn_cos4_rate=dict(required=False, type='str'), + pn_cos7_rate=dict(required=False, type='str'), + pn_port=dict(required=False, type='str', + choices=['control-port', 'data-port', 'span-ports']), + ), + required_if=( + ['state', 'update', ['pn_port']], + ), + required_one_of=[['pn_cos0_rate', + 'pn_cos1_rate', + 'pn_cos2_rate', + 'pn_cos3_rate', + 'pn_cos4_rate', + 'pn_cos5_rate', + 'pn_cos6_rate', + 'pn_cos7_rate']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + cos1_rate = module.params['pn_cos1_rate'] + cos5_rate = module.params['pn_cos5_rate'] + cos2_rate = module.params['pn_cos2_rate'] + cos0_rate = module.params['pn_cos0_rate'] + cos6_rate = module.params['pn_cos6_rate'] + cos3_rate = module.params['pn_cos3_rate'] + cos4_rate = module.params['pn_cos4_rate'] + cos7_rate = module.params['pn_cos7_rate'] + port = module.params['pn_port'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'port-cos-rate-setting-modify': + cli += ' %s ' % command + if cos1_rate: + cli += ' cos1-rate ' + cos1_rate + if cos5_rate: + cli += ' cos5-rate ' + cos5_rate + if cos2_rate: + cli += ' cos2-rate ' + cos2_rate + if cos0_rate: + cli += ' cos0-rate ' + cos0_rate + if cos6_rate: + cli += ' cos6-rate ' + cos6_rate + if cos3_rate: + cli += ' cos3-rate ' + cos3_rate + if cos4_rate: + cli += ' cos4-rate ' + cos4_rate + if cos7_rate: + cli += ' cos7-rate ' + cos7_rate + if port: + cli += ' port ' + port + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list.py new file mode 100644 index 00000000..04baeb98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list.py @@ -0,0 +1,159 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_prefix_list +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete prefix-list +description: + - This module can be used to create or delete prefix list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create prefix-list and + C(absent) to delete prefix-list. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_name: + description: + - Prefix List Name. + required: true + type: str + pn_scope: + description: + - scope of prefix-list. + required: false + type: str + choices: ['local', 'fabric'] +''' + +EXAMPLES = """ +- name: Create prefix list + community.network.pn_prefix_list: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_scope: "local" + state: "present" + +- name: Delete prefix list + community.network.pn_prefix_list: + pn_cliswitch: "sw01" + pn_name: "foo" + state: "absent" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the prefix-list command. + returned: always + type: list +stderr: + description: set of error responses from the prefix-list command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the prefix-list-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' prefix-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='prefix-list-create', + absent='prefix-list-delete' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_name=dict(required=True, type='str'), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + scope = module.params['pn_scope'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command == 'prefix-list-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='prefix-list with name %s does not exist' % name + ) + else: + if command == 'prefix-list-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='prefix list with name %s already exists' % name + ) + cli += ' scope %s ' % scope + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list_network.py new file mode 100644 index 00000000..e024e5ad --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_prefix_list_network.py @@ -0,0 +1,185 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_prefix_list_network +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove prefix-list-network +description: + - This module is used to add network associated with prefix list + and remove networks associated with prefix list. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create + prefix-list-network and C(absent) to delete prefix-list-network. + required: true + type: str + choices: ['present', 'absent'] + pn_netmask: + description: + - netmask of the network associated the prefix list. + required: false + type: str + pn_name: + description: + - Prefix List Name. + required: false + type: str + pn_network: + description: + - network associated with the prefix list. + required: false + type: str +''' + +EXAMPLES = """ +- name: Prefix list network add + community.network.pn_prefix_list_network: + pn_cliswitch: "sw01" + pn_name: "foo" + pn_network: "172.16.3.1" + pn_netmask: "24" + state: "present" + +- name: Prefix list network remove + community.network.pn_prefix_list_network: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + pn_network: "172.16.3.1" + pn_netmask: "24" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the prefix-list-network command. + returned: always + type: list +stderr: + description: set of error responses from the prefix-list-network command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using prefix-list-network-show command. + If network exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + network = module.params['pn_network'] + show = cli + + cli += ' prefix-list-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if name not in out.split()[-1]: + module.fail_json( + failed=True, + msg='Prefix list with name %s does not exists' % name + ) + + cli = show + cli += ' prefix-list-network-show name %s format network no-show-headers' % name + rc, out, err = run_commands(module, cli) + + if out: + out = out.split()[-1] + return True if network in out.split('/')[0] else False + + return False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='prefix-list-network-add', + absent='prefix-list-network-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_netmask=dict(required=False, type='str'), + pn_name=dict(required=False, type='str'), + pn_network=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_network", "pn_netmask"]], + ["state", "absent", ["pn_name", "pn_network", "pn_netmask"]], + ), + required_together=( + ["pn_network", "pn_netmask"], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + netmask = module.params['pn_netmask'] + name = module.params['pn_name'] + network = module.params['pn_network'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NETWORK_EXISTS = check_cli(module, cli) + cli += ' %s ' % command + + if command == 'prefix-list-network-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Prefix list with network %s does not exist' % network + ) + + if command == 'prefix-list-network-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Prefix list with network %s already exists' % network + ) + + if name: + cli += ' name ' + name + if network: + cli += ' network ' + network + if netmask: + cli += ' netmask ' + netmask + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_role.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_role.py new file mode 100644 index 00000000..78becdbd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_role.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_role +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete/modify role +description: + - This module can be used to create, delete and modify user roles. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create role and + C(absent) to delete role and C(update) to modify role. + required: true + type: str + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - local or fabric. + required: false + type: str + choices: ['local', 'fabric'] + pn_access: + description: + - type of access. + required: false + type: str + choices: ['read-only', 'read-write'] + pn_shell: + description: + - allow shell command. + required: false + type: bool + pn_sudo: + description: + - allow sudo from shell. + required: false + type: bool + pn_running_config: + description: + - display running configuration of switch. + required: false + type: bool + pn_name: + description: + - role name. + required: true + type: str + pn_delete_from_users: + description: + - delete from users. + required: false + type: bool +''' + +EXAMPLES = """ +- name: Role create + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'present' + pn_name: 'foo' + pn_scope: 'local' + pn_access: 'read-only' + +- name: Role delete + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'foo' + +- name: Role modify + community.network.pn_role: + pn_cliswitch: 'sw01' + state: 'update' + pn_name: 'foo' + pn_access: 'read-write' + pn_sudo: true + pn_shell: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the role command. + returned: always + type: list +stderr: + description: set of error responses from the role command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the role-show command. + If a role with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + role_name = module.params['pn_name'] + + cli += ' role-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if role_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='role-create', + absent='role-delete', + update='role-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_access=dict(required=False, type='str', + choices=['read-only', 'read-write']), + pn_shell=dict(required=False, type='bool'), + pn_sudo=dict(required=False, type='bool'), + pn_running_config=dict(required=False, type='bool'), + pn_name=dict(required=False, type='str'), + pn_delete_from_users=dict(required=False, type='bool'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + access = module.params['pn_access'] + shell = module.params['pn_shell'] + sudo = module.params['pn_sudo'] + running_config = module.params['pn_running_config'] + name = module.params['pn_name'] + delete_from_users = module.params['pn_delete_from_users'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + ROLE_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if shell is (False or '') and sudo is True: + module.fail_json( + failed=True, + msg='sudo access requires shell access' + ) + + if command == 'role-modify': + if ROLE_EXISTS is False: + module.fail_json( + failed=True, + msg='Role with name %s does not exist' % name + ) + + if command == 'role-delete': + if ROLE_EXISTS is False: + module.exit_json( + skipped=True, + msg='Role with name %s does not exist' % name + ) + + if command == 'role-create': + if ROLE_EXISTS is True: + module.exit_json( + skipped=True, + msg='Role with name %s already exists' % name + ) + + if scope: + cli += ' scope ' + scope + + if command != 'role-delete': + if access: + cli += ' access ' + access + + cli += booleanArgs(shell, 'shell', 'no-shell') + cli += booleanArgs(sudo, 'sudo', 'no-sudo') + cli += booleanArgs(running_config, 'running-config', 'no-running-config') + + if command == 'role-modify': + if delete_from_users: + cli += ' delete-from-users ' + delete_from_users + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_show.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_show.py new file mode 100644 index 00000000..6de9d250 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_show.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +""" PN CLI show commands """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_show +author: "Pluribus Networks (@amitsi)" +short_description: Run show commands on nvOS device. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute show command in the nodes and returns the results + read from the device. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + pn_command: + description: + - The C(pn_command) takes a CLI show command as value. + required: true + pn_parameters: + description: + - Display output using a specific parameter. Use 'all' to display + possible output. List of comma separated parameters. + default: 'all' + pn_options: + description: + - Specify formatting options. +''' + +EXAMPLES = """ +- name: Run the vlan-show command + community.network.pn_show: + pn_command: 'vlan-show' + pn_parameters: id,scope,ports + pn_options: 'layout vertical' + +- name: Run the vlag-show command + community.network.pn_show: + pn_command: 'vlag-show' + pn_parameters: 'id,name,cluster,mode' + pn_options: 'no-show-headers' + +- name: Run the cluster-show command + community.network.pn_show: + pn_command: 'cluster-show' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the show command. + returned: always + type: list +stderr: + description: The set of error responses from the show command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused any change on the target. + returned: always(False) + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch: + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + command = module.params['pn_command'] + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + msg='%s: ' % command, + stderr=err.strip(), + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + msg='%s: ' % command, + stdout=out.strip(), + changed=False + ) + + else: + module.exit_json( + command=cli, + msg='%s: Nothing to display!!!' % command, + changed=False + ) + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=True, type='str'), + pn_clipassword=dict(required=True, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str'), + pn_command=dict(required=True, type='str'), + pn_parameters=dict(default='all', type='str'), + pn_options=dict(type='str') + ) + ) + + # Accessing the arguments + command = module.params['pn_command'] + parameters = module.params['pn_parameters'] + options = module.params['pn_options'] + + # Building the CLI command string + cli = pn_cli(module) + + cli += ' %s format %s ' % (command, parameters) + + if options: + cli += options + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_community.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_community.py new file mode 100644 index 00000000..e42e925f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_community.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_community +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete snmp-community +description: + - This module can be used to create SNMP communities for SNMPv1 or + delete SNMP communities for SNMPv1 or modify SNMP communities for SNMPv1. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + state: + description: + - State the action to perform. Use C(present) to create snmp-community and + C(absent) to delete snmp-community C(update) to update snmp-community. + required: true + type: str + choices: ['present', 'absent', 'update'] + pn_community_type: + description: + - community type. + type: str + choices: ['read-only', 'read-write'] + pn_community_string: + description: + - community name. + type: str +''' + +EXAMPLES = """ +- name: Create snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "present" + pn_community_string: "foo" + pn_community_type: "read-write" + +- name: Delete snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "absent" + pn_community_string: "foo" + +- name: Modify snmp community + community.network.pn_snmp_community: + pn_cliswitch: "sw01" + state: "update" + pn_community_string: "foo" + pn_community_type: "read-only" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-community command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-community command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-community-show command. + If a user with given name exists, return as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + comm_str = module.params['pn_community_string'] + + cli += ' snmp-community-show format community-string no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if comm_str in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-community-create', + absent='snmp-community-delete', + update='snmp-community-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_community_type=dict(required=False, type='str', + choices=['read-only', 'read-write']), + pn_community_string=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_community_type", "pn_community_string"]], + ["state", "absent", ["pn_community_string"]], + ["state", "update", ["pn_community_type", "pn_community_string"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + community_type = module.params['pn_community_type'] + comm_str = module.params['pn_community_string'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + COMMUNITY_EXISTS = check_cli(module, cli) + + if command == 'snmp-community-modify': + if COMMUNITY_EXISTS is False: + module.fail_json( + failed=True, + msg='snmp community name %s does not exist' % comm_str + ) + + if command == 'snmp-community-delete': + if COMMUNITY_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp community name %s does not exist' % comm_str + ) + + if command == 'snmp-community-create': + if COMMUNITY_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp community with name %s already exists' % comm_str + ) + + cli += ' %s community-string %s ' % (command, comm_str) + + if command != 'snmp-community-delete' and community_type: + cli += ' community-type ' + community_type + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_trap_sink.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_trap_sink.py new file mode 100644 index 00000000..f60ff96b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_trap_sink.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_trap_sink +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete snmp-trap-sink +description: + - This module can be used to create a SNMP trap sink and delete a SNMP trap sink. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to create snmp-trap-sink and + C(absent) to delete snmp-trap-sink. + required: true + type: str + choices: ['present', 'absent'] + pn_dest_host: + description: + - destination host. + type: str + pn_community: + description: + - community type. + type: str + pn_dest_port: + description: + - destination port. + type: str + default: '162' + pn_type: + description: + - trap type. + type: str + choices: ['TRAP_TYPE_V1_TRAP', 'TRAP_TYPE_V2C_TRAP', 'TRAP_TYPE_V2_INFORM'] + default: 'TRAP_TYPE_V2C_TRAP' +''' + +EXAMPLES = """ +- name: Snmp trap sink functionality + community.network.pn_snmp_trap_sink: + pn_cliswitch: "sw01" + state: "present" + pn_community: "foo" + pn_type: "TRAP_TYPE_V2_INFORM" + pn_dest_host: "192.168.67.8" + +- name: Snmp trap sink functionality + community.network.pn_snmp_trap_sink: + pn_cliswitch: "sw01" + state: "absent" + pn_community: "foo" + pn_type: "TRAP_TYPE_V2_INFORM" + pn_dest_host: "192.168.67.8" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-trap-sink command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-trap-sink command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-trap-sink-show command. + If a trap with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + community = module.params['pn_community'] + dest_host = module.params['pn_dest_host'] + + show = cli + cli += ' snmp-community-show format community-string no-show-headers' + rc, out, err = run_commands(module, cli) + + if out: + out = out.split() + + if community in out: + cli = show + cli += ' snmp-trap-sink-show community %s format type,dest-host no-show-headers' % community + rc, out, err = run_commands(module, cli) + + if out: + out = out.split() + + return True if dest_host in out else False + else: + return None + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-trap-sink-create', + absent='snmp-trap-sink-delete' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_dest_host=dict(required=False, type='str'), + pn_community=dict(required=False, type='str'), + pn_dest_port=dict(required=False, type='str', default='162'), + pn_type=dict(required=False, type='str', + choices=['TRAP_TYPE_V1_TRAP', + 'TRAP_TYPE_V2C_TRAP', + 'TRAP_TYPE_V2_INFORM'], + default='TRAP_TYPE_V2C_TRAP'), + ), + required_if=( + ["state", "present", ["pn_community", "pn_dest_host"]], + ["state", "absent", ["pn_community", "pn_dest_host"]], + ) + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + dest_host = module.params['pn_dest_host'] + community = module.params['pn_community'] + dest_port = module.params['pn_dest_port'] + pn_type = module.params['pn_type'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VALUE_EXISTS = check_cli(module, cli) + cli += ' %s ' % command + + if command == 'snmp-trap-sink-create': + if VALUE_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp trap sink already exists' + ) + if VALUE_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp community does not exists to create trap sink' + ) + if pn_type: + cli += ' type ' + pn_type + if dest_host: + cli += ' dest-host ' + dest_host + if community: + cli += ' community ' + community + if dest_port: + cli += ' dest-port ' + dest_port + + if command == 'snmp-trap-sink-delete': + if VALUE_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp community does not exists to delete trap sink' + ) + if VALUE_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp-trap-sink with community %s does not exist with dest-host %s ' % (community, dest_host) + ) + if community: + cli += ' community ' + community + if dest_host: + cli += ' dest-host ' + dest_host + if dest_port: + cli += ' dest-port ' + dest_port + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_vacm.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_vacm.py new file mode 100644 index 00000000..62c33a6c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_snmp_vacm.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_snmp_vacm +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete snmp-vacm +description: + - This module can be used to create View Access Control Models (VACM), + modify VACM and delete VACM. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(present) to create snmp-vacm and + C(absent) to delete snmp-vacm and C(update) to modify snmp-vacm. + type: str + required: true + choices: ['present', 'absent', 'update'] + pn_oid_restrict: + description: + - restrict OID. + type: str + pn_priv: + description: + - privileges. + type: bool + pn_auth: + description: + - authentication required. + type: bool + pn_user_type: + description: + - SNMP user type. + type: str + choices: ['rouser', 'rwuser'] + pn_user_name: + description: + - SNMP administrator name. + type: str +''' + +EXAMPLES = """ +- name: Create snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "present" + pn_user_name: "foo" + pn_user_type: "rouser" + +- name: Update snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "update" + pn_user_name: "foo" + pn_user_type: "rwuser" + +- name: Delete snmp vacm + community.network.pn_snmp_vacm: + pn_cliswitch: "sw01" + state: "absent" + pn_user_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the snmp-vacm command. + returned: always + type: list +stderr: + description: set of error responses from the snmp-vacm command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the snmp-vacm-show command. + If a user with given name exists, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + user_name = module.params['pn_user_name'] + show = cli + + cli += ' snmp-user-show format user-name no-show-headers' + rc, out, err = run_commands(module, cli) + + if out and user_name in out.split(): + pass + else: + return None + + cli = show + cli += ' snmp-vacm-show format user-name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if user_name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='snmp-vacm-create', + absent='snmp-vacm-delete', + update='snmp-vacm-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_oid_restrict=dict(required=False, type='str'), + pn_priv=dict(required=False, type='bool'), + pn_auth=dict(required=False, type='bool'), + pn_user_type=dict(required=False, type='str', + choices=['rouser', 'rwuser']), + pn_user_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_user_name"]], + ["state", "absent", ["pn_user_name"]], + ["state", "update", ["pn_user_name"]] + ) + + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + oid_restrict = module.params['pn_oid_restrict'] + priv = module.params['pn_priv'] + auth = module.params['pn_auth'] + user_type = module.params['pn_user_type'] + user_name = module.params['pn_user_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s user-name %s ' % (command, user_name) + + if command == 'snmp-vacm-modify': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='snmp vacm with name %s does not exists' % user_name + ) + + if command == 'snmp-vacm-delete': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='snmp vacm with name %s does not exist' % user_name + ) + + if command == 'snmp-vacm-create': + if USER_EXISTS is None: + module.fail_json( + failed=True, + msg='snmp user with name %s does not exists' % user_name + ) + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='snmp vacm with name %s already exists' % user_name + ) + + if command != 'snmp-vacm-delete': + if oid_restrict: + cli += ' oid-restrict ' + oid_restrict + if user_type: + cli += ' user-type ' + user_type + + cli += booleanArgs(auth, 'auth', 'no-auth') + cli += booleanArgs(priv, 'priv', 'no-priv') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp.py new file mode 100644 index 00000000..9b7b8fd4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp.py @@ -0,0 +1,199 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_stp +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify stp +description: + - This module can be used to modify Spanning Tree Protocol parameters. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to stp. + type: str + required: true + choices: ['update'] + pn_hello_time: + description: + - STP hello time between 1 and 10 secs. + type: str + default: '2' + pn_enable: + description: + - enable or disable STP + type: bool + pn_root_guard_wait_time: + description: + - root guard wait time between 0 and 300 secs. 0 to disable wait. + type: str + default: '20' + pn_bpdus_bridge_ports: + description: + - BPDU packets to bridge specific port. + type: bool + pn_mst_max_hops: + description: + - maximum hop count for mstp bpdu. + type: str + default: '20' + pn_bridge_id: + description: + - STP bridge id. + type: str + pn_max_age: + description: + - maximum age time between 6 and 40 secs. + type: str + default: '20' + pn_stp_mode: + description: + - STP mode. + type: str + choices: ['rstp', 'mstp'] + pn_mst_config_name: + description: + - Name for MST Configuration Instance. + type: str + pn_forwarding_delay: + description: + - STP forwarding delay between 4 and 30 secs. + type: str + default: '15' + pn_bridge_priority: + description: + - STP bridge priority. + type: str + default: '32768' +''' + +EXAMPLES = """ +- name: Modify stp + community.network.pn_stp: + pn_cliswitch: "sw01" + state: "update" + pn_hello_time: "3" + pn_stp_mode: "rstp" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the stp command. + returned: always + type: list +stderr: + description: set of error responses from the stp command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='stp-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_hello_time=dict(required=False, type='str', default='2'), + pn_enable=dict(required=False, type='bool'), + pn_root_guard_wait_time=dict(required=False, type='str', default='20'), + pn_bpdus_bridge_ports=dict(required=False, type='bool'), + pn_mst_max_hops=dict(required=False, type='str', default='20'), + pn_bridge_id=dict(required=False, type='str'), + pn_max_age=dict(required=False, type='str', default='20'), + pn_stp_mode=dict(required=False, type='str', + choices=['rstp', 'mstp']), + pn_mst_config_name=dict(required=False, type='str'), + pn_forwarding_delay=dict(required=False, type='str', default='15'), + pn_bridge_priority=dict(required=False, type='str', default='32768'), + ), + required_one_of=[['pn_enable', 'pn_hello_time', + 'pn_root_guard_wait_time', + 'pn_bpdus_bridge_ports', + 'pn_mst_max_hops', + 'pn_bridge_id', + 'pn_max_age', + 'pn_stp_mode', + 'pn_mst_config_name', + 'pn_forwarding_delay', + 'pn_bridge_priority']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + hello_time = module.params['pn_hello_time'] + enable = module.params['pn_enable'] + root_guard_wait_time = module.params['pn_root_guard_wait_time'] + bpdus_bridge_ports = module.params['pn_bpdus_bridge_ports'] + mst_max_hops = module.params['pn_mst_max_hops'] + bridge_id = module.params['pn_bridge_id'] + max_age = module.params['pn_max_age'] + stp_mode = module.params['pn_stp_mode'] + mst_config_name = module.params['pn_mst_config_name'] + forwarding_delay = module.params['pn_forwarding_delay'] + bridge_priority = module.params['pn_bridge_priority'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'stp-modify': + cli += ' %s ' % command + if hello_time: + cli += ' hello-time ' + hello_time + if root_guard_wait_time: + cli += ' root-guard-wait-time ' + root_guard_wait_time + if mst_max_hops: + cli += ' mst-max-hops ' + mst_max_hops + if bridge_id: + cli += ' bridge-id ' + bridge_id + if max_age: + cli += ' max-age ' + max_age + if stp_mode: + cli += ' stp-mode ' + stp_mode + if mst_config_name: + cli += ' mst-config-name ' + mst_config_name + if forwarding_delay: + cli += ' forwarding-delay ' + forwarding_delay + if bridge_priority: + cli += ' bridge-priority ' + bridge_priority + + cli += booleanArgs(enable, 'enable', 'disable') + cli += booleanArgs(bpdus_bridge_ports, 'bpdus-bridge-ports', 'bpdus-all-ports') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp_port.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp_port.py new file mode 100644 index 00000000..81b4dde8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_stp_port.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_stp_port +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify stp-port. +description: + - This module can be used modify Spanning Tree Protocol (STP) parameters on ports. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(update) to update stp-port. + type: str + required: true + choices: ['update'] + pn_priority: + description: + - STP port priority from 0 to 240. + type: str + default: '128' + pn_cost: + description: + - STP port cost from 1 to 200000000. + type: str + default: '2000' + pn_root_guard: + description: + - STP port Root guard. + type: bool + pn_filter: + description: + - STP port filters BPDUs. + type: bool + pn_edge: + description: + - STP port is an edge port. + type: bool + pn_bpdu_guard: + description: + - STP port BPDU guard. + type: bool + pn_port: + description: + - STP port. + type: str + pn_block: + description: + - Specify if a STP port blocks BPDUs. + type: bool +''' + +EXAMPLES = """ +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_filter: True + pn_priority: '144' + +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_cost: "200" + +- name: Modify stp port + community.network.pn_stp_port: + pn_cliswitch: "sw01" + state: "update" + pn_port: "1" + pn_edge: True + pn_cost: "200" + +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the stp-port command. + returned: always + type: list +stderr: + description: set of error responses from the stp-port command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='stp-port-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_priority=dict(required=False, type='str', default='128'), + pn_cost=dict(required=False, type='str', default='2000'), + pn_root_guard=dict(required=False, type='bool'), + pn_filter=dict(required=False, type='bool'), + pn_edge=dict(required=False, type='bool'), + pn_bpdu_guard=dict(required=False, type='bool'), + pn_port=dict(required=False, type='str'), + pn_block=dict(required=False, type='bool'), + ), + required_if=( + ["state", "update", ["pn_port"]], + ), + required_one_of=( + ['pn_cost', 'pn_root_guard', 'pn_filter', + 'pn_edge', 'pn_bpdu_guard', 'pn_block'], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + priority = module.params['pn_priority'] + cost = module.params['pn_cost'] + root_guard = module.params['pn_root_guard'] + pn_filter = module.params['pn_filter'] + edge = module.params['pn_edge'] + bpdu_guard = module.params['pn_bpdu_guard'] + port = module.params['pn_port'] + block = module.params['pn_block'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'stp-port-modify': + cli += ' %s ' % command + if priority and (int(priority) % 16 == 0 and int(priority) < 240): + cli += ' priority ' + priority + else: + module.fail_json( + failed=True, + msg='Priority must be increment of 16 and should be less that 240' + ) + if cost and (int(cost) < 200000000): + cli += ' cost ' + cost + else: + module.fail_json( + failed=True, + msg='cost must be between 1 and 200000000' + ) + if port: + cli += ' port ' + port + + cli += booleanArgs(root_guard, 'root-guard', 'no-root-guard') + cli += booleanArgs(pn_filter, 'filter', 'no-filter') + cli += booleanArgs(edge, 'edge', 'no-edge') + cli += booleanArgs(bpdu_guard, 'bpdu-guard', 'no-bpdu-guard') + cli += booleanArgs(block, 'block', 'no-block') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_switch_setup.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_switch_setup.py new file mode 100644 index 00000000..21f6313d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_switch_setup.py @@ -0,0 +1,407 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_switch_setup +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify switch-setup +description: + - This module can be used to modify switch setup. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify the switch-setup. + required: true + type: str + choices: ['update'] + pn_force: + description: + - Force analytics-store change even if it involves removing data. + required: false + type: bool + pn_dns_ip: + description: + - DNS IP address. + required: false + type: str + pn_mgmt_netmask: + description: + - Netmask. + required: false + type: str + pn_gateway_ip6: + description: + - Gateway IPv6 address. + required: false + type: str + pn_in_band_ip6_assign: + description: + - Data IPv6 address assignment. + required: false + type: str + choices: ['none', 'autoconf'] + pn_domain_name: + description: + - Domain name. + required: false + type: str + pn_timezone: + description: + - Timezone to be configured. + required: false + type: str + pn_in_band_netmask: + description: + - Data in-band netmask. + required: false + type: str + pn_in_band_ip6: + description: + - Data in-band IPv6 address. + required: false + type: str + pn_in_band_netmask_ip6: + description: + - Data in-band IPv6 netmask. + required: false + type: str + pn_motd: + description: + - Message of the Day. + required: false + type: str + pn_loopback_ip6: + description: + - loopback IPv6 address. + required: false + type: str + pn_mgmt_ip6_assignment: + description: + - IPv6 address assignment. + required: false + choices: ['none', 'autoconf'] + pn_ntp_secondary_server: + description: + - Secondary NTP server. + required: false + type: str + pn_in_band_ip: + description: + - data in-band IP address. + required: false + type: str + pn_eula_accepted: + description: + - Accept EULA. + required: false + type: str + choices: ['true', 'false'] + pn_mgmt_ip: + description: + - Management IP address. + required: false + type: str + pn_ntp_server: + description: + - NTP server. + required: false + type: str + pn_mgmt_ip_assignment: + description: + - IP address assignment. + required: false + type: str + choices: ['none', 'dhcp'] + pn_date: + description: + - Date. + required: false + type: str + pn_password: + description: + - plain text password. + required: false + type: str + pn_banner: + description: + - Banner to display on server-switch. + required: false + type: str + pn_loopback_ip: + description: + - loopback IPv4 address. + required: false + type: str + pn_dns_secondary_ip: + description: + - secondary DNS IP address. + required: false + type: str + pn_switch_name: + description: + - switch name. + required: false + type: str + pn_eula_timestamp: + description: + - EULA timestamp. + required: false + type: str + pn_mgmt_netmask_ip6: + description: + - IPv6 netmask. + required: false + type: str + pn_enable_host_ports: + description: + - Enable host ports by default. + required: false + type: bool + pn_mgmt_ip6: + description: + - IPv6 address. + required: false + type: str + pn_analytics_store: + description: + - type of disk storage for analytics. + required: false + type: str + choices: ['default', 'optimized'] + pn_gateway_ip: + description: + - gateway IPv4 address. + required: false + type: str +''' + +EXAMPLES = """ +- name: Modify switch + community.network.pn_switch_setup: + pn_cliswitch: "sw01" + state: "update" + pn_timezone: "America/New_York" + pn_in_band_ip: "20.20.1.1" + pn_in_band_netmask: "24" + +- name: Modify switch + community.network.pn_switch_setup: + pn_cliswitch: "sw01" + state: "update" + pn_in_band_ip6: "2001:0db8:85a3::8a2e:0370:7334" + pn_in_band_netmask_ip6: "127" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the switch-setup command. + returned: always + type: list +stderr: + description: set of error responses from the switch-setup command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, booleanArgs, run_cli + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='switch-setup-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=['update']), + pn_force=dict(required=False, type='bool'), + pn_dns_ip=dict(required=False, type='str'), + pn_mgmt_netmask=dict(required=False, type='str'), + pn_gateway_ip6=dict(required=False, type='str'), + pn_in_band_ip6_assign=dict(required=False, type='str', + choices=['none', 'autoconf']), + pn_domain_name=dict(required=False, type='str'), + pn_timezone=dict(required=False, type='str'), + pn_in_band_netmask=dict(required=False, type='str'), + pn_in_band_ip6=dict(required=False, type='str'), + pn_in_band_netmask_ip6=dict(required=False, type='str'), + pn_motd=dict(required=False, type='str'), + pn_loopback_ip6=dict(required=False, type='str'), + pn_mgmt_ip6_assignment=dict(required=False, type='str', + choices=['none', 'autoconf']), + pn_ntp_secondary_server=dict(required=False, type='str'), + pn_in_band_ip=dict(required=False, type='str'), + pn_eula_accepted=dict(required=False, type='str', + choices=['true', 'false']), + pn_mgmt_ip=dict(required=False, type='str'), + pn_ntp_server=dict(required=False, type='str'), + pn_mgmt_ip_assignment=dict(required=False, type='str', + choices=['none', 'dhcp']), + pn_date=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_banner=dict(required=False, type='str'), + pn_loopback_ip=dict(required=False, type='str'), + pn_dns_secondary_ip=dict(required=False, type='str'), + pn_switch_name=dict(required=False, type='str'), + pn_eula_timestamp=dict(required=False, type='str'), + pn_mgmt_netmask_ip6=dict(required=False, type='str'), + pn_enable_host_ports=dict(required=False, type='bool'), + pn_mgmt_ip6=dict(required=False, type='str'), + pn_analytics_store=dict(required=False, type='str', + choices=['default', 'optimized']), + pn_gateway_ip=dict(required=False, type='str'), + ), + required_one_of=[['pn_force', 'pn_dns_ip', 'pn_mgmt_netmask', + 'pn_gateway_ip6', 'pn_in_band_ip6_assign', + 'pn_domain_name', 'pn_timezone', + 'pn_in_band_netmask', 'pn_in_band_ip6', + 'pn_in_band_netmask_ip6', 'pn_motd', + 'pn_loopback_ip6', 'pn_mgmt_ip6_assignment', + 'pn_ntp_secondary_server', 'pn_in_band_ip', + 'pn_eula_accepted', 'pn_mgmt_ip', + 'pn_ntp_server', 'pn_mgmt_ip_assignment', + 'pn_date', 'pn_password', + 'pn_banner', 'pn_loopback_ip', + 'pn_dns_secondary_ip', 'pn_switch_name', + 'pn_eula_timestamp', 'pn_mgmt_netmask_ip6', + 'pn_enable_host_ports', 'pn_mgmt_ip6', + 'pn_analytics_store', 'pn_gateway_ip']], + required_together=[['pn_in_band_ip6', 'pn_in_band_netmask_ip6'], + ['pn_in_band_ip', 'pn_in_band_netmask'], + ['pn_mgmt_ip', 'pn_mgmt_netmask'], + ['pn_mgmt_ip6', 'pn_mgmt_netmask_ip6']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + force = module.params['pn_force'] + dns_ip = module.params['pn_dns_ip'] + mgmt_netmask = module.params['pn_mgmt_netmask'] + gateway_ip6 = module.params['pn_gateway_ip6'] + in_band_ip6_assign = module.params['pn_in_band_ip6_assign'] + domain_name = module.params['pn_domain_name'] + timezone = module.params['pn_timezone'] + in_band_netmask = module.params['pn_in_band_netmask'] + in_band_ip6 = module.params['pn_in_band_ip6'] + in_band_netmask_ip6 = module.params['pn_in_band_netmask_ip6'] + motd = module.params['pn_motd'] + loopback_ip6 = module.params['pn_loopback_ip6'] + mgmt_ip6_assignment = module.params['pn_mgmt_ip6_assignment'] + ntp_secondary_server = module.params['pn_ntp_secondary_server'] + in_band_ip = module.params['pn_in_band_ip'] + eula_accepted = module.params['pn_eula_accepted'] + mgmt_ip = module.params['pn_mgmt_ip'] + ntp_server = module.params['pn_ntp_server'] + mgmt_ip_assignment = module.params['pn_mgmt_ip_assignment'] + date = module.params['pn_date'] + password = module.params['pn_password'] + banner = module.params['pn_banner'] + loopback_ip = module.params['pn_loopback_ip'] + dns_secondary_ip = module.params['pn_dns_secondary_ip'] + switch_name = module.params['pn_switch_name'] + eula_timestamp = module.params['pn_eula_timestamp'] + mgmt_netmask_ip6 = module.params['pn_mgmt_netmask_ip6'] + enable_host_ports = module.params['pn_enable_host_ports'] + mgmt_ip6 = module.params['pn_mgmt_ip6'] + analytics_store = module.params['pn_analytics_store'] + gateway_ip = module.params['pn_gateway_ip'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'switch-setup-modify': + cli += ' %s ' % command + if dns_ip: + cli += ' dns-ip ' + dns_ip + if mgmt_netmask: + cli += ' mgmt-netmask ' + mgmt_netmask + if gateway_ip6: + cli += ' gateway-ip6 ' + gateway_ip6 + if in_band_ip6_assign: + cli += ' in-band-ip6-assign ' + in_band_ip6_assign + if domain_name: + cli += ' domain-name ' + domain_name + if timezone: + cli += ' timezone ' + timezone + if in_band_netmask: + cli += ' in-band-netmask ' + in_band_netmask + if in_band_ip6: + cli += ' in-band-ip6 ' + in_band_ip6 + if in_band_netmask_ip6: + cli += ' in-band-netmask-ip6 ' + in_band_netmask_ip6 + if motd: + cli += ' motd ' + motd + if loopback_ip6: + cli += ' loopback-ip6 ' + loopback_ip6 + if mgmt_ip6_assignment: + cli += ' mgmt-ip6-assignment ' + mgmt_ip6_assignment + if ntp_secondary_server: + cli += ' ntp-secondary-server ' + ntp_secondary_server + if in_band_ip: + cli += ' in-band-ip ' + in_band_ip + if eula_accepted: + cli += ' eula-accepted ' + eula_accepted + if mgmt_ip: + cli += ' mgmt-ip ' + mgmt_ip + if ntp_server: + cli += ' ntp-server ' + ntp_server + if mgmt_ip_assignment: + cli += ' mgmt-ip-assignment ' + mgmt_ip_assignment + if date: + cli += ' date ' + date + if password: + cli += ' password ' + password + if banner: + cli += ' banner ' + banner + if loopback_ip: + cli += ' loopback-ip ' + loopback_ip + if dns_secondary_ip: + cli += ' dns-secondary-ip ' + dns_secondary_ip + if switch_name: + cli += ' switch-name ' + switch_name + if eula_timestamp: + cli += ' eula_timestamp ' + eula_timestamp + if mgmt_netmask_ip6: + cli += ' mgmt-netmask-ip6 ' + mgmt_netmask_ip6 + if mgmt_ip6: + cli += ' mgmt-ip6 ' + mgmt_ip6 + if analytics_store: + cli += ' analytics-store ' + analytics_store + if gateway_ip: + cli += ' gateway-ip ' + gateway_ip + + cli += booleanArgs(force, 'force', 'no-force') + cli += booleanArgs(enable_host_ports, 'enable-host-ports', 'disable-host-ports') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_trunk.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_trunk.py new file mode 100644 index 00000000..f7144347 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_trunk.py @@ -0,0 +1,462 @@ +#!/usr/bin/python +""" PN CLI trunk-create/trunk-delete/trunk-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_trunk +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify a trunk. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute trunk-create or trunk-delete command. + - Trunks can be used to aggregate network links at Layer 2 on the local + switch. Use this command to create a new trunk. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create trunk, + 'absent' to delete trunk and 'update' to modify trunk. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - Specify the name for the trunk configuration. + required: true + pn_ports: + description: + - Specify the port number(s) for the link(s) to aggregate into the trunk. + - Required for trunk-create. + pn_speed: + description: + - Specify the port speed or disable the port. + choices: ['disable', '10m', '100m', '1g', '2.5g', '10g', '40g'] + pn_egress_rate_limit: + description: + - Specify an egress port data rate limit for the configuration. + pn_jumbo: + description: + - Specify if the port can receive jumbo frames. + type: bool + pn_lacp_mode: + description: + - Specify the LACP mode for the configuration. + choices: ['off', 'passive', 'active'] + pn_lacp_priority: + description: + - Specify the LACP priority. This is a number between 1 and 65535 with a + default value of 32768. + pn_lacp_timeout: + description: + - Specify the LACP time out as slow (30 seconds) or fast (4seconds). + The default value is slow. + choices: ['slow', 'fast'] + pn_lacp_fallback: + description: + - Specify the LACP fallback mode as bundles or individual. + choices: ['bundle', 'individual'] + pn_lacp_fallback_timeout: + description: + - Specify the LACP fallback timeout in seconds. The range is between 30 + and 60 seconds with a default value of 50 seconds. + pn_edge_switch: + description: + - Specify if the switch is an edge switch. + type: bool + pn_pause: + description: + - Specify if pause frames are sent. + type: bool + pn_description: + description: + - Specify a description for the trunk configuration. + pn_loopback: + description: + - Specify loopback if you want to use loopback. + type: bool + pn_mirror_receive: + description: + - Specify if the configuration receives mirrored traffic. + type: bool + pn_unknown_ucast_level: + description: + - Specify an unknown unicast level in percent. The default value is 100%. + pn_unknown_mcast_level: + description: + - Specify an unknown multicast level in percent. The default value is 100%. + pn_broadcast_level: + description: + - Specify a broadcast level in percent. The default value is 100%. + pn_port_macaddr: + description: + - Specify the MAC address of the port. + pn_loopvlans: + description: + - Specify a list of looping vlans. + pn_routing: + description: + - Specify if the port participates in routing on the network. + type: bool + pn_host: + description: + - Host facing port control setting. + type: bool +''' + +EXAMPLES = """ +- name: Create trunk + community.network.pn_trunk: + state: 'present' + pn_name: 'spine-to-leaf' + pn_ports: '11,12,13,14' + +- name: Delete trunk + community.network.pn_trunk: + state: 'absent' + pn_name: 'spine-to-leaf' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the trunk command. + returned: always + type: list +stderr: + description: The set of error responses from the trunk command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +TRUNK_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the trunk-show command. + If a trunk with given name exists, return TRUNK_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: TRUNK_EXISTS + """ + name = module.params['pn_name'] + + show = cli + ' trunk-show format switch,name no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global TRUNK_EXISTS + if name in out: + TRUNK_EXISTS = True + else: + TRUNK_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'trunk-create' + if state == 'absent': + command = 'trunk-delete' + if state == 'update': + command = 'trunk-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_ports=dict(type='str'), + pn_speed=dict(type='str', + choices=['disable', '10m', '100m', '1g', '2.5g', + '10g', '40g']), + pn_egress_rate_limit=dict(type='str'), + pn_jumbo=dict(type='bool'), + pn_lacp_mode=dict(type='str', choices=[ + 'off', 'passive', 'active']), + pn_lacp_priority=dict(type='int'), + pn_lacp_timeout=dict(type='str', choices=['slow', 'fast']), + pn_lacp_fallback=dict(type='str', choices=[ + 'bundle', 'individual']), + pn_lacp_fallback_timeout=dict(type='str'), + pn_edge_switch=dict(type='bool'), + pn_pause=dict(type='bool'), + pn_description=dict(type='str'), + pn_loopback=dict(type='bool'), + pn_mirror_receive=dict(type='bool'), + pn_unknown_ucast_level=dict(type='str'), + pn_unknown_mcast_level=dict(type='str'), + pn_broadcast_level=dict(type='str'), + pn_port_macaddr=dict(type='str'), + pn_loopvlans=dict(type='str'), + pn_routing=dict(type='bool'), + pn_host=dict(type='bool') + ), + required_if=( + ["state", "present", ["pn_name", "pn_ports"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + name = module.params['pn_name'] + ports = module.params['pn_ports'] + speed = module.params['pn_speed'] + egress_rate_limit = module.params['pn_egress_rate_limit'] + jumbo = module.params['pn_jumbo'] + lacp_mode = module.params['pn_lacp_mode'] + lacp_priority = module.params['pn_lacp_priority'] + lacp_timeout = module.params['pn_lacp_timeout'] + lacp_fallback = module.params['pn_lacp_fallback'] + lacp_fallback_timeout = module.params['pn_lacp_fallback_timeout'] + edge_switch = module.params['pn_edge_switch'] + pause = module.params['pn_pause'] + description = module.params['pn_description'] + loopback = module.params['pn_loopback'] + mirror_receive = module.params['pn_mirror_receive'] + unknown_ucast_level = module.params['pn_unknown_ucast_level'] + unknown_mcast_level = module.params['pn_unknown_mcast_level'] + broadcast_level = module.params['pn_broadcast_level'] + port_macaddr = module.params['pn_port_macaddr'] + loopvlans = module.params['pn_loopvlans'] + routing = module.params['pn_routing'] + host = module.params['pn_host'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'trunk-delete': + + check_cli(module, cli) + if TRUNK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Trunk with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + if command == 'trunk-create': + check_cli(module, cli) + if TRUNK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Trunk with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + # Appending options + if ports: + cli += ' ports ' + ports + + if speed: + cli += ' speed ' + speed + + if egress_rate_limit: + cli += ' egress-rate-limit ' + egress_rate_limit + + if jumbo is True: + cli += ' jumbo ' + if jumbo is False: + cli += ' no-jumbo ' + + if lacp_mode: + cli += ' lacp-mode ' + lacp_mode + + if lacp_priority: + cli += ' lacp-priority ' + lacp_priority + + if lacp_timeout: + cli += ' lacp-timeout ' + lacp_timeout + + if lacp_fallback: + cli += ' lacp-fallback ' + lacp_fallback + + if lacp_fallback_timeout: + cli += ' lacp-fallback-timeout ' + lacp_fallback_timeout + + if edge_switch is True: + cli += ' edge-switch ' + if edge_switch is False: + cli += ' no-edge-switch ' + + if pause is True: + cli += ' pause ' + if pause is False: + cli += ' no-pause ' + + if description: + cli += ' description ' + description + + if loopback is True: + cli += ' loopback ' + if loopback is False: + cli += ' no-loopback ' + + if mirror_receive is True: + cli += ' mirror-receive-only ' + if mirror_receive is False: + cli += ' no-mirror-receive-only ' + + if unknown_ucast_level: + cli += ' unknown-ucast-level ' + unknown_ucast_level + + if unknown_mcast_level: + cli += ' unknown-mcast-level ' + unknown_mcast_level + + if broadcast_level: + cli += ' broadcast-level ' + broadcast_level + + if port_macaddr: + cli += ' port-mac-address ' + port_macaddr + + if loopvlans: + cli += ' loopvlans ' + loopvlans + + if routing is True: + cli += ' routing ' + if routing is False: + cli += ' no-routing ' + + if host is True: + cli += ' host-enable ' + if host is False: + cli += ' host-disable ' + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_user.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_user.py new file mode 100644 index 00000000..93673e03 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_user.py @@ -0,0 +1,195 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_user +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/modify/delete user +description: + - This module can be used to create a user and apply a role, + update a user and delete a user. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + type: str + required: false + state: + description: + - State the action to perform. Use C(present) to create user and + C(absent) to delete user C(update) to update user. + type: str + required: true + choices: ['present', 'absent', 'update'] + pn_scope: + description: + - local or fabric. + type: str + choices: ['local', 'fabric'] + pn_initial_role: + description: + - initial role for user. + type: str + pn_password: + description: + - plain text password. + type: str + pn_name: + description: + - username. + type: str +''' + +EXAMPLES = """ +- name: Create user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "present" + pn_scope: "fabric" + pn_password: "foo123" + pn_name: "foo" + +- name: Delete user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "absent" + pn_name: "foo" + +- name: Modify user + community.network.pn_user: + pn_cliswitch: "sw01" + state: "update" + pn_password: "test1234" + pn_name: "foo" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the user command. + returned: always + type: list +stderr: + description: set of error responses from the user command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the user-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' user-show format name no-show-headers' + out = run_commands(module, cli)[1] + + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='user-create', + absent='user-delete', + update='user-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_scope=dict(required=False, type='str', + choices=['local', 'fabric']), + pn_initial_role=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_name", "pn_scope"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name", "pn_password"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + scope = module.params['pn_scope'] + initial_role = module.params['pn_initial_role'] + password = module.params['pn_password'] + name = module.params['pn_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + USER_EXISTS = check_cli(module, cli) + cli += ' %s name %s ' % (command, name) + + if command == 'user-modify': + if USER_EXISTS is False: + module.fail_json( + failed=True, + msg='User with name %s does not exist' % name + ) + if initial_role or scope: + module.fail_json( + failed=True, + msg='Only password can be modified' + ) + + if command == 'user-delete': + if USER_EXISTS is False: + module.exit_json( + skipped=True, + msg='user with name %s does not exist' % name + ) + + if command == 'user-create': + if USER_EXISTS is True: + module.exit_json( + skipped=True, + msg='User with name %s already exists' % name + ) + if scope: + cli += ' scope ' + scope + + if initial_role: + cli += ' initial-role ' + initial_role + + if command != 'user-delete': + if password: + cli += ' password ' + password + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vflow_table_profile.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vflow_table_profile.py new file mode 100644 index 00000000..6f0d14b5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vflow_table_profile.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vflow_table_profile +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify vflow-table-profile +description: + - This module can be used to modify a vFlow table profile. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify + the vflow-table-profile. + required: true + type: str + choices: ['update'] + pn_profile: + description: + - type of vFlow profile. + required: false + type: str + choices: ['application', 'ipv6', 'qos'] + pn_hw_tbl: + description: + - hardware table used by vFlow. + required: false + type: str + choices: ['switch-main', 'switch-hash', 'npu-main', 'npu-hash'] + pn_enable: + description: + - enable or disable vflow profile table. + required: false + type: bool +''' + +EXAMPLES = """ +- name: Modify vflow table profile + community.network.pn_vflow_table_profile: + pn_cliswitch: 'sw01' + state: 'update' + pn_profile: 'ipv6' + pn_hw_tbl: 'switch-main' + pn_enable: true + +- name: Modify vflow table profile + community.network.pn_vflow_table_profile: + state: 'update' + pn_profile: 'qos' + pn_hw_tbl: 'switch-main' + pn_enable: false +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vflow-table-profile command. + returned: always + type: list +stderr: + description: set of error responses from the vflow-table-profile command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='vflow-table-profile-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_profile=dict(required=False, type='str', + choices=['application', 'ipv6', 'qos']), + pn_hw_tbl=dict(required=False, type='str', + choices=['switch-main', 'switch-hash', + 'npu-main', 'npu-hash']), + pn_enable=dict(required=False, type='bool'), + ), + required_if=( + ['state', 'update', ['pn_profile', 'pn_hw_tbl']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + profile = module.params['pn_profile'] + hw_tbl = module.params['pn_hw_tbl'] + enable = module.params['pn_enable'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'vflow-table-profile-modify': + cli += ' %s ' % command + if profile: + cli += ' profile ' + profile + if hw_tbl: + cli += ' hw-tbl ' + hw_tbl + + cli += booleanArgs(enable, 'enable', 'disable') + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlag.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlag.py new file mode 100644 index 00000000..6df7b52e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlag.py @@ -0,0 +1,350 @@ +#!/usr/bin/python +""" PN CLI vlag-create/vlag-delete/vlag-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vlag +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify vlag. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vlag-create/vlag-delete/vlag-modify command. + - A virtual link aggregation group (VLAG) allows links that are physically + connected to two different Pluribus Networks devices to appear as a single + trunk to a third device. The third device can be a switch, server, or any + Ethernet device. A VLAG can provide Layer 2 multipathing, which allows you + to create redundancy by increasing bandwidth, enabling multiple parallel + paths between nodes and loadbalancing traffic where alternative paths exist. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run this command on. + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vlag, + 'absent' to delete vlag and 'update' to modify vlag. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - The C(pn_name) takes a valid name for vlag configuration. + required: true + pn_port: + description: + - Specify the local VLAG port. + - Required for vlag-create. + pn_peer_port: + description: + - Specify the peer VLAG port. + - Required for vlag-create. + pn_mode: + description: + - Specify the mode for the VLAG. Active-standby indicates one side is + active and the other side is in standby mode. Active-active indicates + that both sides of the vlag are up by default. + choices: ['active-active', 'active-standby'] + pn_peer_switch: + description: + - Specify the fabric-name of the peer switch. + pn_failover_action: + description: + - Specify the failover action as move or ignore. + choices: ['move', 'ignore'] + pn_lacp_mode: + description: + - Specify the LACP mode. + choices: ['off', 'passive', 'active'] + pn_lacp_timeout: + description: + - Specify the LACP timeout as slow(30 seconds) or fast(4 seconds). + choices: ['slow', 'fast'] + pn_lacp_fallback: + description: + - Specify the LACP fallback mode as bundles or individual. + choices: ['bundle', 'individual'] + pn_lacp_fallback_timeout: + description: + - Specify the LACP fallback timeout in seconds. The range is between 30 + and 60 seconds with a default value of 50 seconds. +''' + +EXAMPLES = """ +- name: Create a VLAG + community.network.pn_vlag: + state: 'present' + pn_name: spine-to-leaf + pn_port: 'spine01-to-leaf' + pn_peer_port: 'spine02-to-leaf' + pn_peer_switch: spine02 + pn_mode: 'active-active' + +- name: Delete VLAGs + community.network.pn_vlag: + state: 'absent' + pn_name: spine-to-leaf +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vlag command. + returned: always + type: list +stderr: + description: The set of error responses from the vlag command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VLAG_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlag-show command. + If a vlag with given vlag exists, return VLAG_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VLAG_EXISTS + """ + name = module.params['pn_name'] + + show = cli + ' vlag-show format name no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global VLAG_EXISTS + if name in out: + VLAG_EXISTS = True + else: + VLAG_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vlag-create' + if state == 'absent': + command = 'vlag-delete' + if state == 'update': + command = 'vlag-modify' + return command + + +def main(): + """ This section is for argument parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_port=dict(type='str'), + pn_peer_port=dict(type='str'), + pn_mode=dict(type='str', choices=[ + 'active-standby', 'active-active']), + pn_peer_switch=dict(type='str'), + pn_failover_action=dict(type='str', choices=['move', 'ignore']), + pn_lacp_mode=dict(type='str', choices=[ + 'off', 'passive', 'active']), + pn_lacp_timeout=dict(type='str', choices=['slow', 'fast']), + pn_lacp_fallback=dict(type='str', choices=[ + 'bundle', 'individual']), + pn_lacp_fallback_timeout=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_name", "pn_port", "pn_peer_port", + "pn_peer_switch"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Argument accessing + state = module.params['state'] + name = module.params['pn_name'] + port = module.params['pn_port'] + peer_port = module.params['pn_peer_port'] + mode = module.params['pn_mode'] + peer_switch = module.params['pn_peer_switch'] + failover_action = module.params['pn_failover_action'] + lacp_mode = module.params['pn_lacp_mode'] + lacp_timeout = module.params['pn_lacp_timeout'] + lacp_fallback = module.params['pn_lacp_fallback'] + lacp_fallback_timeout = module.params['pn_lacp_fallback_timeout'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'vlag-delete': + + check_cli(module, cli) + if VLAG_EXISTS is False: + module.exit_json( + skipped=True, + msg='VLAG with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + + if command == 'vlag-create': + check_cli(module, cli) + if VLAG_EXISTS is True: + module.exit_json( + skipped=True, + msg='VLAG with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + if port: + cli += ' port %s peer-port %s ' % (port, peer_port) + + if mode: + cli += ' mode ' + mode + + if peer_switch: + cli += ' peer-switch ' + peer_switch + + if failover_action: + cli += ' failover-' + failover_action + '-L2 ' + + if lacp_mode: + cli += ' lacp-mode ' + lacp_mode + + if lacp_timeout: + cli += ' lacp-timeout ' + lacp_timeout + + if lacp_fallback: + cli += ' lacp-fallback ' + lacp_fallback + + if lacp_fallback_timeout: + cli += ' lacp-fallback-timeout ' + lacp_fallback_timeout + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlan.py new file mode 100644 index 00000000..ce0d55fb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vlan.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +""" PN CLI vlan-create/vlan-delete """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vlan +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete a VLAN. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vlan-create or vlan-delete command. + - VLANs are used to isolate network traffic at Layer 2.The VLAN identifiers + 0 and 4095 are reserved and cannot be used per the IEEE 802.1Q standard. + The range of configurable VLAN identifiers is 2 through 4092. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vlan and + 'absent' to delete vlan. + required: True + choices: ['present', 'absent'] + pn_vlanid: + description: + - Specify a VLAN identifier for the VLAN. This is a value between + 2 and 4092. + required: True + pn_scope: + description: + - Specify a scope for the VLAN. + - Required for vlan-create. + choices: ['fabric', 'local'] + pn_description: + description: + - Specify a description for the VLAN. + pn_stats: + description: + - Specify if you want to collect statistics for a VLAN. Statistic + collection is enabled by default. + type: bool + pn_ports: + description: + - Specifies the switch network data port number, list of ports, or range + of ports. Port numbers must ne in the range of 1 to 64. + pn_untagged_ports: + description: + - Specifies the ports that should have untagged packets mapped to the + VLAN. Untagged packets are packets that do not contain IEEE 802.1Q VLAN + tags. +''' + +EXAMPLES = """ +- name: Create a VLAN + community.network.pn_vlan: + state: 'present' + pn_vlanid: 1854 + pn_scope: fabric + +- name: Delete VLANs + community.network.pn_vlan: + state: 'absent' + pn_vlanid: 1854 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vlan command. + returned: always + type: list +stderr: + description: The set of error responses from the vlan command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VLAN_EXISTS = None +MAX_VLAN_ID = 4092 +MIN_VLAN_ID = 2 + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlan-show command. + If a vlan with given vlan id exists, return VLAN_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VLAN_EXISTS + """ + vlanid = module.params['pn_vlanid'] + + show = cli + \ + ' vlan-show id %s format id,scope no-show-headers' % str(vlanid) + show = shlex.split(show) + out = module.run_command(show)[1] + + out = out.split() + # Global flags + global VLAN_EXISTS + if str(vlanid) in out: + VLAN_EXISTS = True + else: + VLAN_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vlan-create' + if state == 'absent': + command = 'vlan-delete' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_vlanid=dict(required=True, type='int'), + pn_scope=dict(type='str', choices=['fabric', 'local']), + pn_description=dict(type='str'), + pn_stats=dict(type='bool'), + pn_ports=dict(type='str'), + pn_untagged_ports=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_vlanid", "pn_scope"]], + ["state", "absent", ["pn_vlanid"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vlanid = module.params['pn_vlanid'] + scope = module.params['pn_scope'] + description = module.params['pn_description'] + stats = module.params['pn_stats'] + ports = module.params['pn_ports'] + untagged_ports = module.params['pn_untagged_ports'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if not MIN_VLAN_ID <= vlanid <= MAX_VLAN_ID: + module.exit_json( + msg="VLAN id must be between 2 and 4092", + changed=False + ) + + if command == 'vlan-create': + + check_cli(module, cli) + if VLAN_EXISTS is True: + module.exit_json( + skipped=True, + msg='VLAN with id %s already exists' % str(vlanid) + ) + + cli += ' %s id %s scope %s ' % (command, str(vlanid), scope) + + if description: + cli += ' description ' + description + + if stats is True: + cli += ' stats ' + if stats is False: + cli += ' no-stats ' + + if ports: + cli += ' ports ' + ports + + if untagged_ports: + cli += ' untagged-ports ' + untagged_ports + + if command == 'vlan-delete': + + check_cli(module, cli) + if VLAN_EXISTS is False: + module.exit_json( + skipped=True, + msg='VLAN with id %s does not exist' % str(vlanid) + ) + + cli += ' %s id %s ' % (command, str(vlanid)) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter.py new file mode 100644 index 00000000..d640d3cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +""" PN CLI vrouter-create/vrouter-delete/vrouter-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouter +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to create/delete/modify a vrouter. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-create, vrouter-delete, vrouter-modify command. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a virtual router (vRouter) service that forwards + traffic between networks and implements Layer 3 protocols. + - C(vrouter-create) creates a new vRouter service. + - C(vrouter-delete) deletes a vRouter service. + - C(vrouter-modify) modifies a vRouter service. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the CLI on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to create vrouter, + 'absent' to delete vrouter and 'update' to modify vrouter. + required: True + choices: ['present', 'absent', 'update'] + pn_name: + description: + - Specify the name of the vRouter. + required: true + pn_vnet: + description: + - Specify the name of the VNET. + - Required for vrouter-create. + pn_service_type: + description: + - Specify if the vRouter is a dedicated or shared VNET service. + choices: ['dedicated', 'shared'] + pn_service_state: + description: + - Specify to enable or disable vRouter service. + choices: ['enable', 'disable'] + pn_router_type: + description: + - Specify if the vRouter uses software or hardware. + - Note that if you specify hardware as router type, you cannot assign IP + addresses using DHCP. You must specify a static IP address. + choices: ['hardware', 'software'] + pn_hw_vrrp_id: + description: + - Specifies the VRRP ID for a hardware vrouter. + pn_router_id: + description: + - Specify the vRouter IP address. + pn_bgp_as: + description: + - Specify the Autonomous System Number(ASN) if the vRouter runs Border + Gateway Protocol(BGP). + pn_bgp_redistribute: + description: + - Specify how BGP routes are redistributed. + choices: ['static', 'connected', 'rip', 'ospf'] + pn_bgp_max_paths: + description: + - Specify the maximum number of paths for BGP. This is a number between + 1 and 255 or 0 to unset. + pn_bgp_options: + description: + - Specify other BGP options as a whitespaces separated string within + single quotes ''. + pn_rip_redistribute: + description: + - Specify how RIP routes are redistributed. + choices: ['static', 'connected', 'ospf', 'bgp'] + pn_ospf_redistribute: + description: + - Specify how OSPF routes are redistributed. + choices: ['static', 'connected', 'bgp', 'rip'] + pn_ospf_options: + description: + - Specify other OSPF options as a whitespaces separated string within + single quotes ''. + pn_vrrp_track_port: + description: + - Specify list of ports and port ranges. +''' + +EXAMPLES = """ +- name: Create vrouter + community.network.pn_vrouter: + state: 'present' + pn_name: 'ansible-vrouter' + pn_vnet: 'ansible-fab-global' + pn_router_id: 208.74.182.1 + +- name: Delete vrouter + community.network.pn_vrouter: + state: 'absent' + pn_name: 'ansible-vrouter' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouter command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouter command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# AnsibleModule boilerplate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +VROUTER_NAME_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vlan-show command. + A switch can have only one vRouter configuration. + If a vRouter already exists on the given switch, return VROUTER_EXISTS as + True else False. + If a vRouter with the given name exists(on a different switch), return + VROUTER_NAME_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, VROUTER_NAME_EXISTS + """ + name = module.params['pn_name'] + # Global flags + global VROUTER_EXISTS, VROUTER_NAME_EXISTS + + # Get the name of the local switch + location = cli + ' switch-setup-show format switch-name' + location = shlex.split(location) + out = module.run_command(location)[1] + location = out.split()[1] + + # Check for any vRouters on the switch + check_vrouter = cli + ' vrouter-show location %s ' % location + check_vrouter += 'format name no-show-headers' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + + if out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for any vRouters with the given name + show = cli + ' vrouter-show format name no-show-headers ' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if name in out: + VROUTER_NAME_EXISTS = True + else: + VROUTER_NAME_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-create' + if state == 'absent': + command = 'vrouter-delete' + if state == 'update': + command = 'vrouter-modify' + return command + + +def main(): + """ This section is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_name=dict(required=True, type='str'), + pn_vnet=dict(type='str'), + pn_service_type=dict(type='str', choices=['dedicated', 'shared']), + pn_service_state=dict(type='str', choices=['enable', 'disable']), + pn_router_type=dict(type='str', choices=['hardware', 'software']), + pn_hw_vrrp_id=dict(type='int'), + pn_router_id=dict(type='str'), + pn_bgp_as=dict(type='int'), + pn_bgp_redistribute=dict(type='str', choices=['static', 'connected', + 'rip', 'ospf']), + pn_bgp_max_paths=dict(type='int'), + pn_bgp_options=dict(type='str'), + pn_rip_redistribute=dict(type='str', choices=['static', 'connected', + 'bgp', 'ospf']), + pn_ospf_redistribute=dict(type='str', choices=['static', 'connected', + 'bgp', 'rip']), + pn_ospf_options=dict(type='str'), + pn_vrrp_track_port=dict(type='str') + ), + required_if=( + ["state", "present", ["pn_name", "pn_vnet"]], + ["state", "absent", ["pn_name"]], + ["state", "update", ["pn_name"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + name = module.params['pn_name'] + vnet = module.params['pn_vnet'] + service_type = module.params['pn_service_type'] + service_state = module.params['pn_service_state'] + router_type = module.params['pn_router_type'] + hw_vrrp_id = module.params['pn_hw_vrrp_id'] + router_id = module.params['pn_router_id'] + bgp_as = module.params['pn_bgp_as'] + bgp_redistribute = module.params['pn_bgp_redistribute'] + bgp_max_paths = module.params['pn_bgp_max_paths'] + bgp_options = module.params['pn_bgp_options'] + rip_redistribute = module.params['pn_rip_redistribute'] + ospf_redistribute = module.params['pn_ospf_redistribute'] + ospf_options = module.params['pn_ospf_options'] + vrrp_track_port = module.params['pn_vrrp_track_port'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if command == 'vrouter-delete': + check_cli(module, cli) + if VROUTER_NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with name %s does not exist' % name + ) + cli += ' %s name %s ' % (command, name) + + else: + + if command == 'vrouter-create': + check_cli(module, cli) + if VROUTER_EXISTS is True: + module.exit_json( + skipped=True, + msg='Maximum number of vRouters has been reached on this ' + 'switch' + ) + if VROUTER_NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with name %s already exists' % name + ) + cli += ' %s name %s ' % (command, name) + + if vnet: + cli += ' vnet ' + vnet + + if service_type: + cli += ' %s-vnet-service ' % service_type + + if service_state: + cli += ' ' + service_state + + if router_type: + cli += ' router-type ' + router_type + + if hw_vrrp_id: + cli += ' hw-vrrp-id ' + str(hw_vrrp_id) + + if router_id: + cli += ' router-id ' + router_id + + if bgp_as: + cli += ' bgp-as ' + str(bgp_as) + + if bgp_redistribute: + cli += ' bgp-redistribute ' + bgp_redistribute + + if bgp_max_paths: + cli += ' bgp-max-paths ' + str(bgp_max_paths) + + if bgp_options: + cli += ' %s ' % bgp_options + + if rip_redistribute: + cli += ' rip-redistribute ' + rip_redistribute + + if ospf_redistribute: + cli += ' ospf-redistribute ' + ospf_redistribute + + if ospf_options: + cli += ' %s ' % ospf_options + + if vrrp_track_port: + cli += ' vrrp-track-port ' + vrrp_track_port + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp.py new file mode 100644 index 00000000..8351351b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp.py @@ -0,0 +1,467 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_bgp +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/modify/remove vrouter-bgp +description: + - This module can be used to add Border Gateway Protocol neighbor to a vRouter + modify Border Gateway Protocol neighbor to a vRouter and remove Border Gateway Protocol + neighbor from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-bgp configuration command. + required: false + type: str + choices: ['present', 'absent', 'update'] + default: 'present' + pn_neighbor: + description: + - IP address for BGP neighbor. + required: true + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str + pn_send_community: + description: + - send any community attribute to neighbor. + required: false + type: bool + pn_weight: + description: + - default weight value between 0 and 65535 for the neighbor's routes. + required: false + pn_multi_protocol: + description: + - Multi-protocol features. + required: false + choices: ['ipv4-unicast', 'ipv6-unicast'] + pn_prefix_list_in: + description: + - prefixes used for filtering. + required: false + type: str + pn_route_reflector_client: + description: + - set as route reflector client. + required: false + type: bool + pn_default_originate: + description: + - announce default routes to the neighbor or not. + required: false + type: bool + pn_neighbor_holdtime: + description: + - BGP Holdtime (seconds). + required: false + type: str + pn_connect_retry_interval: + description: + - BGP Connect retry interval (seconds). + required: false + type: str + pn_advertisement_interval: + description: + - Minimum interval between sending BGP routing updates. + required: false + type: str + pn_route_map_out: + description: + - route map out for nbr. + required: false + type: str + pn_update_source: + description: + - IP address of BGP packets required for peering over loopback interface. + required: false + type: str + pn_bfd: + description: + - BFD protocol support for fault detection. + required: false + type: bool + default: False + pn_next_hop_self: + description: + - BGP next hop is self or not. + required: false + type: bool + pn_allowas_in: + description: + - Allow/reject routes with local AS in AS_PATH. + required: false + type: bool + pn_neighbor_keepalive_interval: + description: + - BGP Keepalive interval (seconds). + required: false + type: str + pn_max_prefix: + description: + - maximum number of prefixes. + required: false + type: str + pn_bfd_multihop: + description: + - always use BFD multi-hop port for fault detection. + required: false + type: bool + pn_interface: + description: + - Interface to reach the neighbor. + required: false + type: str + pn_password: + description: + - password for MD5 BGP. + required: false + type: str + pn_route_map_in: + description: + - route map in for nbr. + required: false + type: str + pn_soft_reconfig_inbound: + description: + - soft reset to reconfigure inbound traffic. + required: false + type: bool + pn_override_capability: + description: + - override capability. + required: false + type: bool + pn_max_prefix_warn_only: + description: + - warn if the maximum number of prefixes is exceeded. + required: false + type: bool + pn_ebgp_multihop: + description: + - value for external BGP from 1 to 255. + required: false + type: str + pn_remote_as: + description: + - BGP remote AS from 1 to 4294967295. + required: false + type: str + pn_prefix_list_out: + description: + - prefixes used for filtering outgoing packets. + required: false + type: str + pn_no_route_map_out: + description: + - Remove egress route-map from BGP neighbor. + required: false + type: str + pn_no_route_map_in: + description: + - Remove ingress route-map from BGP neighbor. + required: false + type: str +''' + +EXAMPLES = """ +- name: "Add BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'present' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + pn_remote_as: 65000 + pn_bfd: true + +- name: "Remove BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'absent' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + +- name: "Modify BGP to vRouter" + community.network.pn_vrouter_bgp: + state: 'update' + pn_vrouter_name: 'sw01-vrouter' + pn_neighbor: '105.104.104.1' + pn_remote_as: 65000 + pn_bfd: false + pn_allowas_in: true +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-bgp command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-bgp command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli, booleanArgs +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def is_valid(module, param_name, param_val, min_val, max_val): + if int(param_val) < min_val or int(param_val) > max_val: + module.fail_json( + failed=True, + msg='Valid %s range is %s to %s' % (param_name, min_val, max_val) + ) + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-bgp-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If the given neighbor exists on the given vRouter, return NEIGHBOR_EXISTS as True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NEIGHBOR_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if neighbor: + # Check for BGP neighbor + show = cli + ' vrouter-bgp-show vrouter-name %s ' % vrouter_name + show += 'format neighbor no-show-headers' + out = run_commands(module, show)[1] + + if out and neighbor in out.split(): + NEIGHBOR_EXISTS = True + else: + NEIGHBOR_EXISTS = False + + return VROUTER_EXISTS, NEIGHBOR_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-bgp-add', + absent='vrouter-bgp-remove', + update='vrouter-bgp-modify' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_neighbor=dict(required=True, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + pn_send_community=dict(required=False, type='bool'), + pn_weight=dict(required=False, type='str'), + pn_multi_protocol=dict(required=False, type='str', choices=['ipv4-unicast', 'ipv6-unicast']), + pn_prefix_list_in=dict(required=False, type='str'), + pn_route_reflector_client=dict(required=False, type='bool'), + pn_default_originate=dict(required=False, type='bool'), + pn_neighbor_holdtime=dict(required=False, type='str'), + pn_connect_retry_interval=dict(required=False, type='str'), + pn_advertisement_interval=dict(required=False, type='str'), + pn_route_map_out=dict(required=False, type='str'), + pn_update_source=dict(required=False, type='str'), + pn_bfd=dict(required=False, type='bool', default=False), + pn_next_hop_self=dict(required=False, type='bool'), + pn_allowas_in=dict(required=False, type='bool'), + pn_neighbor_keepalive_interval=dict(required=False, type='str'), + pn_max_prefix=dict(required=False, type='str'), + pn_bfd_multihop=dict(required=False, type='bool'), + pn_interface=dict(required=False, type='str'), + pn_password=dict(required=False, type='str', no_log=True), + pn_route_map_in=dict(required=False, type='str'), + pn_soft_reconfig_inbound=dict(required=False, type='bool'), + pn_override_capability=dict(required=False, type='bool'), + pn_max_prefix_warn_only=dict(required=False, type='bool'), + pn_ebgp_multihop=dict(required=False, type='str'), + pn_remote_as=dict(required=False, type='str'), + pn_prefix_list_out=dict(required=False, type='str'), + pn_no_route_map_out=dict(required=False, type='str'), + pn_no_route_map_in=dict(required=False, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_neighbor", "pn_remote_as"]], + ["state", "absent", ["pn_vrouter_name", "pn_neighbor"]], + ["state", "update", ["pn_vrouter_name", "pn_neighbor"]] + ), + required_one_of=[['pn_send_community', 'pn_weight', 'pn_multi_protocol', + 'pn_prefix_list_in', 'pn_route_reflector_client', 'pn_default_originate', + 'pn_neighbor_holdtime', 'pn_connect_retry_interval', 'pn_advertisement_interval', + 'pn_route_map_out', 'pn_update_source', 'pn_bfd', + 'pn_next_hop_self', 'pn_allowas_in', 'pn_neighbor_keepalive_interval', + 'pn_max_prefix', 'pn_bfd_multihop', 'pn_interface', + 'pn_password', 'pn_route_map_in', 'pn_soft_reconfig_inbound', + 'pn_override_capability', 'pn_max_prefix_warn_only', 'pn_ebgp_multihop', + 'pn_remote_as', 'pn_prefix_list_out', 'pn_no_route_map_out', + 'pn_no_route_map_in']], + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + neighbor = module.params['pn_neighbor'] + vrouter_name = module.params['pn_vrouter_name'] + send_community = module.params['pn_send_community'] + weight = module.params['pn_weight'] + multi_protocol = module.params['pn_multi_protocol'] + prefix_list_in = module.params['pn_prefix_list_in'] + route_reflector_client = module.params['pn_route_reflector_client'] + default_originate = module.params['pn_default_originate'] + neighbor_holdtime = module.params['pn_neighbor_holdtime'] + connect_retry_interval = module.params['pn_connect_retry_interval'] + advertisement_interval = module.params['pn_advertisement_interval'] + route_map_out = module.params['pn_route_map_out'] + update_source = module.params['pn_update_source'] + bfd = module.params['pn_bfd'] + next_hop_self = module.params['pn_next_hop_self'] + allowas_in = module.params['pn_allowas_in'] + neighbor_keepalive_interval = module.params['pn_neighbor_keepalive_interval'] + max_prefix = module.params['pn_max_prefix'] + bfd_multihop = module.params['pn_bfd_multihop'] + interface = module.params['pn_interface'] + password = module.params['pn_password'] + route_map_in = module.params['pn_route_map_in'] + soft_reconfig_inbound = module.params['pn_soft_reconfig_inbound'] + override_capability = module.params['pn_override_capability'] + max_prefix_warn_only = module.params['pn_max_prefix_warn_only'] + ebgp_multihop = module.params['pn_ebgp_multihop'] + remote_as = module.params['pn_remote_as'] + prefix_list_out = module.params['pn_prefix_list_out'] + no_route_map_out = module.params['pn_no_route_map_out'] + no_route_map_in = module.params['pn_no_route_map_in'] + + command = state_map[state] + + if weight and weight != 'none': + if int(weight) < 1 or int(weight) > 65535: + module.fail_json( + failed=True, + msg='Valid weight range is 1 to 65535' + ) + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + VROUTER_EXISTS, NEIGHBOR_EXISTS = check_cli(module, cli) + + if state: + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if command == 'vrouter-bgp-remove' or command == 'vrouter-bgp-modify': + if NEIGHBOR_EXISTS is False: + module.exit_json( + skipped=True, + msg='BGP neighbor with IP %s does not exist on %s' % (neighbor, vrouter_name) + ) + + if command == 'vrouter-bgp-add': + if NEIGHBOR_EXISTS is True: + module.exit_json( + skipped=True, + msg='BGP neighbor with IP %s already exists on %s' % (neighbor, vrouter_name) + ) + + cli += ' %s vrouter-name %s neighbor %s ' % (command, vrouter_name, neighbor) + + if command == 'vrouter-bgp-add' or command == 'vrouter-bgp-modify': + if weight: + cli += ' weight ' + weight + if multi_protocol: + cli += ' multi-protocol ' + multi_protocol + if prefix_list_in: + cli += ' prefix-list-in ' + prefix_list_in + if neighbor_holdtime: + is_valid(module, 'neighbor holdtime', neighbor_holdtime, '0', '65535') + cli += ' neighbor-holdtime ' + neighbor_holdtime + if connect_retry_interval: + is_valid(module, 'connect retry interval', connect_retry_interval, '0', '65535') + cli += ' connect-retry-interval ' + connect_retry_interval + if advertisement_interval: + is_valid(module, 'advertisement interval', advertisement_interval, '0', '65535') + cli += ' advertisement-interval ' + advertisement_interval + if route_map_out: + cli += ' route-map-out ' + route_map_out + if update_source: + cli += ' update-source ' + update_source + if neighbor_keepalive_interval: + is_valid(module, 'neighbor keepalive interval', neighbor_keepalive_interval, '0', '65535') + cli += ' neighbor-keepalive-interval ' + neighbor_keepalive_interval + if max_prefix: + cli += ' max-prefix ' + max_prefix + if interface: + cli += ' interface ' + interface + if password: + cli += ' password ' + password + if route_map_in: + cli += ' route-map-in ' + route_map_in + if ebgp_multihop: + is_valid(module, 'ebgp_multihop', ebgp_multihop, '1', '255') + cli += ' ebgp-multihop ' + ebgp_multihop + if remote_as: + cli += ' remote-as ' + remote_as + if prefix_list_out: + cli += ' prefix-list-out ' + prefix_list_out + cli += booleanArgs(send_community, 'send-community', 'no-send-community') + cli += booleanArgs(route_reflector_client, 'route-reflector-client', 'no-route-reflector-client') + cli += booleanArgs(default_originate, 'default-originate', 'no-default-originate') + cli += booleanArgs(bfd, 'bfd', 'no-bfd') + cli += booleanArgs(next_hop_self, 'next-hop-self', 'no-next-hop-self') + cli += booleanArgs(allowas_in, 'allowas-in', 'no-allowas-in') + cli += booleanArgs(bfd_multihop, 'bfd-multihop', 'no-bfd-multihop') + cli += booleanArgs(soft_reconfig_inbound, 'soft-reconfig-inbound', 'no-soft-reconfig-inbound') + cli += booleanArgs(override_capability, 'override-capability', 'no-override-capability') + cli += booleanArgs(max_prefix_warn_only, 'max-prefix-warn-only', 'no-max-prefix-warn-only') + + if command == 'vrouter-bgp-modify': + if no_route_map_out: + cli += ' no-route-map-out ' + no_route_map_out + if no_route_map_in: + cli += ' no-route-map-in ' + no_route_map_in + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp_network.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp_network.py new file mode 100644 index 00000000..7ccb62a3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_bgp_network.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_bgp_network +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-bgp-network +description: + - This module can be used to add Border Gateway Protocol network to a vRouter + and remove Border Gateway Protocol network from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add bgp network and + C(absent) to remove bgp network. + required: true + type: str + choices: ['present', 'absent'] + pn_netmask: + description: + - BGP network mask. + required: false + type: str + pn_network: + description: + - IP address for BGP network. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add network to bgp + community.network.pn_vrouter_bgp_network: + pn_cliswitch: "sw01" + state: "present" + pn_vrouter_name: "foo-vrouter" + pn_network: '10.10.10.10' + pn_netmask: '31' + +- name: Remove network from bgp + community.network.pn_vrouter_bgp_network: + pn_cliswitch: "sw01" + state: "absent" + pn_vrouter_name: "foo-vrouter" + pn_network: '10.10.10.10' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-bgp-network command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-bgp-network command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for pim ssm config using the vrouter-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_vrouter_name'] + network = module.params['pn_network'] + + show = cli + cli += ' vrouter-show name %s format name no-show-headers' % name + rc, out, err = run_commands(module, cli) + VROUTER_EXISTS = '' if out else None + + cli = show + cli += ' vrouter-bgp-network-show vrouter-name %s network %s format network no-show-headers' % (name, network) + out = run_commands(module, cli)[1] + out = out.split() + NETWORK_EXISTS = True if network in out[-1] else False + + return NETWORK_EXISTS, VROUTER_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-bgp-network-add', + absent='vrouter-bgp-network-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_netmask=dict(required=False, type='str'), + pn_network=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ['state', 'present', ['pn_vrouter_name', 'pn_netmask', 'pn_network']], + ['state', 'absent', ['pn_vrouter_name', 'pn_network']], + ), + ) + + # Accessing the arguments + state = module.params['state'] + cliswitch = module.params['pn_cliswitch'] + netmask = module.params['pn_netmask'] + network = module.params['pn_network'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NETWORK_EXISTS, VROUTER_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is None: + module.fail_json( + failed=True, + msg='vRouter %s does not exists' % vrouter_name + ) + + if command == 'vrouter-bgp-network-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='Network %s already added to bgp' % network + ) + + if command == 'vrouter-bgp-network-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='Network %s does not exists' % network + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if netmask: + cli += ' netmask ' + netmask + if network: + cli += ' network ' + network + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_interface_ip.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_interface_ip.py new file mode 100644 index 00000000..1de6e507 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_interface_ip.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_interface_ip +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-interface-ip +description: + - This module can be used to add an IP address on interface from a vRouter + or remove an IP address on interface from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to addvrouter-interface-ip + and C(absent) to remove vrouter-interface-ip. + required: true + type: str + choices: ['present', 'absent'] + pn_bd: + description: + - interface Bridge Domain. + required: false + type: str + pn_netmask: + description: + - netmask. + required: false + type: str + pn_vnet: + description: + - interface VLAN VNET. + required: false + type: str + pn_ip: + description: + - IP address. + required: false + type: str + pn_nic: + description: + - virtual NIC assigned to interface. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add vrouter interface to nic + community.network.pn_vrouter_interface_ip: + state: "present" + pn_cliswitch: "sw01" + pn_vrouter_name: "foo-vrouter" + pn_ip: "2620:0:1651:1::30" + pn_netmask: "127" + pn_nic: "eth0.4092" + +- name: Remove vrouter interface to nic + community.network.pn_vrouter_interface_ip: + state: "absent" + pn_cliswitch: "sw01" + pn_vrouter_name: "foo-vrouter" + pn_ip: "2620:0:1651:1::30" + pn_nic: "eth0.4092" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-interface-ip command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-interface-ip command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If an interface with the given ip exists on the given vRouter, + return INTERFACE_EXISTS as True else False. This is required for + vrouter-interface-add. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-interface-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_ip'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip2 %s format ip2,nic no-show-headers' % interface_ip + out = run_commands(module, show)[1] + + if out and interface_ip in out.split(' ')[-2]: + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + if nic_str: + # Check for nic + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'format nic no-show-headers' + out = run_commands(module, show)[1] + + if out: + out = out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-interface-ip-add', + absent='vrouter-interface-ip-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_bd=dict(required=False, type='str'), + pn_netmask=dict(required=False, type='str'), + pn_vnet=dict(required=False, type='str'), + pn_ip=dict(required=False, type='str'), + pn_nic=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_nic", "pn_ip", "pn_netmask"]], + ["state", "absent", ["pn_vrouter_name", "pn_nic", "pn_ip"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + bd = module.params['pn_bd'] + netmask = module.params['pn_netmask'] + vnet = module.params['pn_vnet'] + ip = module.params['pn_ip'] + nic = module.params['pn_nic'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if NIC_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter with nic %s does not exist' % nic + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if command == 'vrouter-interface-ip-add': + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with interface ip %s exist' % ip + ) + cli += ' nic %s ip %s ' % (nic, ip) + + if bd: + cli += ' bd ' + bd + if netmask: + cli += ' netmask ' + netmask + if vnet: + cli += ' vnet ' + vnet + + if command == 'vrouter-interface-ip-remove': + if INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with interface ip %s does not exist' % ip + ) + if nic: + cli += ' nic %s ' % nic + if ip: + cli += ' ip %s ' % ip.split('/')[0] + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_loopback_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_loopback_interface.py new file mode 100644 index 00000000..1f806c00 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_loopback_interface.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_loopback_interface +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-loopback-interface +description: + - This module can be used to add loopback interface to a vRouter or + remove loopback interface from a vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add vrouter-loopback-interface + and C(absent) to remove vrouter-loopback-interface. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_ip: + description: + - loopback IP address. + required: true + type: str + pn_index: + description: + - loopback index from 1 to 255. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: Add vrouter loopback interface + community.network.pn_vrouter_loopback_interface: + state: "present" + pn_cliswitch: "sw01" + pn_vrouter_name: "sw01-vrouter" + pn_ip: "192.168.10.1" + +- name: Remove vrouter loopback interface + community.network.pn_vrouter_loopback_interface: + state: "absent" + pn_cliswitch: "sw01" + pn_vrouter_name: "sw01-vrouter" + pn_ip: "192.168.10.1" + pn_index: "2" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-loopback-interface command. + returned: always + type: list +stderr: + description: set of error response from the vrouter-loopback-interface + command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, INTERFACE_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_ip'] + + # Check for vRouter + check_vrouter = 'vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-loopback-interface-show ' + show += 'vrouter-name %s ' % vrouter_name + show += 'format ip no-show-headers' + out = run_commands(module, show)[1] + + if out and interface_ip in out.split(): + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + return VROUTER_EXISTS, INTERFACE_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-loopback-interface-add', + absent='vrouter-loopback-interface-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', + choices=state_map.keys(), default='present'), + pn_ip=dict(required=True, type='str'), + pn_index=dict(required=False, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_ip"]], + ["state", "absent", ["pn_vrouter_name", "pn_ip", "pn_index"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ip = module.params['pn_ip'] + index = module.params['pn_index'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, INTERFACE_EXISTS = check_cli(module, cli) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if index and (int(index) < 1 or int(index) > 255): + module.fail_json( + failed=True, + msg='index should be in range 1 to 255' + ) + + if index and state == 'present': + show = 'vrouter-loopback-interface-show format index parsable-delim ,' + out = run_commands(module, show)[1] + if out: + out = out.split() + for res in out: + res = res.strip().split(',') + if index in res: + module.fail_json( + failed=True, + msg='index with value %s exist' % index + ) + + if command == 'vrouter-loopback-interface-add': + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg='vRouter with loopback ip %s exist' % ip + ) + if ip: + cli += ' ip ' + ip + if index: + cli += ' index ' + index + + if command == 'vrouter-loopback-interface-remove': + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter with loopback ip %s doesnt exist' % ip + ) + + if index: + cli += ' index ' + index + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf.py new file mode 100644 index 00000000..f32b02ea --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_ospf +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-ospf +description: + - This module can be used to add OSPF protocol to vRouter + and remove OSPF protocol from a vRouter +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-ospf configuration command. + required: false + type: str + choices: ['present', 'absent'] + default: 'present' + pn_netmask: + description: + - OSPF network IP address netmask. + required: false + type: str + pn_ospf_area: + description: + - stub area number for the configuration. + required: false + type: str + pn_network: + description: + - OSPF network IP address. + required: true + type: str + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: Add OSPF to vRouter + community.network.pn_vrouter_ospf: + state: 'present' + pn_vrouter_name: 'sw01-vrouter' + pn_network: '105.104.104.1' + pn_netmask: '24' + pn_ospf_area: '0' +- name: "Remove OSPF to vRouter" + community.network.pn_vrouter_ospf: + state: 'absent' + pn_vrouter_name: 'sw01-vrouter' + pn_network: '105.104.104.1' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-ospf command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-ospf command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If an OSPF network with the given ip exists on the given vRouter, + return NETWORK_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :return Booleans: VROUTER_EXISTS, NETWORK_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + network = module.params['pn_network'] + show_cli = pn_cli(module) + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + # Check for OSPF networks + check_network = cli + ' vrouter-ospf-show vrouter-name %s ' % vrouter_name + check_network += 'format network no-show-headers' + out = run_commands(module, check_network)[1] + + if out and network in out: + NETWORK_EXISTS = True + else: + NETWORK_EXISTS = False + + return VROUTER_EXISTS, NETWORK_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-ospf-add', + absent='vrouter-ospf-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_netmask=dict(required=False, type='str'), + pn_ospf_area=dict(required=False, type='str'), + pn_network=dict(required=True, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ['pn_vrouter_name', 'pn_network', 'pn_netmask', 'pn_ospf_area']], + ["state", "absent", ['pn_vrouter_name', 'pn_network']], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + netmask = module.params['pn_netmask'] + ospf_area = module.params['pn_ospf_area'] + network = module.params['pn_network'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + VROUTER_EXISTS, NETWORK_EXISTS = check_cli(module, cli) + + if state: + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if command == 'vrouter-ospf-remove': + if NETWORK_EXISTS is False: + module.exit_json( + skipped=True, + msg='OSPF with network %s dose not exists' % network + ) + cli += ' %s vrouter-name %s network %s' % (command, vrouter_name, network) + + if command == 'vrouter-ospf-add': + if NETWORK_EXISTS is True: + module.exit_json( + skipped=True, + msg='OSPF with network %s already exists' % network + ) + if netmask: + cli += ' netmask ' + netmask + if ospf_area: + cli += ' ospf-area ' + ospf_area + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf6.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf6.py new file mode 100644 index 00000000..9d2e0041 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_ospf6.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_ospf6 +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-ospf6 +description: + - This module can be used to add interface ip to OSPF6 protocol + or remove interface ip from OSPF6 protocol on vRouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(present) to add vrouter-ospf6 and + C(absent) to remove interface from vrouter-ospf6. + required: true + type: str + choices: ['present', 'absent'] + pn_ospf6_area: + description: + - area id for this interface in IPv4 address format. + required: false + type: str + pn_nic: + description: + - OSPF6 control for this interface. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Add vrouter interface nic to ospf6 + community.network.pn_vrouter_ospf6: + pn_cliswitch: "sw01" + state: "present" + pn_vrouter_name: "foo-vrouter" + pn_nic: "eth0.4092" + pn_ospf6_area: "0.0.0.0" + +- name: Remove vrouter interface nic to ospf6 + community.network.pn_vrouter_ospf6: + pn_cliswitch: "sw01" + state: "absent" + pn_vrouter_name: "foo-vrouter" + pn_nic: "eth0.4092" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-ospf6 command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-ospf6 command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-ospf6-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + out = run_commands(module, check_vrouter)[1] + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if nic_str: + # Check for nic + show = cli + ' vrouter-ospf6-show vrouter-name %s format nic no-show-headers' % vrouter_name + out = run_commands(module, show)[1] + + if out: + out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-ospf6-add', + absent='vrouter-ospf6-remove' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_ospf6_area=dict(required=False, type='str'), + pn_nic=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + ), + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_nic", + "pn_ospf6_area"]], + ["state", "absent", ["pn_vrouter_name", "pn_nic"]] + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + ospf6_area = module.params['pn_ospf6_area'] + nic = module.params['pn_nic'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + + if command == 'vrouter-ospf6-add': + if NIC_EXISTS is True: + module.exit_json( + skipped=True, + msg='OSPF6 with nic %s already exist' % nic + ) + if nic: + cli += ' nic %s' % nic + if ospf6_area: + cli += ' ospf6-area %s ' % ospf6_area + + if command == 'vrouter-ospf6-remove': + if NIC_EXISTS is False: + module.exit_json( + skipped=True, + msg='OSPF6 with nic %s does not exist' % nic + ) + if nic: + cli += ' nic %s' % nic + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_packet_relay.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_packet_relay.py new file mode 100644 index 00000000..64256d84 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_packet_relay.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_packet_relay +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to add/remove vrouter-packet-relay +description: + - This module can be used to add packet relay configuration for DHCP on vrouter + and remove packet relay configuration for DHCP on vrouter. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vrouter-packet-relay configuration command. + required: false + choices: ['present', 'absent'] + type: str + default: 'present' + pn_forward_ip: + description: + - forwarding IP address. + required: true + type: str + pn_nic: + description: + - NIC. + required: true + type: str + pn_forward_proto: + description: + - protocol type to forward packets. + required: false + type: str + choices: ['dhcp'] + default: 'dhcp' + pn_vrouter_name: + description: + - name of service config. + required: true + type: str +''' + +EXAMPLES = """ +- name: VRouter packet relay add + community.network.pn_vrouter_packet_relay: + pn_cliswitch: "sw01" + pn_forward_ip: "192.168.10.1" + pn_nic: "eth0.4092" + pn_vrouter_name: "sw01-vrouter" + +- name: VRouter packet relay remove + community.network.pn_vrouter_packet_relay: + pn_cliswitch: "sw01" + state: "absent" + pn_forward_ip: "192.168.10.1" + pn_nic: "eth0.4092" + pn_vrouter_name: "sw01-vrouter" +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-packet-relay command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-packet-relay command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Booleans: VROUTER_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + nic_str = module.params['pn_nic'] + + # Check for vRouter + check_vrouter = 'vrouter-show format name no-show-headers' + out = run_commands(module, check_vrouter)[1] + + if out: + out = out.split() + + VROUTER_EXISTS = True if vrouter_name in out else False + + if nic_str: + # Check for nic + show = 'vrouter-interface-show vrouter-name %s format nic no-show-headers' % vrouter_name + out = run_commands(module, show)[1] + if out: + out = out.split() + + NIC_EXISTS = True if nic_str in out else False + + return VROUTER_EXISTS, NIC_EXISTS + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vrouter-packet-relay-add', + absent='vrouter-packet-relay-remove' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_forward_ip=dict(required=True, type='str'), + pn_nic=dict(required=True, type='str'), + pn_forward_proto=dict(required=False, type='str', choices=['dhcp'], default='dhcp'), + pn_vrouter_name=dict(required=True, type='str'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_vrouter_name", "pn_forward_ip", "pn_nic", "pn_forward_proto"]], + ["state", "absent", ["pn_vrouter_name", "pn_forward_ip", "pn_nic", "pn_forward_proto"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + forward_ip = module.params['pn_forward_ip'] + nic = module.params['pn_nic'] + forward_proto = module.params['pn_forward_proto'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + VROUTER_EXISTS, NIC_EXISTS = check_cli(module, cli) + + if VROUTER_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if NIC_EXISTS is False: + module.fail_json( + failed=True, + msg='vRouter with nic %s does not exist' % nic + ) + + if command == 'vrouter-packet-relay-add' or command == 'vrouter-packet-relay-remove': + cli += ' %s' % command + cli += ' vrouter-name %s nic %s' % (vrouter_name, nic) + cli += ' forward-proto %s forward-ip %s' % (forward_proto, forward_ip) + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_pim_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_pim_config.py new file mode 100644 index 00000000..a6cd6acd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouter_pim_config.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vrouter_pim_config +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to modify vrouter-pim-config +description: + - This module can be used to modify pim parameters. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - State the action to perform. Use C(update) to modify the vrouter-pim-config. + required: true + type: str + choices: ['update'] + pn_query_interval: + description: + - igmp query interval in seconds. + required: false + type: str + pn_querier_timeout: + description: + - igmp querier timeout in seconds. + required: false + type: str + pn_hello_interval: + description: + - hello interval in seconds. + required: false + type: str + pn_vrouter_name: + description: + - name of service config. + required: false + type: str +''' + +EXAMPLES = """ +- name: Pim config modify + community.network.pn_vrouter_pim_config: + pn_cliswitch: '192.168.1.1' + pn_query_interval: '10' + pn_querier_timeout: '30' + state: 'update' + pn_vrouter_name: 'ansible-spine1-vrouter' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vrouter-pim-config command. + returned: always + type: list +stderr: + description: set of error responses from the vrouter-pim-config command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for pim ssm config using the vrouter-show command. + If a user already exists on the given switch, return True else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_vrouter_name'] + + show = cli + cli += ' vrouter-show format name no-show-headers ' + out = run_commands(module, cli)[1] + if out: + out = out.split() + if name in out: + pass + else: + return False + + cli = show + cli += ' vrouter-show name %s format proto-multi no-show-headers' % name + out = run_commands(module, cli)[1] + if out: + out = out.split() + + return True if 'none' not in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + update='vrouter-pim-config-modify' + ) + + module = AnsibleModule( + argument_spec=dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=True, type='str', + choices=state_map.keys()), + pn_query_interval=dict(required=False, type='str'), + pn_querier_timeout=dict(required=False, type='str'), + pn_hello_interval=dict(required=False, type='str'), + pn_vrouter_name=dict(required=True, type='str'), + ), + required_if=( + ['state', 'update', ['pn_vrouter_name']], + ), + required_one_of=[['pn_query_interval', + 'pn_querier_timeout', + 'pn_hello_interval']] + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + query_interval = module.params['pn_query_interval'] + querier_timeout = module.params['pn_querier_timeout'] + hello_interval = module.params['pn_hello_interval'] + vrouter_name = module.params['pn_vrouter_name'] + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + if command == 'vrouter-pim-config-modify': + PIM_SSM_CONFIG = check_cli(module, cli) + if PIM_SSM_CONFIG is False: + module.exit_json( + skipped=True, + msg='vrouter proto-multi is not configured/vrouter is not created' + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + if querier_timeout: + cli += ' querier-timeout ' + querier_timeout + if hello_interval: + cli += ' hello-interval ' + hello_interval + if query_interval: + cli += ' query-interval ' + query_interval + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterbgp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterbgp.py new file mode 100644 index 00000000..5e1d4ffa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterbgp.py @@ -0,0 +1,485 @@ +#!/usr/bin/python +""" PN-CLI vrouter-bgp-add/vrouter-bgp-remove/vrouter-bgp-modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterbgp +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove/modify vrouter-bgp. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-bgp-add, vrouter-bgp-remove, vrouter-bgp-modify command. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a vRouter service that forwards traffic between + networks and implements Layer 4 protocols. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add bgp, + 'absent' to remove bgp and 'update' to modify bgp. + required: True + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify a name for the vRouter service. + required: True + pn_neighbor: + description: + - Specify a neighbor IP address to use for BGP. + - Required for vrouter-bgp-add. + pn_remote_as: + description: + - Specify the remote Autonomous System(AS) number. This value is between + 1 and 4294967295. + - Required for vrouter-bgp-add. + pn_next_hop_self: + description: + - Specify if the next-hop is the same router or not. + type: bool + pn_password: + description: + - Specify a password, if desired. + pn_ebgp: + description: + - Specify a value for external BGP to accept or attempt BGP connections + to external peers, not directly connected, on the network. This is a + value between 1 and 255. + pn_prefix_listin: + description: + - Specify the prefix list to filter traffic inbound. + pn_prefix_listout: + description: + - Specify the prefix list to filter traffic outbound. + pn_route_reflector: + description: + - Specify if a route reflector client is used. + type: bool + pn_override_capability: + description: + - Specify if you want to override capability. + type: bool + pn_soft_reconfig: + description: + - Specify if you want a soft reconfiguration of inbound traffic. + type: bool + pn_max_prefix: + description: + - Specify the maximum number of prefixes. + pn_max_prefix_warn: + description: + - Specify if you want a warning message when the maximum number of + prefixes is exceeded. + type: bool + pn_bfd: + description: + - Specify if you want BFD protocol support for fault detection. + type: bool + pn_multiprotocol: + description: + - Specify a multi-protocol for BGP. + choices: ['ipv4-unicast', 'ipv6-unicast'] + pn_weight: + description: + - Specify a default weight value between 0 and 65535 for the neighbor + routes. + pn_default_originate: + description: + - Specify if you want announce default routes to the neighbor or not. + type: bool + pn_keepalive: + description: + - Specify BGP neighbor keepalive interval in seconds. + pn_holdtime: + description: + - Specify BGP neighbor holdtime in seconds. + pn_route_mapin: + description: + - Specify inbound route map for neighbor. + pn_route_mapout: + description: + - Specify outbound route map for neighbor. +''' + +EXAMPLES = """ +- name: Add vrouter-bgp + community.network.pn_vrouterbgp: + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_neighbor: 104.104.104.1 + pn_remote_as: 1800 + +- name: Remove vrouter-bgp + community.network.pn_vrouterbgp: + state: 'absent' + pn_name: 'ansible-vrouter' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterbpg command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouterbgp command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +NEIGHBOR_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-bgp-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If a BGP neighbor with the given ip exists on the given vRouter, + return NEIGHBOR_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, NEIGHBOR_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + # Global flags + global VROUTER_EXISTS, NEIGHBOR_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for BGP neighbors + show = cli + ' vrouter-bgp-show vrouter-name %s ' % vrouter_name + show += 'format neighbor no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if neighbor in out: + NEIGHBOR_EXISTS = True + else: + NEIGHBOR_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-bgp-add' + if state == 'absent': + command = 'vrouter-bgp-remove' + if state == 'update': + command = 'vrouter-bgp-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_neighbor=dict(type='str'), + pn_remote_as=dict(type='str'), + pn_next_hop_self=dict(type='bool'), + pn_password=dict(type='str', no_log=True), + pn_ebgp=dict(type='int'), + pn_prefix_listin=dict(type='str'), + pn_prefix_listout=dict(type='str'), + pn_route_reflector=dict(type='bool'), + pn_override_capability=dict(type='bool'), + pn_soft_reconfig=dict(type='bool'), + pn_max_prefix=dict(type='int'), + pn_max_prefix_warn=dict(type='bool'), + pn_bfd=dict(type='bool'), + pn_multiprotocol=dict(type='str', + choices=['ipv4-unicast', 'ipv6-unicast']), + pn_weight=dict(type='int'), + pn_default_originate=dict(type='bool'), + pn_keepalive=dict(type='str'), + pn_holdtime=dict(type='str'), + pn_route_mapin=dict(type='str'), + pn_route_mapout=dict(type='str') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_neighbor", "pn_remote_as"]], + ["state", "absent", + ["pn_vrouter_name", "pn_neighbor"]], + ["state", "update", + ["pn_vrouter_name", "pn_neighbor"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + neighbor = module.params['pn_neighbor'] + remote_as = module.params['pn_remote_as'] + next_hop_self = module.params['pn_next_hop_self'] + password = module.params['pn_password'] + ebgp = module.params['pn_ebgp'] + prefix_listin = module.params['pn_prefix_listin'] + prefix_listout = module.params['pn_prefix_listout'] + route_reflector = module.params['pn_route_reflector'] + override_capability = module.params['pn_override_capability'] + soft_reconfig = module.params['pn_soft_reconfig'] + max_prefix = module.params['pn_max_prefix'] + max_prefix_warn = module.params['pn_max_prefix_warn'] + bfd = module.params['pn_bfd'] + multiprotocol = module.params['pn_multiprotocol'] + weight = module.params['pn_weight'] + default_originate = module.params['pn_default_originate'] + keepalive = module.params['pn_keepalive'] + holdtime = module.params['pn_holdtime'] + route_mapin = module.params['pn_route_mapin'] + route_mapout = module.params['pn_route_mapout'] + + # Building the CLI command string + cli = pn_cli(module) + + command = get_command_from_state(state) + if command == 'vrouter-bgp-remove': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NEIGHBOR_EXISTS is False: + module.exit_json( + skipped=True, + msg=('BGP neighbor with IP %s does not exist on %s' + % (neighbor, vrouter_name)) + ) + cli += (' %s vrouter-name %s neighbor %s ' + % (command, vrouter_name, neighbor)) + + else: + + if command == 'vrouter-bgp-add': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NEIGHBOR_EXISTS is True: + module.exit_json( + skipped=True, + msg=('BGP neighbor with IP %s already exists on %s' + % (neighbor, vrouter_name)) + ) + + cli += (' %s vrouter-name %s neighbor %s ' + % (command, vrouter_name, neighbor)) + + if remote_as: + cli += ' remote-as ' + str(remote_as) + + if next_hop_self is True: + cli += ' next-hop-self ' + if next_hop_self is False: + cli += ' no-next-hop-self ' + + if password: + cli += ' password ' + password + + if ebgp: + cli += ' ebgp-multihop ' + str(ebgp) + + if prefix_listin: + cli += ' prefix-list-in ' + prefix_listin + + if prefix_listout: + cli += ' prefix-list-out ' + prefix_listout + + if route_reflector is True: + cli += ' route-reflector-client ' + if route_reflector is False: + cli += ' no-route-reflector-client ' + + if override_capability is True: + cli += ' override-capability ' + if override_capability is False: + cli += ' no-override-capability ' + + if soft_reconfig is True: + cli += ' soft-reconfig-inbound ' + if soft_reconfig is False: + cli += ' no-soft-reconfig-inbound ' + + if max_prefix: + cli += ' max-prefix ' + str(max_prefix) + + if max_prefix_warn is True: + cli += ' max-prefix-warn-only ' + if max_prefix_warn is False: + cli += ' no-max-prefix-warn-only ' + + if bfd is True: + cli += ' bfd ' + if bfd is False: + cli += ' no-bfd ' + + if multiprotocol: + cli += ' multi-protocol ' + multiprotocol + + if weight: + cli += ' weight ' + str(weight) + + if default_originate is True: + cli += ' default-originate ' + if default_originate is False: + cli += ' no-default-originate ' + + if keepalive: + cli += ' neighbor-keepalive-interval ' + keepalive + + if holdtime: + cli += ' neighbor-holdtime ' + holdtime + + if route_mapin: + cli += ' route-map-in ' + route_mapin + + if route_mapout: + cli += ' route-map-out ' + route_mapout + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterif.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterif.py new file mode 100644 index 00000000..173565a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterif.py @@ -0,0 +1,490 @@ +#!/usr/bin/python +""" PN-CLI vrouter-interface-add/remove/modify """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterif +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove/modify vrouter-interface. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-interface-add, vrouter-interface-remove, + vrouter-interface-modify command. + - You configure interfaces to vRouter services on a fabric, cluster, + standalone switch or virtual network(VNET). +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add vrouter interface, + 'absent' to remove vrouter interface and 'update' to modify vrouter + interface. + required: True + choices: ['present', 'absent', 'update'] + pn_vrouter_name: + description: + - Specify the name of the vRouter interface. + required: True + pn_vlan: + description: + - Specify the VLAN identifier. This is a value between 1 and 4092. + pn_interface_ip: + description: + - Specify the IP address of the interface in x.x.x.x/n format. + pn_assignment: + description: + - Specify the DHCP method for IP address assignment. + choices: ['none', 'dhcp', 'dhcpv6', 'autov6'] + pn_vxlan: + description: + - Specify the VXLAN identifier. This is a value between 1 and 16777215. + pn_interface: + description: + - Specify if the interface is management, data or span interface. + choices: ['mgmt', 'data', 'span'] + pn_alias: + description: + - Specify an alias for the interface. + pn_exclusive: + description: + - Specify if the interface is exclusive to the configuration. Exclusive + means that other configurations cannot use the interface. Exclusive is + specified when you configure the interface as span interface and allows + higher throughput through the interface. + type: bool + required: False + pn_nic_enable: + description: + - Specify if the NIC is enabled or not + type: bool + pn_vrrp_id: + description: + - Specify the ID for the VRRP interface. The IDs on both vRouters must be + the same IS number. + pn_vrrp_priority: + description: + - Specify the priority for the VRRP interface. This is a value between + 1 (lowest) and 255 (highest). + pn_vrrp_adv_int: + description: + - Specify a VRRP advertisement interval in milliseconds. The range is + from 30 to 40950 with a default value of 1000. + pn_l3port: + description: + - Specify a Layer 3 port for the interface. + pn_secondary_macs: + description: + - Specify a secondary MAC address for the interface. + pn_nic_str: + description: + - Specify the type of NIC. Used for vrouter-interface remove/modify. +''' + +EXAMPLES = """ +- name: Add vrouter-interface + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 + pn_vlan: 101 + +- name: Add VRRP.. + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 + pn_vrrp_ip: 101.101.101.1/24 + pn_vrrp_priority: 100 + pn_vlan: 101 + +- name: Remove vrouter-interface + community.network.pn_vrouterif: + pn_cliusername: admin + pn_clipassword: admin + state: 'absent' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: 101.101.101.2/24 +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterif command. + returned: on success + type: list +stderr: + description: The set of error responses from the vrouterif command. + returned: on error + type: str +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +INTERFACE_EXISTS = None +NIC_EXISTS = None +VRRP_EXISTS = None + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the vrouter-interface-show + command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + + If an interface with the given ip exists on the given vRouter, + return INTERFACE_EXISTS as True else False. This is required for + vrouter-interface-add. + + If nic_str exists on the given vRouter, return NIC_EXISTS as True else + False. This is required for vrouter-interface-remove. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + nic_str = module.params['pn_nic_str'] + + # Global flags + global VROUTER_EXISTS, INTERFACE_EXISTS, NIC_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + if interface_ip: + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip %s format ip,nic no-show-headers' % interface_ip + show = shlex.split(show) + out = module.run_command(show)[1] + if out: + INTERFACE_EXISTS = True + else: + INTERFACE_EXISTS = False + + if nic_str: + # Check for nic + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += ' format nic no-show-headers' + show = shlex.split(show) + out = module.run_command(show)[1] + if nic_str in out: + NIC_EXISTS = True + else: + NIC_EXISTS = False + + +def get_nic(module, cli): + """ + This module checks if VRRP interface can be added. If No, return VRRP_EXISTS + as True. + If Yes, fetch the nic string from the primary interface and return nic and + VRRP_EXISTS as False. + :param module: + :param cli: + :return: nic, Global Boolean: VRRP_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + + global VRRP_EXISTS + + # Check for interface and VRRP and fetch nic for VRRP + show = cli + ' vrouter-interface-show vrouter-name %s ' % vrouter_name + show += 'ip %s format ip,nic no-show-headers' % interface_ip + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if len(out) > 3: + VRRP_EXISTS = True + return None + else: + nic = out[2] + VRRP_EXISTS = False + return nic + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-interface-add' + if state == 'absent': + command = 'vrouter-interface-remove' + if state == 'update': + command = 'vrouter-interface-modify' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent', 'update']), + pn_vrouter_name=dict(required=True, type='str'), + pn_vlan=dict(type='int'), + pn_interface_ip=dict(required=True, type='str'), + pn_assignment=dict(type='str', + choices=['none', 'dhcp', 'dhcpv6', 'autov6']), + pn_vxlan=dict(type='int'), + pn_interface=dict(type='str', choices=['mgmt', 'data', 'span']), + pn_alias=dict(type='str'), + pn_exclusive=dict(type='bool'), + pn_nic_enable=dict(type='bool'), + pn_vrrp_id=dict(type='int'), + pn_vrrp_priority=dict(type='int'), + pn_vrrp_adv_int=dict(type='str'), + pn_l3port=dict(type='str'), + pn_secondary_macs=dict(type='str'), + pn_nic_str=dict(type='str') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_interface_ip"]], + ["state", "absent", + ["pn_vrouter_name", "pn_nic_str"]] + ), + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + vlan = module.params['pn_vlan'] + interface_ip = module.params['pn_interface_ip'] + assignment = module.params['pn_assignment'] + vxlan = module.params['pn_vxlan'] + interface = module.params['pn_interface'] + alias = module.params['pn_alias'] + exclusive = module.params['pn_exclusive'] + nic_enable = module.params['pn_nic_enable'] + vrrp_id = module.params['pn_vrrp_id'] + vrrp_priority = module.params['pn_vrrp_priority'] + vrrp_adv_int = module.params['pn_vrrp_adv_int'] + l3port = module.params['pn_l3port'] + secondary_macs = module.params['pn_secondary_macs'] + nic_str = module.params['pn_nic_str'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + check_cli(module, cli) + if command == 'vrouter-interface-add': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + + if vrrp_id: + vrrp_primary = get_nic(module, cli) + if VRRP_EXISTS is True: + module.exit_json( + skipped=True, + msg=('VRRP interface on %s already exists. Check ' + 'the IP addresses' % vrouter_name) + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + cli += (' ip %s vrrp-primary %s vrrp-id %s ' + % (interface_ip, vrrp_primary, str(vrrp_id))) + if vrrp_priority: + cli += ' vrrp-priority %s ' % str(vrrp_priority) + if vrrp_adv_int: + cli += ' vrrp-adv-int %s ' % vrrp_adv_int + + else: + if INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg=('vRouter interface on %s already exists. Check the ' + 'IP addresses' % vrouter_name) + ) + cli += ' %s vrouter-name %s ' % (command, vrouter_name) + cli += ' ip %s ' % interface_ip + + if vlan: + cli += ' vlan ' + str(vlan) + + if l3port: + cli += ' l3-port ' + l3port + + if assignment: + cli += ' assignment ' + assignment + + if vxlan: + cli += ' vxlan ' + str(vxlan) + + if interface: + cli += ' if ' + interface + + if alias: + cli += ' alias-on ' + alias + + if exclusive is True: + cli += ' exclusive ' + if exclusive is False: + cli += ' no-exclusive ' + + if nic_enable is True: + cli += ' nic-enable ' + if nic_enable is False: + cli += ' nic-disable ' + + if secondary_macs: + cli += ' secondary-macs ' + secondary_macs + + if command == 'vrouter-interface-remove': + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if NIC_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter interface with nic %s does not exist' % nic_str + ) + cli += ' %s vrouter-name %s nic %s ' % (command, vrouter_name, nic_str) + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterlbif.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterlbif.py new file mode 100644 index 00000000..379f3142 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vrouterlbif.py @@ -0,0 +1,331 @@ +#!/usr/bin/python +""" PN CLI vrouter-loopback-interface-add/remove """ + +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: pn_vrouterlbif +author: "Pluribus Networks (@amitsi)" +short_description: CLI command to add/remove vrouter-loopback-interface. +deprecated: + removed_in: 2.0.0 # was Ansible 2.12 + why: Doesn't support latest Pluribus Networks netvisor + alternative: Latest modules will be pushed in Ansible future versions. +description: + - Execute vrouter-loopback-interface-add, vrouter-loopback-interface-remove + commands. + - Each fabric, cluster, standalone switch, or virtual network (VNET) can + provide its tenants with a virtual router (vRouter) service that forwards + traffic between networks and implements Layer 3 protocols. +options: + pn_cliusername: + description: + - Provide login username if user is not root. + required: False + pn_clipassword: + description: + - Provide login password if user is not root. + required: False + pn_cliswitch: + description: + - Target switch(es) to run the cli on. + required: False + default: 'local' + state: + description: + - State the action to perform. Use 'present' to add vrouter loopback + interface and 'absent' to remove vrouter loopback interface. + required: True + choices: ['present', 'absent'] + pn_vrouter_name: + description: + - Specify the name of the vRouter. + required: True + pn_index: + description: + - Specify the interface index from 1 to 255. + pn_interface_ip: + description: + - Specify the IP address. + required: True +''' + +EXAMPLES = """ +- name: Add vrouter-loopback-interface + community.network.pn_vrouterlbif: + state: 'present' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: '104.104.104.1' + +- name: Remove vrouter-loopback-interface + community.network.pn_vrouterlbif: + state: 'absent' + pn_vrouter_name: 'ansible-vrouter' + pn_interface_ip: '104.104.104.1' +""" + +RETURN = """ +command: + description: The CLI command run on the target node(s). + returned: always + type: str +stdout: + description: The set of responses from the vrouterlb command. + returned: always + type: list +stderr: + description: The set of error responses from the vrouterlb command. + returned: on error + type: list +changed: + description: Indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +import shlex + +# Ansible boiler-plate +from ansible.module_utils.basic import AnsibleModule + +VROUTER_EXISTS = None +LB_INTERFACE_EXISTS = None +# Index range +MIN_INDEX = 1 +MAX_INDEX = 255 + + +def pn_cli(module): + """ + This method is to generate the cli portion to launch the Netvisor cli. + It parses the username, password, switch parameters from module. + :param module: The Ansible module to fetch username, password and switch + :return: returns the cli string for further processing + """ + username = module.params['pn_cliusername'] + password = module.params['pn_clipassword'] + cliswitch = module.params['pn_cliswitch'] + + if username and password: + cli = '/usr/bin/cli --quiet --user %s:%s ' % (username, password) + else: + cli = '/usr/bin/cli --quiet ' + + if cliswitch == 'local': + cli += ' switch-local ' + else: + cli += ' switch ' + cliswitch + return cli + + +def check_cli(module, cli): + """ + This method checks if vRouter exists on the target node. + This method also checks for idempotency using the + vrouter-loopback-interface-show command. + If the given vRouter exists, return VROUTER_EXISTS as True else False. + If a loopback interface with the given ip exists on the given vRouter, + return LB_INTERFACE_EXISTS as True else False. + + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + :return Global Booleans: VROUTER_EXISTS, LB_INTERFACE_EXISTS + """ + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + + # Global flags + global VROUTER_EXISTS, LB_INTERFACE_EXISTS + + # Check for vRouter + check_vrouter = cli + ' vrouter-show format name no-show-headers ' + check_vrouter = shlex.split(check_vrouter) + out = module.run_command(check_vrouter)[1] + out = out.split() + + if vrouter_name in out: + VROUTER_EXISTS = True + else: + VROUTER_EXISTS = False + + # Check for loopback interface + show = (cli + ' vrouter-loopback-interface-show vrouter-name %s format ip ' + 'no-show-headers' % vrouter_name) + show = shlex.split(show) + out = module.run_command(show)[1] + out = out.split() + + if interface_ip in out: + LB_INTERFACE_EXISTS = True + else: + LB_INTERFACE_EXISTS = False + + +def run_cli(module, cli): + """ + This method executes the cli command on the target node(s) and returns the + output. The module then exits based on the output. + :param cli: the complete cli string to be executed on the target node(s). + :param module: The Ansible module to fetch command + """ + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + command = get_command_from_state(state) + + cmd = shlex.split(cli) + + # 'out' contains the output + # 'err' contains the error messages + result, out, err = module.run_command(cmd) + + print_cli = cli.split(cliswitch)[1] + + # Response in JSON format + if result != 0: + module.exit_json( + command=print_cli, + stderr=err.strip(), + msg="%s operation failed" % command, + changed=False + ) + + if out: + module.exit_json( + command=print_cli, + stdout=out.strip(), + msg="%s operation completed" % command, + changed=True + ) + + else: + module.exit_json( + command=print_cli, + msg="%s operation completed" % command, + changed=True + ) + + +def get_command_from_state(state): + """ + This method gets appropriate command name for the state specified. It + returns the command name for the specified state. + :param state: The state for which the respective command name is required. + """ + command = None + if state == 'present': + command = 'vrouter-loopback-interface-add' + if state == 'absent': + command = 'vrouter-loopback-interface-remove' + return command + + +def main(): + """ This portion is for arguments parsing """ + module = AnsibleModule( + argument_spec=dict( + pn_cliusername=dict(required=False, type='str'), + pn_clipassword=dict(required=False, type='str', no_log=True), + pn_cliswitch=dict(required=False, type='str', default='local'), + state=dict(required=True, type='str', + choices=['present', 'absent']), + pn_vrouter_name=dict(required=True, type='str'), + pn_interface_ip=dict(type='str'), + pn_index=dict(type='int') + ), + required_if=( + ["state", "present", + ["pn_vrouter_name", "pn_interface_ip"]], + ["state", "absent", + ["pn_vrouter_name", "pn_interface_ip"]] + ) + ) + + # Accessing the arguments + state = module.params['state'] + vrouter_name = module.params['pn_vrouter_name'] + interface_ip = module.params['pn_interface_ip'] + index = module.params['pn_index'] + + command = get_command_from_state(state) + + # Building the CLI command string + cli = pn_cli(module) + + if index: + if not MIN_INDEX <= index <= MAX_INDEX: + module.exit_json( + msg="Index must be between 1 and 255", + changed=False + ) + index = str(index) + + if command == 'vrouter-loopback-interface-remove': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg='vRouter %s does not exist' % vrouter_name + ) + if LB_INTERFACE_EXISTS is False: + module.exit_json( + skipped=True, + msg=('Loopback interface with IP %s does not exist on %s' + % (interface_ip, vrouter_name)) + ) + if not index: + # To remove loopback interface, we need the index. + # If index is not specified, get the Loopback interface index + # using the given interface ip. + get_index = cli + get_index += (' vrouter-loopback-interface-show vrouter-name %s ip ' + '%s ' % (vrouter_name, interface_ip)) + get_index += 'format index no-show-headers' + + get_index = shlex.split(get_index) + out = module.run_command(get_index)[1] + index = out.split()[1] + + cli += ' %s vrouter-name %s index %s' % (command, vrouter_name, index) + + if command == 'vrouter-loopback-interface-add': + check_cli(module, cli) + if VROUTER_EXISTS is False: + module.exit_json( + skipped=True, + msg=('vRouter %s does not exist' % vrouter_name) + ) + if LB_INTERFACE_EXISTS is True: + module.exit_json( + skipped=True, + msg=('Loopback interface with IP %s already exists on %s' + % (interface_ip, vrouter_name)) + ) + cli += (' %s vrouter-name %s ip %s' + % (command, vrouter_name, interface_ip)) + if index: + cli += ' index %s ' % index + + run_cli(module, cli) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vtep.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vtep.py new file mode 100644 index 00000000..cc0a172c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/pn_vtep.py @@ -0,0 +1,198 @@ +#!/usr/bin/python +# Copyright: (c) 2018, Pluribus Networks +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: pn_vtep +author: "Pluribus Networks (@rajaspachipulusu17)" +short_description: CLI command to create/delete vtep +description: + - This module can be used to create a vtep and delete a vtep. +options: + pn_cliswitch: + description: + - Target switch to run the CLI on. + required: false + type: str + state: + description: + - vtep configuration command. + required: false + choices: ['present', 'absent'] + type: str + default: 'present' + pn_name: + description: + - vtep name. + required: false + type: str + pn_ip: + description: + - Primary IP address. + required: false + type: str + pn_vrouter_name: + description: + - name of the vrouter service. + required: false + type: str + pn_virtual_ip: + description: + - Virtual/Secondary IP address. + required: false + type: str + pn_location: + description: + - switch name. + required: false + type: str + pn_switch_in_cluster: + description: + - Tells whether switch in cluster or not. + required: false + type: bool + default: True +''' + +EXAMPLES = """ +- name: Create vtep + community.network.pn_vtep: + pn_cliswitch: 'sw01' + pn_name: 'foo' + pn_vrouter_name: 'foo-vrouter' + pn_ip: '22.22.22.2' + pn_location: 'sw01' + pn_virtual_ip: "22.22.22.1" + +- name: Delete vtep + community.network.pn_vtep: + pn_cliswitch: 'sw01' + state: 'absent' + pn_name: 'foo' +""" + +RETURN = """ +command: + description: the CLI command run on the target node. + returned: always + type: str +stdout: + description: set of responses from the vtep command. + returned: always + type: list +stderr: + description: set of error responses from the vtep command. + returned: on error + type: list +changed: + description: indicates whether the CLI caused changes on the target. + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.network.plugins.module_utils.network.netvisor.pn_nvos import pn_cli, run_cli +from ansible_collections.community.network.plugins.module_utils.network.netvisor.netvisor import run_commands + + +def check_cli(module, cli): + """ + This method checks for idempotency using the vtep-show command. + If a name exists, return True if name exists else False. + :param module: The Ansible module to fetch input parameters + :param cli: The CLI string + """ + name = module.params['pn_name'] + + cli += ' vtep-show format name no-show-headers' + out = run_commands(module, cli)[1] + + if out: + out = out.split() + + return True if name in out else False + + +def main(): + """ This section is for arguments parsing """ + + state_map = dict( + present='vtep-create', + absent='vtep-delete' + ) + + argument_spec = dict( + pn_cliswitch=dict(required=False, type='str'), + state=dict(required=False, type='str', choices=state_map.keys(), default='present'), + pn_name=dict(required=False, type='str'), + pn_ip=dict(required=False, type='str'), + pn_vrouter_name=dict(required=False, type='str'), + pn_virtual_ip=dict(required=False, type='str'), + pn_location=dict(required=False, type='str'), + pn_switch_in_cluster=dict(required=False, type='bool', default='True') + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ["state", "present", ["pn_name", "pn_ip", "pn_vrouter_name", "pn_location"]], + ["state", "absent", ["pn_name"]], + ), + ) + + # Accessing the arguments + cliswitch = module.params['pn_cliswitch'] + state = module.params['state'] + name = module.params['pn_name'] + ip = module.params['pn_ip'] + vrouter_name = module.params['pn_vrouter_name'] + virtual_ip = module.params['pn_virtual_ip'] + location = module.params['pn_location'] + switch_in_cluster = module.params['pn_switch_in_cluster'] + + if switch_in_cluster and not virtual_ip and state == 'present': + module.exit_json( + failed=True, + msg='virtual ip is required when switch is in cluster' + ) + + command = state_map[state] + + # Building the CLI command string + cli = pn_cli(module, cliswitch) + + NAME_EXISTS = check_cli(module, cli) + + cli += ' %s name %s ' % (command, name) + + if command == 'vtep-delete': + if NAME_EXISTS is False: + module.exit_json( + skipped=True, + msg='vtep with name %s does not exist' % name + ) + + if command == 'vtep-create': + if NAME_EXISTS is True: + module.exit_json( + skipped=True, + msg='vtpe with name %s already exists' % name + ) + + cli += 'vrouter-name %s ' % vrouter_name + cli += 'ip %s ' % ip + cli += 'location %s ' % location + + if virtual_ip: + cli += 'virtual-ip %s ' % virtual_ip + + run_cli(module, cli, state_map) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_api.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_api.py new file mode 100644 index 00000000..9969e6d6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_api.py @@ -0,0 +1,482 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Nikolay Dachev +# GNU General Public License v3.0+ https://www.gnu.org/licenses/gpl-3.0.txt + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_api +version_added: 1.1.0 +author: "Nikolay Dachev (@NikolayDachev)" +short_description: Ansible module for RouterOS API +description: + - Ansible module for RouterOS API with python librouteros. + - This module can add, remove, update, query and execute arbitrary command in routeros via API. +notes: + - I(add), I(remove), I(update), I(cmd) and I(query) are mutually exclusive. + - I(check_mode) is not supported. +requirements: + - librouteros + - Python >= 3.6 (for librouteros) +options: + hostname: + description: + - RouterOS hostname API. + required: true + type: str + username: + description: + - RouterOS login user. + required: true + type: str + password: + description: + - RouterOS user password. + required: true + type: str + ssl: + description: + - If is set TLS will be used for RouterOS API connection. + required: false + type: bool + default: false + port: + description: + - RouterOS api port. If ssl is set, port will apply to ssl connection. + - Defaults are C(8728) for the HTTP API, and C(8729) for the HTTPS API. + type: int + path: + description: + - Main path for all other arguments. + - If other arguments are not set, api will return all items in selected path. + - Example C(ip address). Equivalent of RouterOS CLI C(/ip address print). + required: true + type: str + add: + description: + - Will add selected arguments in selected path to RouterOS config. + - Example C(address=1.1.1.1/32 interface=ether1). + - Equivalent in RouterOS CLI C(/ip address add address=1.1.1.1/32 interface=ether1). + type: str + remove: + description: + - Remove config/value from RouterOS by '.id'. + - Example C(*03) will remove config/value with C(id=*03) in selected path. + - Equivalent in RouterOS CLI C(/ip address remove numbers=1). + - Note C(number) in RouterOS CLI is different from C(.id). + type: str + update: + description: + - Update config/value in RouterOS by '.id' in selected path. + - Example C(.id=*03 address=1.1.1.3/32) and path C(ip address) will replace existing ip address with C(.id=*03). + - Equivalent in RouterOS CLI C(/ip address set address=1.1.1.3/32 numbers=1). + - Note C(number) in RouterOS CLI is different from C(.id). + type: str + query: + description: + - Query given path for selected query attributes from RouterOS aip and return '.id'. + - WHERE is key word which extend query. WHERE format is key operator value - with spaces. + - WHERE valid operators are C(==), C(!=), C(>), C(<). + - Example path C(ip address) and query C(.id address) will return only C(.id) and C(address) for all items in C(ip address) path. + - Example path C(ip address) and query C(.id address WHERE address == 1.1.1.3/32). + will return only C(.id) and C(address) for items in C(ip address) path, where address is eq to 1.1.1.3/32. + - Example path C(interface) and query C(mtu name WHERE mut > 1400) will + return only interfaces C(mtu,name) where mtu is bigger than 1400. + - Equivalent in RouterOS CLI C(/interface print where mtu > 1400). + type: str + cmd: + description: + - Execute any/arbitrary command in selected path, after the command we can add C(.id). + - Example path C(system script) and cmd C(run .id=*03) is equivalent in RouterOS CLI C(/system script run number=0). + - Example path C(ip address) and cmd C(print) is equivalent in RouterOS CLI C(/ip address print). + type: str +''' + +EXAMPLES = ''' +--- +- name: Test routeros_api + hosts: localhost + gather_facts: no + vars: + hostname: "ros_api_hostname/ip" + username: "admin" + password: "secret_password" + + path: "ip address" + + nic: "ether2" + ip1: "1.1.1.1/32" + ip2: "2.2.2.2/32" + ip3: "3.3.3.3/32" + + tasks: + - name: Get "{{ path }} print" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + register: print_path + + - name: Dump "{{ path }} print" output + ansible.builtin.debug: + msg: '{{ print_path }}' + + - name: Add ip address "{{ ip1 }}" and "{{ ip2 }}" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + add: "{{ item }}" + loop: + - "address={{ ip1 }} interface={{ nic }}" + - "address={{ ip2 }} interface={{ nic }}" + register: addout + + - name: Dump "Add ip address" output - ".id" for new added items + ansible.builtin.debug: + msg: '{{ addout }}' + + - name: Query for ".id" in "{{ path }} WHERE address == {{ ip2 }}" + community.network.routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + query: ".id address WHERE address == {{ ip2 }}" + register: queryout + + - name: Dump "Query for" output and set fact with ".id" for "{{ ip2 }}" + ansible.builtin.debug: + msg: '{{ queryout }}' + + - ansible.builtin.set_fact: + query_id : "{{ queryout['msg'][0]['.id'] }}" + + - name: Update ".id = {{ query_id }}" taken with custom fact "fquery_id" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + update: ".id={{ query_id }} address={{ ip3 }}" + register: updateout + + - name: Dump "Update" output + ansible.builtin.debug: + msg: '{{ updateout }}' + + - name: Remove ips - stage 1 - query ".id" for "{{ ip2 }}" and "{{ ip3 }}" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + query: ".id address WHERE address == {{ item }}" + register: id_to_remove + loop: + - "{{ ip2 }}" + - "{{ ip3 }}" + + - name: set fact for ".id" from "Remove ips - stage 1 - query" + ansible.builtin.set_fact: + to_be_remove: "{{ to_be_remove |default([]) + [item['msg'][0]['.id']] }}" + loop: "{{ id_to_remove.results }}" + + - name: Dump "Remove ips - stage 1 - query" output + ansible.builtin.debug: + msg: '{{ to_be_remove }}' + + # Remove "{{ rmips }}" with ".id" by "to_be_remove" from query + - name: Remove ips - stage 2 - remove "{{ ip2 }}" and "{{ ip3 }}" by '.id' + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "{{ path }}" + remove: "{{ item }}" + register: remove + loop: "{{ to_be_remove }}" + + - name: Dump "Remove ips - stage 2 - remove" output + ansible.builtin.debug: + msg: '{{ remove }}' + + - name: Arbitrary command example "/system identity print" + routeros_api: + hostname: "{{ hostname }}" + password: "{{ password }}" + username: "{{ username }}" + path: "system identity" + cmd: "print" + register: cmdout + + - name: Dump "Arbitrary command example" output + ansible.builtin.debug: + msg: "{{ cmdout }}" +''' + +RETURN = ''' +--- +message: + description: All outputs are in list with dictionary elements returned from RouterOS api. + sample: C([{...},{...}]) + type: list + returned: always +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils._text import to_native + +import ssl +import traceback + +LIB_IMP_ERR = None +try: + from librouteros import connect + from librouteros.query import Key + HAS_LIB = True +except Exception as e: + HAS_LIB = False + LIB_IMP_ERR = traceback.format_exc() + + +class ROS_api_module: + def __init__(self): + module_args = (dict( + username=dict(type='str', required=True), + password=dict(type='str', required=True, no_log=True), + hostname=dict(type='str', required=True), + port=dict(type='int'), + ssl=dict(type='bool', default=False), + path=dict(type='str', required=True), + add=dict(type='str'), + remove=dict(type='str'), + update=dict(type='str'), + cmd=dict(type='str'), + query=dict(type='str'))) + + self.module = AnsibleModule(argument_spec=module_args, + supports_check_mode=False, + mutually_exclusive=(('add', 'remove', 'update', + 'cmd', 'query'),),) + + if not HAS_LIB: + self.module.fail_json(msg=missing_required_lib("librouteros"), + exception=LIB_IMP_ERR) + + self.api = self.ros_api_connect(self.module.params['username'], + self.module.params['password'], + self.module.params['hostname'], + self.module.params['port'], + self.module.params['ssl']) + + self.path = self.list_remove_empty(self.module.params['path'].split(' ')) + self.add = self.module.params['add'] + self.remove = self.module.params['remove'] + self.update = self.module.params['update'] + self.arbitrary = self.module.params['cmd'] + + self.where = None + self.query = self.module.params['query'] + if self.query: + if 'WHERE' in self.query: + split = self.query.split('WHERE') + self.query = self.list_remove_empty(split[0].split(' ')) + self.where = self.list_remove_empty(split[1].split(' ')) + else: + self.query = self.list_remove_empty(self.module.params['query'].split(' ')) + + self.result = dict( + message=[]) + + # create api base path + self.api_path = self.api_add_path(self.api, self.path) + + # api call's + if self.add: + self.api_add() + elif self.remove: + self.api_remove() + elif self.update: + self.api_update() + elif self.query: + self.api_query() + elif self.arbitrary: + self.api_arbitrary() + else: + self.api_get_all() + + def list_remove_empty(self, check_list): + while("" in check_list): + check_list.remove("") + return check_list + + def list_to_dic(self, ldict): + dict = {} + for p in ldict: + if '=' not in p: + self.errors("missing '=' after '%s'" % p) + p = p.split('=') + if p[0] == 'id': + self.errors("'%s' must be '.id'" % p[0]) + if p[1]: + dict[p[0]] = p[1] + return dict + + def api_add_path(self, api, path): + api_path = api.path() + for p in path: + api_path = api_path.join(p) + return api_path + + def api_get_all(self): + try: + for i in self.api_path: + self.result['message'].append(i) + self.return_result(False, True) + except Exception as e: + self.errors(e) + + def api_add(self): + param = self.list_to_dic(self.add.split(' ')) + try: + self.result['message'].append("added: .id= %s" + % self.api_path.add(**param)) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_remove(self): + try: + self.api_path.remove(self.remove) + self.result['message'].append("removed: .id= %s" % self.remove) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_update(self): + param = self.list_to_dic(self.update.split(' ')) + if '.id' not in param.keys(): + self.errors("missing '.id' for %s" % param) + try: + self.api_path.update(**param) + self.result['message'].append("updated: %s" % param) + self.return_result(True) + except Exception as e: + self.errors(e) + + def api_query(self): + keys = {} + for k in self.query: + if 'id' in k and k != ".id": + self.errors("'%s' must be '.id'" % k) + keys[k] = Key(k) + try: + if self.where: + if len(self.where) < 3: + self.errors("invalid syntax for 'WHERE %s'" + % ' '.join(self.where)) + + where = [] + if self.where[1] == '==': + select = self.api_path.select(*keys).where(keys[self.where[0]] == self.where[2]) + elif self.where[1] == '!=': + select = self.api_path.select(*keys).where(keys[self.where[0]] != self.where[2]) + elif self.where[1] == '>': + select = self.api_path.select(*keys).where(keys[self.where[0]] > self.where[2]) + elif self.where[1] == '<': + select = self.api_path.select(*keys).where(keys[self.where[0]] < self.where[2]) + else: + self.errors("'%s' is not operator for 'where'" + % self.where[1]) + for row in select: + self.result['message'].append(row) + else: + for row in self.api_path.select(*keys): + self.result['message'].append(row) + if len(self.result['message']) < 1: + msg = "no results for '%s 'query' %s" % (' '.join(self.path), + ' '.join(self.query)) + if self.where: + msg = msg + ' WHERE %s' % ' '.join(self.where) + self.result['message'].append(msg) + self.return_result(False) + except Exception as e: + self.errors(e) + + def api_arbitrary(self): + param = {} + self.arbitrary = self.arbitrary.split(' ') + arb_cmd = self.arbitrary[0] + if len(self.arbitrary) > 1: + param = self.list_to_dic(self.arbitrary[1:]) + try: + arbitrary_result = self.api_path(arb_cmd, **param) + for i in arbitrary_result: + self.result['message'].append(i) + self.return_result(False) + except Exception as e: + self.errors(e) + + def return_result(self, ch_status=False, status=True): + if status == "False": + self.module.fail_json(msg=to_native(self.result['message'])) + else: + self.module.exit_json(changed=ch_status, + msg=self.result['message']) + + def errors(self, e): + if e.__class__.__name__ == 'TrapError': + self.result['message'].append("%s" % e) + self.return_result(False, True) + self.result['message'].append("%s" % e) + self.return_result(False, False) + + def ros_api_connect(self, username, password, host, port, use_ssl): + # connect to routeros api + conn_status = {"connection": {"username": username, + "hostname": host, + "port": port, + "ssl": use_ssl, + "status": "Connected"}} + try: + if use_ssl is True: + if not port: + port = 8729 + conn_status["connection"]["port"] = port + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.set_ciphers('ADH:@SECLEVEL=0') + api = connect(username=username, + password=password, + host=host, + ssl_wrapper=ctx.wrap_socket, + port=port) + else: + if not port: + port = 8728 + conn_status["connection"]["port"] = port + api = connect(username=username, + password=password, + host=host, + port=port) + except Exception as e: + conn_status["connection"]["status"] = "error: %s" % e + self.module.fail_json(msg=to_native([conn_status])) + return api + + +def main(): + + ROS_api_module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_command.py new file mode 100644 index 00000000..403df21a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_command.py @@ -0,0 +1,181 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_command +author: "Egor Zaitsev (@heuels)" +short_description: Run commands on remote devices running MikroTik RouterOS +description: + - Sends arbitrary commands to an RouterOS node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +options: + commands: + description: + - List of commands to send to the remote RouterOS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run command on remote devices + community.network.routeros_command: + commands: /system routerboard print + + - name: Run command and check to see if output contains routeros + community.network.routeros_command: + commands: /system resource print + wait_for: result[0] contains MikroTik + + - name: Run multiple commands on remote nodes + community.network.routeros_command: + commands: + - /system routerboard print + - /system identity print + + - name: Run multiple commands and evaluate the output + community.network.routeros_command: + commands: + - /system routerboard print + - /interface ethernet print + wait_for: + - result[0] contains x86 + - result[1] contains ether1 +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" + +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import run_commands +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import routeros_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(routeros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, module.params['commands']) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_facts.py new file mode 100644 index 00000000..05d89152 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/routeros_facts.py @@ -0,0 +1,629 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: routeros_facts +author: "Egor Zaitsev (@heuels)" +short_description: Collect facts from remote devices running MikroTik RouterOS +description: + - Collects a base set of device facts from a remote device that + is running RotuerOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + C(all), C(hardware), C(config), and C(interfaces). Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.routeros_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.routeros_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.routeros_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_arch: + description: The CPU architecture of the device + returned: always + type: str + version_added: 1.2.0 +ansible_net_uptime: + description: The uptime of the device + returned: always + type: str + version_added: 1.2.0 +ansible_net_cpu_load: + description: Current CPU load + returned: always + type: str + version_added: 1.2.0 + +# hardware +ansible_net_spacefree_mb: + description: The available disk space on the remote device in MiB + returned: when hardware is configured + type: dict +ansible_net_spacetotal_mb: + description: The total disk space on the remote device in MiB + returned: when hardware is configured + type: dict +ansible_net_memfree_mb: + description: The available free memory on the remote device in MiB + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in MiB + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of neighbors from the remote device + returned: when interfaces is configured + type: dict + +# routing +ansible_net_bgp_peer: + description: The dict bgp peer + returned: peer information + type: dict + version_added: 1.2.0 +ansible_net_bgp_vpnv4_route: + description: The dict bgp vpnv4 route + returned: vpnv4 route information + type: dict + version_added: 1.2.0 +ansible_net_bgp_instance: + description: The dict bgp instance + returned: bgp instance information + type: dict + version_added: 1.2.0 +ansible_net_route: + description: The dict routes in all routing table + returned: routes information in all routing table + type: dict + version_added: 1.2.0 +ansible_net_ospf_instance: + description: The dict ospf instance + returned: ospf instance information + type: dict + version_added: 1.2.0 +ansible_net_ospf_neighbor: + description: The dict ospf neighbor + returned: ospf neighbor information + type: dict + version_added: 1.2.0 +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import run_commands +from ansible_collections.community.network.plugins.module_utils.network.routeros.routeros import routeros_argument_spec +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = [ + '/system identity print without-paging', + '/system resource print without-paging', + '/system routerboard print without-paging' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['hostname'] = self.parse_hostname(data) + data = self.responses[1] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['arch'] = self.parse_arch(data) + self.facts['uptime'] = self.parse_uptime(data) + self.facts['cpu_load'] = self.parse_cpu_load(data) + data = self.responses[2] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + def parse_hostname(self, data): + match = re.search(r'name:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_version(self, data): + match = re.search(r'version:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'model:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_arch(self, data): + match = re.search(r'architecture-name:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_uptime(self, data): + match = re.search(r'uptime:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_cpu_load(self, data): + match = re.search(r'cpu-load:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'serial-number:\s(.*)\s*$', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + '/system resource print without-paging' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.parse_filesystem_info(data) + self.parse_memory_info(data) + + def parse_filesystem_info(self, data): + match = re.search(r'free-hdd-space:\s(.*)([KMG]iB)', data, re.M) + if match: + self.facts['spacefree_mb'] = self.to_megabytes(match) + match = re.search(r'total-hdd-space:\s(.*)([KMG]iB)', data, re.M) + if match: + self.facts['spacetotal_mb'] = self.to_megabytes(match) + + def parse_memory_info(self, data): + match = re.search(r'free-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M) + if match: + self.facts['memfree_mb'] = self.to_megabytes(match) + match = re.search(r'total-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M) + if match: + self.facts['memtotal_mb'] = self.to_megabytes(match) + + def to_megabytes(self, data): + if data.group(2) == 'KiB': + return float(data.group(1)) / 1024 + elif data.group(2) == 'MiB': + return float(data.group(1)) + elif data.group(2) == 'GiB': + return float(data.group(1)) * 1024 + else: + return None + + +class Config(FactsBase): + + COMMANDS = ['/export verbose'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + '/interface print detail without-paging', + '/ip address print detail without-paging', + '/ipv6 address print detail without-paging', + '/ip neighbor print detail without-paging' + ] + + DETAIL_RE = re.compile(r'([\w\d\-]+)=\"?(\w{3}/\d{2}/\d{4}\s\d{2}:\d{2}:\d{2}|[\w\d\-\.:/]+)') + WRAPPED_LINE_RE = re.compile(r'^\s+(?!\d)') + + def populate(self): + super(Interfaces, self).populate() + + self.facts['interfaces'] = dict() + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + self.facts['neighbors'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.populate_interfaces(interfaces) + + data = self.responses[1] + if data: + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv4') + + data = self.responses[2] + if data: + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv6') + + data = self.responses[3] + if data: + self.facts['neighbors'] = list(self.parse_detail(data)) + + def populate_interfaces(self, data): + for key, value in iteritems(data): + self.facts['interfaces'][key] = value + + def populate_addresses(self, data, family): + for value in data: + key = value['interface'] + if family not in self.facts['interfaces'][key]: + self.facts['interfaces'][key][family] = list() + addr, subnet = value['address'].split("/") + ip = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), family) + self.facts['interfaces'][key][family].append(ip) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def preprocess(self, data): + preprocessed = list() + for line in data.split('\n'): + if len(line) == 0 or line[:5] == 'Flags': + continue + elif not re.match(self.WRAPPED_LINE_RE, line): + preprocessed.append(line) + else: + preprocessed[-1] += line + return preprocessed + + def parse_interfaces(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "name" not in parsed: + continue + facts[parsed["name"]] = dict(re.findall(self.DETAIL_RE, line)) + return facts + + def parse_detail(self, data): + data = self.preprocess(data) + for line in data: + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "interface" not in parsed: + continue + yield parsed + + +class Routing(FactsBase): + + COMMANDS = [ + '/routing bgp peer print detail without-paging', + '/routing bgp vpnv4-route print detail without-paging', + '/routing bgp instance print detail without-paging', + '/ip route print detail without-paging', + '/routing ospf instance print detail without-paging', + '/routing ospf neighbor print detail without-paging' + ] + + DETAIL_RE = re.compile(r'([\w\d\-]+)=\"?(\w{3}/\d{2}/\d{4}\s\d{2}:\d{2}:\d{2}|[\w\d\-\.:/]+)') + WRAPPED_LINE_RE = re.compile(r'^\s+(?!\d)') + + def populate(self): + super(Routing, self).populate() + self.facts['bgp_peer'] = dict() + self.facts['bgp_vpnv4_route'] = dict() + self.facts['bgp_instance'] = dict() + self.facts['route'] = dict() + self.facts['ospf_instance'] = dict() + self.facts['ospf_neighbor'] = dict() + data = self.responses[0] + if data: + peer = self.parse_bgp_peer(data) + self.populate_bgp_peer(peer) + data = self.responses[1] + if data: + vpnv4 = self.parse_vpnv4_route(data) + self.populate_vpnv4_route(vpnv4) + data = self.responses[2] + if data: + instance = self.parse_instance(data) + self.populate_bgp_instance(instance) + data = self.responses[3] + if data: + route = self.parse_route(data) + self.populate_route(route) + data = self.responses[4] + if data: + instance = self.parse_instance(data) + self.populate_ospf_instance(instance) + data = self.responses[5] + if data: + instance = self.parse_ospf_neighbor(data) + self.populate_ospf_neighbor(instance) + + def preprocess(self, data): + preprocessed = list() + for line in data.split('\n'): + if len(line) == 0 or line[:5] == 'Flags': + continue + elif not re.match(self.WRAPPED_LINE_RE, line): + preprocessed.append(line) + else: + preprocessed[-1] += line + return preprocessed + + def parse_name(self, data): + match = re.search(r'name=.(\S+\b)', data, re.M) + if match: + return match.group(1) + + def parse_interface(self, data): + match = re.search(r'interface=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + + def parse_instance_name(self, data): + match = re.search(r'instance=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + + def parse_routing_mark(self, data): + match = re.search(r'routing-mark=([\w\d\-]+)', data, re.M) + if match: + return match.group(1) + else: + match = 'main' + return match + + def parse_bgp_peer(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_instance(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_vpnv4_route(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_interface(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_route(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_routing_mark(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_ospf_instance(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def parse_ospf_neighbor(self, data): + facts = dict() + data = self.preprocess(data) + for line in data: + name = self.parse_instance_name(line) + facts[name] = dict() + for (key, value) in re.findall(self.DETAIL_RE, line): + facts[name][key] = value + return facts + + def populate_bgp_peer(self, data): + for key, value in iteritems(data): + self.facts['bgp_peer'][key] = value + + def populate_vpnv4_route(self, data): + for key, value in iteritems(data): + self.facts['bgp_vpnv4_route'][key] = value + + def populate_bgp_instance(self, data): + for key, value in iteritems(data): + self.facts['bgp_instance'][key] = value + + def populate_route(self, data): + for key, value in iteritems(data): + self.facts['route'][key] = value + + def populate_ospf_instance(self, data): + for key, value in iteritems(data): + self.facts['ospf_instance'][key] = value + + def populate_ospf_neighbor(self, data): + for key, value in iteritems(data): + self.facts['ospf_neighbor'][key] = value + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + routing=Routing, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + +warnings = list() + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + argument_spec.update(routeros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset: %s' % subset) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_command.py new file mode 100644 index 00000000..97a37296 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_command.py @@ -0,0 +1,219 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: slxos_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme Networks SLX-OS +description: + - Sends arbitrary commands to an SLX node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.slxos_config) to configure SLX-OS devices. +notes: + - Tested against SLX-OS 17s.1.02 + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote SLX-OS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +tasks: + - name: Run show version on remote devices + community.network.slxos_command: + commands: show version + + - name: Run show version and check to see if output contains SLX + community.network.slxos_command: + commands: show version + wait_for: result[0] contains SLX + + - name: Run multiple commands on remote nodes + community.network.slxos_command: + commands: + - show version + - show interfaces + + - name: Run multiple commands and evaluate the output + community.network.slxos_command: + commands: + - show version + - show interface status + wait_for: + - result[0] contains SLX + - result[1] contains Eth + - name: Run command that requires answering a prompt + community.network.slxos_command: + commands: + - command: 'clear sessions' + prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' + answer: y +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +__metaclass__ = type + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='slxos_command does not support running config mode ' + 'commands. Please use slxos_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_config.py new file mode 100644 index 00000000..76351637 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_config.py @@ -0,0 +1,460 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# 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) +DOCUMENTATION = ''' +--- +module: slxos_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme Networks SLX-OS configuration sections +description: + - Extreme SLX-OS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with SLX-OS configuration sections in + a deterministic way. +notes: + - Tested against SLX-OS 17s.1.02 +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + multiline_delimiter: + description: + - This argument is used when pushing a multiline configuration + element to the SLX-OS device. It specifies the character to use + as the delimiting character. This only applies to the + configuration action. + default: "@" + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that before. If the argument is set to + I(always), then the running-config will always be copied to the + startup-config and the I(modified) flag will always be set to + True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since + the last save to startup-config. If the argument is set to + I(never), the running-config will never be copied to the + startup-config. If the argument is set to I(changed), then the running-config + will only be copied to the startup-config if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure top level configuration + community.network.slxos_config: + lines: hostname {{ inventory_hostname }} + +- name: Configure interface settings + community.network.slxos_config: + lines: + - description test interface + - ip address 172.31.1.1/24 + parents: interface Ethernet 0/1 + +- name: Configure multiple interfaces + community.network.slxos_config: + lines: + - lacp timeout long + parents: "{{ item }}" + with_items: + - interface Ethernet 0/1 + - interface Ethernet 0/2 + +- name: Load new acl into device + community.network.slxos_config: + lines: + - seq 10 permit ip host 1.1.1.1 any log + - seq 20 permit ip host 2.2.2.2 any log + - seq 30 permit ip host 3.3.3.3 any log + - seq 40 permit ip host 4.4.4.4 any log + - seq 50 permit ip host 5.5.5.5 any log + parents: ip access-list extended test + before: no ip access-list extended test + match: exact + +- name: Check the running-config against master config + community.network.slxos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.slxos_config: + diff_against: startup + diff_ignore_lines: + - ntp clock .* + +- name: Save running to startup when modified + community.network.slxos_config: + save_when: modified + +- name: Configurable backup path + community.network.slxos_config: + lines: hostname {{ inventory_hostname }} + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['switch-attributes hostname foo', 'router ospf', 'area 0'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/slxos_config.2018-02-12@18:26:34 +""" + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands, get_config, load_config +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + +__metaclass__ = type + + +def check_args(module, warnings): + if module.params['multiline_delimiter']: + if len(module.params['multiline_delimiter']) != 1: + module.fail_json(msg='multiline_delimiter value can only be a ' + 'single character') + + +def get_running_config(module, current_config=None): + contents = module.params['running_config'] + if not contents: + if current_config: + contents = current_config.config_text + else: + contents = get_config(module) + return NetworkConfig(indent=1, contents=contents) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + + if module.params['src']: + src = module.params['src'] + candidate.load(src) + + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + + return candidate + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + command = {"command": "copy running-config startup-config", + "prompt": "This operation will modify your startup configuration. Do you want to continue", "answer": "y"} + run_commands(module, command) + else: + module.warn('Skipping command `copy running-config startup-config` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + multiline_delimiter=dict(default='@'), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + result['warnings'] = warnings + + config = None + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + match = module.params['match'] + replace = module.params['replace'] + path = module.params['parents'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_running_config(module, config) + path = module.params['parents'] + configobjs = candidate.difference(config, path=path, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + load_config(module, commands) + + result['changed'] = True + + running_config = None + startup_config = None + + diff_ignore_lines = module.params['diff_ignore_lines'] + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + output = run_commands(module, ['show running-config', 'show startup-config']) + + running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + output = run_commands(module, 'show running-config') + contents = output[0] + else: + contents = running_config.config_text + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup_config: + output = run_commands(module, 'show startup-config') + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_facts.py new file mode 100644 index 00000000..d08584cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_facts.py @@ -0,0 +1,451 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from devices running Extreme SLX-OS +description: + - Collects a base set of device facts from a remote device that + is running SLX-OS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against SLX-OS 17s.1.02 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: ['!config'] +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.slxos_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.slxos_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.slxos_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All Primary IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS) + + def run(self, cmd): + return run_commands(self.module, cmd) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show inventory chassis', + r'show running-config | include host\-name' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + + data = self.responses[1] + if data: + self.facts['model'] = self.parse_model(data) + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[2] + if data: + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'SLX-OS Operating System Version: (\S+)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'SID:(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'SN:(\S+)', data, re.M) + if match: + return match.group(1) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show process memory summary' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) + self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) + + def parse_memtotal(self, data): + match = re.search(r'Total\s*Memory: (\d+)\s', data, re.M) + if match: + return match.group(1) + + def parse_memfree(self, data): + match = re.search(r'Total Free: (\d+)\s', data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interface', + 'show ipv6 interface brief', + r'show lldp nei detail | inc ^Local\ Interface|^Remote\ Interface|^System\ Name' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + + data = self.responses[1] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[2] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = list() + primary_address = addresses = [] + primary_address = re.findall(r'Primary Internet Address is (\S+)', value, re.M) + addresses = re.findall(r'Secondary Internet Address is (\S+)', value, re.M) + if not primary_address: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + # Only gets primary IPv6 addresses + def populate_ipv6_interfaces(self, data): + interfaces = re.split('=+', data)[1].strip() + matches = re.findall(r'(\S+ \S+) +[\w-]+.+\s+([\w:/]+/\d+)', interfaces, re.M) + for match in matches: + interface = match[0] + self.facts['interfaces'][interface]['ipv6'] = list() + address, masklen = match[1].split('/') + ipv6 = dict(address=address, masklen=int(masklen)) + self.add_ip_address(ipv6['address'], 'ipv6') + self.facts['interfaces'][interface]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Local Interface: ') + if not lines: + return facts + for line in lines: + match = re.search(r'(\w+ \S+)\s+\(Local Int.+?\)[\s\S]+Remote Interface: (\S+.+?) \(Remote Int.+?\)[\s\S]+System Name: (\S+)', line, re.M) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + for interface in data.split('\n\n'): + match = re.match(r'^(\S+ \S+)', interface, re.M) + if not match: + continue + else: + parsed[match.group(1)] = interface + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'Hardware is Ethernet, address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Primary Internet Address is ([^\s,]+)', data) + if match: + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+) bytes', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'LineSpeed Actual\s+:\s(.+)', data) + if match: + return match.group(1) + + def parse_duplex(self, data): + match = re.search(r'Duplex: (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\S+)', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.match(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=["!config"], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_interface.py new file mode 100644 index 00000000..f13e8a42 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_interface.py @@ -0,0 +1,464 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_interface +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Interfaces on Extreme SLX-OS network devices +description: + - This module provides declarative management of Interfaces + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + name: + description: + - Name of the Interface. + required: true + description: + description: + - Description of Interface. + enabled: + description: + - Interface link status. + default: True + type: bool + speed: + description: + - Interface link speed. + mtu: + description: + - Maximum size of transmit packet. + tx_rate: + description: + - Transmit rate in bits per second (bps). + rx_rate: + description: + - Receiver rate in bits per second (bps). + neighbors: + description: + - Check the operational state of given interface C(name) for LLDP neighbor. + - The following suboptions are available. + suboptions: + host: + description: + - "LLDP neighbor host for given interface C(name)." + port: + description: + - "LLDP neighbor port to which given interface C(name) is connected." + aggregate: + description: List of Interfaces definitions. + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state argument which are + I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). + default: 10 + state: + description: + - State of the Interface configuration, C(up) means present and + operationally up and C(down) means present and operationally C(down) + default: present + choices: ['present', 'absent', 'up', 'down'] +''' + +EXAMPLES = """ +- name: Configure interface + community.network.slxos_interface: + name: Ethernet 0/2 + description: test-interface + speed: 1000 + mtu: 9216 + +- name: Remove interface + community.network.slxos_interface: + name: Loopback 9 + state: absent + +- name: Make interface up + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: True + +- name: Make interface down + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: False + +- name: Check intent arguments + community.network.slxos_interface: + name: Ethernet 0/2 + state: up + tx_rate: ge(0) + rx_rate: le(0) + +- name: Check neighbors intent arguments + community.network.slxos_interface: + name: Ethernet 0/41 + neighbors: + - port: Ethernet 0/41 + host: SLX + +- name: Config + intent + community.network.slxos_interface: + name: Ethernet 0/2 + enabled: False + state: down + +- name: Add interface using aggregate + community.network.slxos_interface: + aggregate: + - { name: Ethernet 0/1, mtu: 1548, description: test-interface-1 } + - { name: Ethernet 0/2, mtu: 1548, description: test-interface-2 } + speed: 10000 + state: present + +- name: Delete interface using aggregate + community.network.slxos_interface: + aggregate: + - name: Loopback 9 + - name: Loopback 10 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/2 + - description test-interface + - mtu 1548 +""" +import re + +from copy import deepcopy +from time import sleep + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import exec_command +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec + + +def validate_mtu(value, module): + if value and not 1548 <= int(value) <= 9216: + module.fail_json(msg='mtu must be between 1548 and 9216') + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_shutdown(configobj, name): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'^shutdown', cfg, re.M) + if match: + return True + else: + return False + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1) + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+ \S+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + obj = { + 'name': item, + 'description': parse_config_argument(configobj, item, 'description'), + 'speed': parse_config_argument(configobj, item, 'speed'), + 'mtu': parse_config_argument(configobj, item, 'mtu'), + 'disable': True if parse_shutdown(configobj, item) else False, + 'state': 'present' + } + instances.append(obj) + return instances + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + d = item.copy() + + if d['enabled']: + d['disable'] = False + else: + d['disable'] = True + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'description': module.params['description'], + 'speed': module.params['speed'], + 'mtu': module.params['mtu'], + 'state': module.params['state'], + 'delay': module.params['delay'], + 'tx_rate': module.params['tx_rate'], + 'rx_rate': module.params['rx_rate'], + 'neighbors': module.params['neighbors'] + } + + validate_param_values(module, params) + if module.params['enabled']: + params.update({'disable': False}) + else: + params.update({'disable': True}) + + obj.append(params) + return obj + + +def map_obj_to_commands(updates): + commands = list() + want, have = updates + args = ('speed', 'description', 'mtu') + for w in want: + name = w['name'] + disable = w['disable'] + state = w['state'] + + obj_in_have = search_obj_in_list(name, have) + interface = 'interface ' + name + + if state == 'absent' and obj_in_have: + commands.append('no ' + interface) + + elif state in ('present', 'up', 'down'): + if obj_in_have: + for item in args: + candidate = w.get(item) + running = obj_in_have.get(item) + if candidate != running: + if candidate: + cmd = item + ' ' + str(candidate) + add_command_to_interface(interface, cmd, commands) + + if disable and not obj_in_have.get('disable', False): + add_command_to_interface(interface, 'shutdown', commands) + elif not disable and obj_in_have.get('disable', False): + add_command_to_interface(interface, 'no shutdown', commands) + else: + commands.append(interface) + for item in args: + value = w.get(item) + if value: + commands.append(item + ' ' + str(value)) + + if disable: + commands.append('no shutdown') + return commands + + +def check_declarative_intent_params(module, want, result): + failed_conditions = [] + have_neighbors = None + for w in want: + want_state = w.get('state') + want_tx_rate = w.get('tx_rate') + want_rx_rate = w.get('rx_rate') + want_neighbors = w.get('neighbors') + + if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: + continue + + if result['changed']: + sleep(w['delay']) + + command = 'show interface %s' % w['name'] + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) + + if want_state in ('up', 'down'): + match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) + have_state = None + if match: + have_state = match.group(1) + if have_state is None or not conditional(want_state, have_state.strip()): + failed_conditions.append('state ' + 'eq(%s)' % want_state) + + if want_tx_rate: + match = re.search(r'%s (\d+)' % 'Output', out, re.M) + have_tx_rate = None + if match: + have_tx_rate = match.group(1) + + if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): + failed_conditions.append('tx_rate ' + want_tx_rate) + + if want_rx_rate: + match = re.search(r'%s (\d+)' % 'Input', out, re.M) + have_rx_rate = None + if match: + have_rx_rate = match.group(1) + + if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): + failed_conditions.append('rx_rate ' + want_rx_rate) + + if want_neighbors: + have_host = [] + have_port = [] + if have_neighbors is None: + rc, have_neighbors, 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 Interface: ') + short_name = w['name'].replace('Ethernet', 'Eth') + for line in lines: + field = line.split('\n') + if field[0].split('(')[0].strip() == short_name: + for item in field: + if item.startswith('System Name:'): + have_host.append(item.split(':')[1].strip()) + if item.startswith('Remote Interface:'): + have_port.append(item.split(':')[1].split('(')[0].strip()) + for item in want_neighbors: + host = item.get('host') + port = item.get('port') + if host and host not in have_host: + failed_conditions.append('host ' + host) + if port and port not in have_port: + failed_conditions.append('port ' + port) + return failed_conditions + + +def main(): + """ main entry point for module execution + """ + neighbors_spec = dict( + host=dict(), + port=dict() + ) + + element_spec = dict( + name=dict(), + description=dict(), + speed=dict(), + mtu=dict(), + enabled=dict(default=True, type='bool'), + tx_rate=dict(), + rx_rate=dict(), + neighbors=dict(type='list', elements='dict', options=neighbors_spec), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent', 'up', 'down']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + failed_conditions = check_declarative_intent_params(module, want, result) + + if failed_conditions: + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions, changed=result['changed']) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l2_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l2_interface.py new file mode 100644 index 00000000..ccd0e0f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l2_interface.py @@ -0,0 +1,501 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_l2_interface +short_description: Manage Layer-2 interface on Extreme Networks SLX-OS devices. +description: + - This module provides declarative management of Layer-2 interface on + Extreme slxos devices. +author: + - Matthew Stone (@bigmstone) +options: + name: + description: + - Full name of the interface excluding any logical + unit number, i.e. Ethernet 0/1. + required: true + aliases: ['interface'] + mode: + description: + - Mode in which interface needs to be configured. + default: access + choices: ['access', 'trunk'] + access_vlan: + description: + - Configure given VLAN in access port. + If C(mode=access), used as the access VLAN ID. + trunk_vlans: + description: + - List of VLANs to be configured in trunk port. + If C(mode=trunk), used as the VLAN range to ADD or REMOVE + from the trunk. + native_vlan: + description: + - Native VLAN to be configured in trunk port. + If C(mode=trunk), used as the trunk native VLAN ID. + trunk_allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. + If C(mode=trunk), these are the only VLANs that will be + configured on the trunk, i.e. "2-10,15". + aggregate: + description: + - List of Layer-2 interface definitions. + state: + description: + - Manage the state of the Layer-2 Interface configuration. + default: present + choices: ['present','absent', 'unconfigured'] +''' + +EXAMPLES = """ +- name: Ensure Ethernet 0/5 is in its default l2 interface state + community.network.slxos_l2_interface: + name: Ethernet 0/5 + state: unconfigured + +- name: Ensure Ethernet 0/5 is configured for access vlan 20 + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: access + access_vlan: 20 + +- name: Ensure Ethernet 0/5 only has vlans 5-10 as trunk vlans + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 5-10 + +- name: Ensure Ethernet 0/5 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + native_vlan: 10 + trunk_vlans: 2-50 + +- name: Ensure these VLANs are not being tagged on the trunk + community.network.slxos_l2_interface: + name: Ethernet 0/5 + mode: trunk + trunk_vlans: 51-4094 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/5 + - switchport access vlan 20 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config, run_commands + + +def get_interface_type(interface): + intf_type = 'unknown' + if interface.upper()[:2] in ('ET', 'GI'): + intf_type = 'ethernet' + elif interface.upper().startswith('VL'): + intf_type = 'svi' + elif interface.upper().startswith('LO'): + intf_type = 'loopback' + elif interface.upper()[:2] in ('MG', 'MA'): + intf_type = 'management' + elif interface.upper().startswith('PO'): + intf_type = 'portchannel' + elif interface.upper().startswith('NV'): + intf_type = 'nve' + + return intf_type + + +def is_switchport(name, module): + intf_type = get_interface_type(name) + + if intf_type in ('ethernet', 'portchannel'): + config = run_commands(module, ['show interface {0} switchport'.format(name)])[0] + match = re.search(r'Interface name\s+:\s', config) + return bool(match) + return False + + +def interface_is_portchannel(name, module): + if get_interface_type(name) == 'ethernet': + config = get_config(module) + if 'channel group' in config: + return True + + return False + + +def get_switchport(name, module): + config = run_commands(module, ['show interface {0} switchport'.format(name)])[0] + mode = re.search(r'Switchport mode\s+:\s(?:.* )?(\w+)$', config, re.M) + if mode: + mode = mode.group(1) + access = re.search(r'Default Vlan\s+:\s(\d+)', config) + if access: + access = access.group(1) + native = re.search(r'Native Vlan\s+:\s(\d+)', config) + if native: + native = native.group(1) + trunk = re.search(r'Active Vlans\s+:\s(.+)$', config, re.M) + if trunk: + trunk = trunk.group(1) + if trunk == 'ALL': + trunk = '1-4094' + + switchport_config = { + "interface": name, + "mode": mode, + "access_vlan": access, + "native_vlan": native, + "trunk_vlans": trunk, + } + + return switchport_config + + +def remove_switchport_config_commands(name, existing, proposed, module): + mode = proposed.get('mode') + commands = [] + command = None + + if mode == 'access': + av_check = existing.get('access_vlan') == proposed.get('access_vlan') + if av_check: + command = 'no switchport access vlan {0}'.format(existing.get('access_vlan')) + 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) + + native_check = existing.get('native_vlan') == proposed.get('native_vlan') + if native_check and proposed.get('native_vlan'): + command = 'no switchport trunk native vlan {0}'.format(existing.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def get_switchport_config_commands(name, existing, proposed, module): + """Gets commands required to config a given switchport interface + """ + + proposed_mode = proposed.get('mode') + existing_mode = existing.get('mode') + commands = [] + command = None + + if proposed_mode != existing_mode: + if proposed_mode == 'trunk': + command = 'switchport mode trunk' + elif proposed_mode == 'access': + command = 'switchport mode access' + + if command: + commands.append(command) + + if proposed_mode == 'access': + av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan')) + if not av_check: + command = 'switchport access vlan {0}'.format(proposed.get('access_vlan')) + commands.append(command) + + elif proposed_mode == 'trunk': + tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list') + + if not tv_check: + if proposed.get('allowed'): + command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_allowed_vlans')) + commands.append(command) + + else: + existing_vlans = existing.get('trunk_vlans_list') + proposed_vlans = proposed.get('trunk_vlans_list') + vlans_to_add = set(proposed_vlans).difference(existing_vlans) + if vlans_to_add: + command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_vlans')) + commands.append(command) + + native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan')) + if not native_check and proposed.get('native_vlan'): + command = 'switchport trunk native vlan {0}'.format(proposed.get('native_vlan')) + commands.append(command) + + if commands: + commands.insert(0, 'interface ' + name) + return commands + + +def is_switchport_default(existing): + """Determines if switchport has a default config based on mode + Args: + existing (dict): existing switchport configuration from Ansible mod + Returns: + boolean: True if switchport has OOB Layer 2 config, i.e. + vlan 1 and trunk all and mode is access + """ + + c1 = str(existing['access_vlan']) == '1' + c2 = str(existing['native_vlan']) == '1' + c3 = existing['trunk_vlans'] == '1-4094' + c4 = existing['mode'] == 'access' + + default = c1 and c2 and c3 and c4 + + return default + + +def default_switchport_config(name): + commands = [] + commands.append('interface ' + name) + commands.append('switchport mode access') + commands.append('switch access vlan 1') + commands.append('switchport trunk native vlan 1') + commands.append('switchport trunk allowed vlan all') + return commands + + +def vlan_range_to_list(vlans): + result = [] + if vlans: + for part in vlans.split(','): + if part == 'none': + break + if '-' in part: + start, stop = (int(i) for i in part.split('-')) + result.extend(range(start, stop + 1)) + else: + result.append(int(part)) + return sorted(result) + + +def get_list_of_vlans(module): + config = run_commands(module, ['show vlan brief'])[0] + vlans = set() + + lines = config.strip().splitlines() + for line in lines: + line_parts = line.split() + if line_parts: + try: + int(line_parts[0]) + except ValueError: + continue + vlans.add(line_parts[0]) + + return list(vlans) + + +def flatten_list(commands): + flat_list = [] + for command in commands: + if isinstance(command, list): + flat_list.extend(command) + else: + flat_list.append(command) + return flat_list + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'mode': module.params['mode'], + 'access_vlan': module.params['access_vlan'], + 'native_vlan': module.params['native_vlan'], + 'trunk_vlans': module.params['trunk_vlans'], + 'trunk_allowed_vlans': module.params['trunk_allowed_vlans'], + 'state': module.params['state'] + }) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(type='str', aliases=['interface']), + mode=dict(choices=['access', 'trunk'], default='access'), + access_vlan=dict(type='str'), + native_vlan=dict(type='str'), + trunk_vlans=dict(type='str'), + trunk_allowed_vlans=dict(type='str'), + state=dict(choices=['absent', 'present', 'unconfigured'], default='present') + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=[['access_vlan', 'trunk_vlans'], + ['access_vlan', 'native_vlan'], + ['access_vlan', 'trunk_allowed_vlans']], + supports_check_mode=True) + + warnings = list() + commands = [] + result = {'changed': False, 'warnings': warnings} + + want = map_params_to_obj(module) + for w in want: + name = w['name'] + mode = w['mode'] + access_vlan = w['access_vlan'] + state = w['state'] + trunk_vlans = w['trunk_vlans'] + native_vlan = w['native_vlan'] + trunk_allowed_vlans = w['trunk_allowed_vlans'] + + args = dict(name=name, mode=mode, access_vlan=access_vlan, + native_vlan=native_vlan, trunk_vlans=trunk_vlans, + trunk_allowed_vlans=trunk_allowed_vlans) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + name = name.lower() + + if mode == 'access' and state == 'present' and not access_vlan: + module.fail_json(msg='access_vlan param is required when mode=access && state=present') + + if mode == 'trunk' and access_vlan: + module.fail_json(msg='access_vlan param not supported when using mode=trunk') + + if not is_switchport(name, module): + module.fail_json(msg='Ensure interface is configured to be a L2' + '\nport first before using this module. You can use' + '\nthe slxos_interface module for this.') + + if interface_is_portchannel(name, module): + module.fail_json(msg='Cannot change L2 config on physical ' + '\nport because it is in a portchannel. ' + '\nYou should update the portchannel config.') + + # existing will never be null for Eth intfs as there is always a default + existing = get_switchport(name, module) + + # Safeguard check + # If there isn't an existing, something is wrong per previous comment + if not existing: + module.fail_json(msg='Make sure you are using the FULL interface name') + + if trunk_vlans or trunk_allowed_vlans: + if trunk_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_vlans) + elif trunk_allowed_vlans: + trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans) + proposed['allowed'] = True + + existing_trunks_list = vlan_range_to_list((existing['trunk_vlans'])) + + existing['trunk_vlans_list'] = existing_trunks_list + proposed['trunk_vlans_list'] = trunk_vlans_list + + current_vlans = get_list_of_vlans(module) + + if state == 'present': + if access_vlan and access_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=access_vlan) + elif native_vlan and native_vlan not in current_vlans: + module.fail_json(msg='You are trying to configure a VLAN' + ' on an interface that\ndoes not exist on the ' + ' switch yet!', vlan=native_vlan) + else: + command = get_switchport_config_commands(name, existing, proposed, module) + commands.append(command) + elif state == 'unconfigured': + is_default = is_switchport_default(existing) + if not is_default: + command = default_switchport_config(name) + commands.append(command) + elif state == 'absent': + command = remove_switchport_config_commands(name, existing, proposed, module) + commands.append(command) + + if trunk_vlans or trunk_allowed_vlans: + existing.pop('trunk_vlans_list') + proposed.pop('trunk_vlans_list') + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + result['changed'] = True + load_config(module, cmds) + if 'configure' in cmds: + cmds.pop(0) + + result['commands'] = cmds + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l3_interface.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l3_interface.py new file mode 100644 index 00000000..240f1024 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_l3_interface.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_l3_interface +author: "Matthew Stone (@bigmstone)" +short_description: Manage L3 interfaces on Extreme Networks SLX-OS network devices. +description: + - This module provides declarative management of L3 interfaces + on slxos network devices. +notes: + - Tested against slxos 15.2 +options: + name: + description: + - Name of the L3 interface to be configured eg. Ethernet 0/2 + ipv4: + description: + - IPv4 address to be set for the L3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-32 eg. 192.168.0.1/24 + ipv6: + description: + - IPv6 address to be set for the L3 interface mentioned in I(name) option. + The address format is /, the mask is number + in range 0-128 eg. fd5d:12c9:2201:1::1/64 + aggregate: + description: + - List of L3 interfaces definitions. Each of the entry in aggregate list should + define name of interface C(name) and a optional C(ipv4) or C(ipv6) address. + state: + description: + - State of the L3 interface configuration. It indicates if the configuration should + be present or absent on remote device. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Remove Ethernet 0/3 IPv4 and IPv6 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + state: absent + +- name: Set Ethernet 0/3 IPv4 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + ipv4: 192.168.0.1/24 + +- name: Set Ethernet 0/3 IPv6 address + community.network.slxos_l3_interface: + name: Ethernet 0/3 + ipv6: "fd5d:12c9:2201:1::1/64" + +- name: Set IP addresses on aggregate + community.network.slxos_l3_interface: + aggregate: + - { name: Ethernet 0/3, ipv4: 192.168.2.10/24 } + - { name: Ethernet 0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + +- name: Remove IP addresses on aggregate + community.network.slxos_l3_interface: + aggregate: + - { name: Ethernet 0/3, ipv4: 192.168.2.10/24 } + - { name: Ethernet 0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface Ethernet 0/2 + - ip address 192.168.0.1/24 + - ipv6 address fd5d:12c9:2201:1::1/64 +""" +import re + +from copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1]) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format %s' % value) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1]) + + +def validate_param_values(module, obj, param=None): + if param is None: + param = module.params + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(param.get(key), module) + + +def parse_config_argument(configobj, name, arg=None): + cfg = configobj['interface %s' % name] + cfg = '\n'.join(cfg.children) + match = re.search(r'%s (.+)$' % arg, cfg, re.M) + if match: + return match.group(1).strip() + + return None + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + return None + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + for w in want: + name = w['name'] + ipv4 = w['ipv4'] + ipv6 = w['ipv6'] + state = w['state'] + + interface = 'interface ' + name + commands.append(interface) + + obj_in_have = search_obj_in_list(name, have) + if state == 'absent' and obj_in_have: + if obj_in_have['ipv4']: + if ipv4: + commands.append('no ip address %s' % ipv4) + else: + commands.append('no ip address') + if obj_in_have['ipv6']: + if ipv6: + commands.append('no ipv6 address %s' % ipv6) + else: + commands.append('no ipv6 address') + + elif state == 'present': + if ipv4: + if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']: + commands.append('ip address %s' % ipv4) + + if ipv6: + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() != obj_in_have['ipv6'].lower(): + commands.append('ipv6 address %s' % ipv6) + + if commands[-1] == interface: + commands.pop(-1) + + return commands + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=1, contents=config) + + match = re.findall(r'^interface (\S+\s[0-9]+/[0-9]+)', config, re.M) + if not match: + return list() + + instances = list() + + for item in set(match): + ipv4 = parse_config_argument(configobj, item, 'ip address') + if ipv4: + # eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24 + address = ipv4.strip().split(' ') + if len(address) == 2 and is_netmask(address[1]): + ipv4 = '%s/%s' % (address[0], to_text(to_masklen(address[1]))) + + obj = { + 'name': item, + 'ipv4': ipv4, + 'ipv6': parse_config_argument(configobj, item, 'ipv6 address'), + 'state': 'present' + } + instances.append(obj) + + return instances + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + validate_param_values(module, item, item) + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'ipv4': module.params['ipv4'], + 'ipv6': module.params['ipv6'], + 'state': module.params['state'] + }) + + validate_param_values(module, obj) + + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + ipv4=dict(), + ipv6=dict(), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['name'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_linkagg.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_linkagg.py new file mode 100644 index 00000000..69b5db2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_linkagg.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: slxos_linkagg +author: "Matthew Stone (@bigmstone)" +short_description: Manage link aggregation groups on Extreme Networks SLX-OS network devices +description: + - This module provides declarative management of link aggregation groups + on Extreme Networks SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + group: + description: + - Channel-group number for the port-channel + Link aggregation group. Range 1-1024. + mode: + description: + - Mode of the link aggregation group. + choices: ['active', 'on', 'passive'] + members: + description: + - List of members of the link aggregation group. + aggregate: + description: List of link aggregation definitions. + state: + description: + - State of the link aggregation group. + default: present + choices: ['present', 'absent'] + purge: + description: + - Purge links not defined in the I(aggregate) parameter. + type: bool + default: false +''' + +EXAMPLES = """ +- name: Create link aggregation group + community.network.slxos_linkagg: + group: 10 + state: present + +- name: Delete link aggregation group + community.network.slxos_linkagg: + group: 10 + state: absent + +- name: Set link aggregation group to members + community.network.slxos_linkagg: + group: 200 + mode: active + members: + - Ethernet 0/1 + - Ethernet 0/2 + +- name: Remove link aggregation group from Ethernet 0/1 + community.network.slxos_linkagg: + group: 200 + mode: active + members: + - Ethernet 0/1 + +- name: Create aggregate of linkagg definitions + community.network.slxos_linkagg: + aggregate: + - { group: 3, mode: on, members: [Ethernet 0/1] } + - { group: 100, mode: passive, members: [Ethernet 0/2] } +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - interface port-channel 30 + - interface Ethernet 0/3 + - channel-group 30 mode on + - no interface port-channel 30 +""" + +import re +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import get_config, load_config + + +def search_obj_in_list(group, lst): + for o in lst: + if o['group'] == group: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + group = w['group'] + mode = w['mode'] + members = w.get('members') or [] + state = w['state'] + del w['state'] + + obj_in_have = search_obj_in_list(group, have) + + if state == 'absent': + if obj_in_have: + commands.append('no interface port-channel {0}'.format(group)) + + elif state == 'present': + cmd = ['interface port-channel {0}'.format(group), + 'exit'] + if not obj_in_have: + if not group: + module.fail_json(msg='group is a required option') + commands.extend(cmd) + + if members: + for m in members: + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + else: + if members: + if 'members' not in obj_in_have.keys(): + for m in members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + elif set(members) != set(obj_in_have['members']): + missing_members = list(set(members) - set(obj_in_have['members'])) + for m in missing_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('channel-group {0} mode {1}'.format(group, mode)) + + superfluous_members = list(set(obj_in_have['members']) - set(members)) + for m in superfluous_members: + commands.extend(cmd) + commands.append('interface {0}'.format(m)) + commands.append('no channel-group') + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['group'], want) + if not obj_in_want: + commands.append('no interface port-channel {0}'.format(h['group'])) + + return commands + + +def map_params_to_obj(module): + obj = [] + + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['group'] = str(d['group']) + + obj.append(d) + else: + obj.append({ + 'group': str(module.params['group']), + 'mode': module.params['mode'], + 'members': module.params['members'], + 'state': module.params['state'] + }) + + return obj + + +def parse_mode(module, config, group, member): + mode = None + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = ['interface {0}'.format(member)] + body = netcfg.get_section(parents) + + match_int = re.findall(r'interface {0}\n'.format(member), body, re.M) + if match_int: + match = re.search(r'channel-group {0} mode (\S+)'.format(group), body, re.M) + if match: + mode = match.group(1) + + return mode + + +def parse_members(module, config, group): + members = [] + + for line in config.strip().split('!'): + l = line.strip() + if l.startswith('interface'): + match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M) + if match_group: + match = re.search(r'^interface (\S+\s\S+)$', l, re.M) + if match: + members.append(match.group(1)) + + return members + + +def get_channel(module, config, group): + match = re.findall(r'^interface (\S+\s\S+)$', config, re.M) + + if not match: + return {} + + channel = {} + for item in set(match): + member = item + channel['mode'] = parse_mode(module, config, group, member) + channel['members'] = parse_members(module, config, group) + + return channel + + +def map_config_to_obj(module): + objs = list() + config = get_config(module) + + for line in config.split('\n'): + l = line.strip() + match = re.search(r'interface Port-channel (\S+)', l, re.M) + if match: + obj = {} + group = match.group(1) + obj['group'] = group + obj.update(get_channel(module, config, group)) + objs.append(obj) + + return objs + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + group=dict(type='int'), + mode=dict(choices=['active', 'on', 'passive']), + members=dict(type='list'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['group'] = dict(required=True) + + required_one_of = [['group', 'aggregate']] + required_together = [['members', 'mode']] + mutually_exclusive = [['group', 'aggregate']] + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec, + required_together=required_together), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + required_together=required_together, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_lldp.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_lldp.py new file mode 100644 index 00000000..3606902e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_lldp.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_lldp +author: "Matthew Stone (@bigmstone)" +short_description: Manage LLDP configuration on Extreme Networks SLX-OS network devices. +description: + - This module provides declarative management of LLDP service + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 17s.1.02 +options: + state: + description: + - State of the LLDP configuration. If value is I(present) lldp will be enabled + else if it is I(absent) it will be disabled. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Enable LLDP service + community.network.slxos_lldp: + state: present + +- name: Disable LLDP service + community.network.slxos_lldp: + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always, except for the platforms that use Netconf transport to manage the device. + type: list + sample: + - lldp run +""" +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import ( + load_config, + get_config +) + +PROTOCOL = "protocol lldp" + + +def has_lldp(module): + config = get_config(module) + netcfg = CustomNetworkConfig(indent=1, contents=config) + parents = [PROTOCOL] + body = netcfg.get_section(parents) + + for line in body.split('\n'): + l = line.strip() + match = re.search(r'disable', l, re.M) + if match: + return False + + return True + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + state=dict(default='present', + choices=['present', 'absent']) + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + HAS_LLDP = has_lldp(module) + + warnings = list() + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + commands = [] + + if module.params['state'] == 'absent' and HAS_LLDP: + commands.append('protocol lldp') + commands.append('disable') + elif module.params['state'] == 'present' and not HAS_LLDP: + commands.append('protocol lldp') + commands.append('no disable') + + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_vlan.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_vlan.py new file mode 100644 index 00000000..0ecf8a8d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/slxos_vlan.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: slxos_vlan +author: "Lindsay Hill (@lindsayhill)" +short_description: Manage VLANs on Extreme Networks SLX-OS network devices +description: + - This module provides declarative management of VLANs + on Extreme SLX-OS network devices. +notes: + - Tested against SLX-OS 18r.1.00 +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + interfaces: + description: + - List of interfaces that should be associated to the VLAN. + required: true + delay: + description: + - Delay the play should wait to check for declarative intent params values. + default: 10 + aggregate: + description: List of VLANs definitions. + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + type: bool + default: no + state: + description: + - State of the VLAN configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +- name: Create vlan + community.network.slxos_vlan: + vlan_id: 100 + name: test-vlan + state: present +- name: Add interfaces to VLAN + community.network.slxos_vlan: + vlan_id: 100 + interfaces: + - Ethernet 0/1 + - Ethernet 0/2 +- name: Delete vlan + community.network.slxos_vlan: + vlan_id: 100 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec +from ansible_collections.community.network.plugins.module_utils.network.slxos.slxos import load_config, run_commands + + +def search_obj_in_list(vlan_id, lst): + for o in lst: + if o['vlan_id'] == vlan_id: + return o + return None + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + name = w['name'] + interfaces = w['interfaces'] + state = w['state'] + + obj_in_have = search_obj_in_list(vlan_id, have) + + if state == 'absent': + if obj_in_have: + commands.append('no vlan %s' % vlan_id) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan %s' % vlan_id) + if name: + commands.append('name %s' % name) + + if interfaces: + for i in interfaces: + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + else: + if name: + if name != obj_in_have['name']: + commands.append('vlan %s' % vlan_id) + commands.append('name %s' % name) + + if interfaces: + if not obj_in_have['interfaces']: + for i in interfaces: + commands.append('vlan %s ' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + elif set(interfaces) != set(obj_in_have['interfaces']): + missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces'])) + for i in missing_interfaces: + commands.append('vlan %s' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport') + commands.append('switchport mode access') + commands.append('switchport access vlan %s' % vlan_id) + + superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) + for i in superfluous_interfaces: + commands.append('vlan %s' % vlan_id) + commands.append('interface %s' % i) + commands.append('switchport mode access') + commands.append('no switchport access vlan %s' % vlan_id) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['vlan_id'], want) + if not obj_in_want and h['vlan_id'] != '1': + commands.append('no vlan %s' % h['vlan_id']) + + return commands + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d['vlan_id'] = str(d['vlan_id']) + + obj.append(d) + else: + obj.append({ + 'vlan_id': str(module.params['vlan_id']), + 'name': module.params['name'], + 'interfaces': module.params['interfaces'], + 'state': module.params['state'] + }) + + return obj + + +def map_config_to_obj(module): + output = run_commands(module, ['show vlan brief']) + lines = output[0].strip().splitlines()[5:] + + if not lines: + return list() + + objs = list() + obj = {} + + for l in lines: + splitted_line = re.split(r'([0-9]+)? +(\S.{14})? +(ACTIVE|INACTIVE\(.+?\))?.*(Eth .+?|Po .+?|Tu .+?)\([ut]\).*$', l.rstrip()) + if len(splitted_line) == 1: + # Handle situation where VLAN is configured, but has no associated ports + inactive = re.match(r'([0-9]+)? +(\S.{14}) +INACTIVE\(no member port\).*$', l.rstrip()) + if inactive: + splitted_line = ['', inactive.groups()[0], inactive.groups()[1], '', ''] + else: + continue + + splitted_line[4] = splitted_line[4].replace('Eth', 'Ethernet').replace('Po', 'Port-channel').replace('Tu', 'Tunnel') + + if splitted_line[1] is None: + obj['interfaces'].append(splitted_line[4]) + continue + + obj = {} + obj['vlan_id'] = splitted_line[1] + obj['name'] = splitted_line[2].strip() + obj['interfaces'] = [splitted_line[4]] + + objs.append(obj) + + return objs + + +def check_declarative_intent_params(want, module): + if module.params['interfaces']: + time.sleep(module.params['delay']) + have = map_config_to_obj(module) + + for w in want: + for i in w['interfaces']: + obj_in_have = search_obj_in_list(w['vlan_id'], have) + if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']: + module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + state=dict(default='present', + choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {'changed': False} + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + if result['changed']: + check_declarative_intent_params(want, module) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_command.py new file mode 100644 index 00000000..5ff8b0c0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_command.py @@ -0,0 +1,228 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_command +author: "Peter Sprygada (@privateip)" +short_description: Run commands on remote devices running Nokia SR OS +description: + - Sends arbitrary commands to an SR OS node and returns the results + read from the device. This module includes an argument that will + cause the module to wait for a specific condition before returning + or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.sros_config) to configure SR OS devices. +extends_documentation_fragment: +- community.network.sros + +options: + commands: + description: + - List of commands to send to the remote SR OS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + aliases: ['waitfor'] + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +tasks: + - name: Run show version on remote devices + community.network.sros_command: + commands: show version + provider: "{{ cli }}" + + - name: Run show version and check to see if output contains sros + community.network.sros_command: + commands: show version + wait_for: result[0] contains sros + provider: "{{ cli }}" + + - name: Run multiple commands on remote nodes + community.network.sros_command: + commands: + - show version + - show port detail + provider: "{{ cli }}" + + - name: Run multiple commands and evaluate the output + community.network.sros_command: + commands: + - show version + - show port detail + wait_for: + - result[0] contains TiMOS-B-14.0.R4 + provider: "{{ cli }}" +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible.module_utils.six import string_types +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import run_commands, sros_argument_spec, check_args + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for index, item in enumerate(commands): + if module.check_mode and not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + elif item['command'].startswith('conf'): + module.fail_json( + msg='sros_command does not support running config mode ' + 'commands. Please use sros_config instead' + ) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list', aliases=['waitfor']), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(sros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result = { + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + } + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_config.py new file mode 100644 index 00000000..1701516c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_config.py @@ -0,0 +1,329 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_config +author: "Peter Sprygada (@privateip)" +short_description: Manage Nokia SR OS device configuration +description: + - Nokia SR OS configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with SR OS configuration sections in + a deterministic way. +extends_documentation_fragment: +- community.network.sros + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. The I(lines) argument only supports current + context lines. See EXAMPLES + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. + If match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + force: + description: + - The force argument instructs the module to not consider the + current devices running-config. When set to true, this will + cause the module to push the contents of I(src) into the device + without first checking if already configured. + - Note this argument should be considered deprecated. To achieve + the equivalent, set the C(match=none) which is idempotent. This argument + will be removed in a future release. + type: bool + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + config: + description: + - The C(config) argument allows the playbook designer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(admin display-config detail). + type: bool + default: 'no' + aliases: ['detail'] + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +- name: Enable rollback location + community.network.sros_config: + lines: configure system rollback rollback-location "cf3:/ansible" + provider: "{{ cli }}" + +- name: Set system name to {{ inventory_hostname }} using one line + community.network.sros_config: + lines: + - configure system name "{{ inventory_hostname }}" + provider: "{{ cli }}" + +- name: Set system name to {{ inventory_hostname }} using parents + community.network.sros_config: + lines: + - 'name "{{ inventory_hostname }}"' + parents: + - configure + - system + provider: "{{ cli }}" + backup: yes + +- name: Load config from file + community.network.sros_config: + src: "{{ inventory_hostname }}.cfg" + provider: "{{ cli }}" + save: yes + +- name: Invalid use of lines + community.network.sros_config: + lines: + - service + - vpls 1000 customer foo 1 create + - description "invalid lines example" + provider: "{{ cli }}" + +- name: Valid use of lines + community.network.sros_config: + lines: + - description "invalid lines example" + parents: + - service + - vpls 1000 customer foo 1 create + provider: "{{ cli }}" + +- name: Configurable backup path + community.network.sros_config: + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['config system name "sros01"'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['config system name "sros01"'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/sros_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import sros_argument_spec, check_args +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import load_config, run_commands, get_config + + +def get_active_config(module): + contents = module.params['config'] + if not contents: + flags = [] + if module.params['defaults']: + flags = ['detail'] + return get_config(module, flags) + return contents + + +def get_candidate(module): + candidate = NetworkConfig(indent=4) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def run(module, result): + match = module.params['match'] + + candidate = get_candidate(module) + + if match != 'none': + config_text = get_active_config(module) + config = NetworkConfig(indent=4, contents=config_text) + configobjs = candidate.difference(config) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands') + commands = commands.split('\n') + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + match=dict(default='line', choices=['line', 'none']), + + config=dict(), + defaults=dict(type='bool', default=False, aliases=['detail']), + + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + save=dict(type='bool', default=False), + ) + + argument_spec.update(sros_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + result = dict(changed=False, warnings=list()) + + warnings = list() + check_args(module, warnings) + if warnings: + result['warnings'] = warnings + + if module.params['backup']: + result['__backup__'] = get_config(module) + + run(module, result) + + if module.params['save']: + if not module.check_mode: + run_commands(module, ['admin save']) + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_rollback.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_rollback.py new file mode 100644 index 00000000..6980466c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/sros_rollback.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: sros_rollback +author: "Peter Sprygada (@privateip)" +short_description: Configure Nokia SR OS rollback +description: + - Configure the rollback feature on remote Nokia devices running + the SR OS operating system. this module provides a stateful + implementation for managing the configuration of the rollback + feature +extends_documentation_fragment: +- community.network.sros + +options: + rollback_location: + description: + - The I(rollback_location) specifies the location and filename + of the rollback checkpoint files. This argument supports any + valid local or remote URL as specified in SR OS + remote_max_checkpoints: + description: + - The I(remote_max_checkpoints) argument configures the maximum + number of rollback files that can be transferred and saved to + a remote location. Valid values for this argument are in the + range of 1 to 50 + local_max_checkpoints: + description: + - The I(local_max_checkpoints) argument configures the maximum + number of rollback files that can be saved on the devices local + compact flash. Valid values for this argument are in the range + of 1 to 50 + rescue_location: + description: + - The I(rescue_location) specifies the location of the + rescue file. This argument supports any valid local + or remote URL as specified in SR OS + state: + description: + - The I(state) argument specifies the state of the configuration + entries in the devices active configuration. When the state + value is set to C(true) the configuration is present in the + devices active configuration. When the state value is set to + C(false) the configuration values are removed from the devices + active configuration. + default: present + choices: ['present', 'absent'] +''' + +EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +--- +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +--- +- name: Configure rollback location + community.network.sros_rollback: + rollback_location: "cb3:/ansible" + provider: "{{ cli }}" + +- name: Remove all rollback configuration + community.network.sros_rollback: + state: absent + provider: "{{ cli }}" +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['...', '...'] +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps +from ansible_collections.community.network.plugins.module_utils.network.sros.sros import load_config, get_config, sros_argument_spec, check_args + + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) + + +def sanitize_config(lines): + commands = list() + for line in lines: + for index, entry in enumerate(commands): + if line.startswith(entry): + del commands[index] + break + commands.append(line) + return commands + + +def present(module, commands): + setters = set() + for key, value in module.argument_spec.items(): + if module.params[key] is not None: + setter = value.get('setter') or 'set_%s' % key + if setter not in setters: + setters.add(setter) + invoke(setter, module, commands) + + +def absent(module, commands): + config = get_config(module) + if 'rollback-location' in config: + commands.append('configure system rollback no rollback-location') + if 'rescue-location' in config: + commands.append('configure system rollback no rescue-location') + if 'remote-max-checkpoints' in config: + commands.append('configure system rollback no remote-max-checkpoints') + if 'local-max-checkpoints' in config: + commands.append('configure system rollback no remote-max-checkpoints') + + +def set_rollback_location(module, commands): + value = module.params['rollback_location'] + commands.append('configure system rollback rollback-location "%s"' % value) + + +def set_local_max_checkpoints(module, commands): + value = module.params['local_max_checkpoints'] + if not 1 <= value <= 50: + module.fail_json(msg='local_max_checkpoints must be between 1 and 50') + commands.append('configure system rollback local-max-checkpoints %s' % value) + + +def set_remote_max_checkpoints(module, commands): + value = module.params['remote_max_checkpoints'] + if not 1 <= value <= 50: + module.fail_json(msg='remote_max_checkpoints must be between 1 and 50') + commands.append('configure system rollback remote-max-checkpoints %s' % value) + + +def set_rescue_location(module, commands): + value = module.params['rescue_location'] + commands.append('configure system rollback rescue-location "%s"' % value) + + +def get_device_config(module): + contents = get_config(module) + return NetworkConfig(indent=4, contents=contents) + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + rollback_location=dict(), + + local_max_checkpoints=dict(type='int'), + remote_max_checkpoints=dict(type='int'), + + rescue_location=dict(), + + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(sros_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + state = module.params['state'] + + result = dict(changed=False) + + commands = list() + invoke(state, module, commands) + + candidate = NetworkConfig(indent=4, contents='\n'.join(commands)) + config = get_device_config(module) + configobjs = candidate.difference(config) + + if configobjs: + # commands = dumps(configobjs, 'lines') + commands = dumps(configobjs, 'commands') + commands = sanitize_config(commands.split('\n')) + + result['updates'] = commands + result['commands'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_commit.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_commit.py new file mode 100644 index 00000000..19c25b60 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_commit.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_commit +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Commits pending configuration changes on Radware devices +description: + - Commits pending configuration changes on one or more Radware devices via vDirect server. + - For Alteon ADC device, apply, sync and save actions will be performed by default. + Skipping of an action is possible by explicit parameter specifying. + - For Alteon VX Container device, no sync operation will be performed + since sync action is only relevant for Alteon ADC devices. + - For DefensePro and AppWall devices, a bulk commit action will be performed. + Explicit apply, sync and save actions specifying is not relevant. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as C(VDIRECT_IP) environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as C(VDIRECT_USER) environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as C(VDIRECT_PASSWORD) environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as C(VDIRECT_SECONDARY_IP) environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as C(VDIRECT_WAIT) environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as C(VDIRECT_HTTPS_PORT) environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as C(VDIRECT_HTTP_PORT) environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as C(VDIRECT_TIMEOUT) environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as C(VDIRECT_HTTPS) or C(VDIRECT_USE_SSL) environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as C(VDIRECT_VALIDATE_CERTS) or C(VDIRECT_VERIFY) environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + devices: + description: + - List of Radware Alteon device names for commit operations. + required: true + apply: + description: + - If C(no), apply action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + save: + description: + - If C(no), save action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + sync: + description: + - If C(no), sync action will not be performed. Relevant for ADC devices only. + type: bool + default: 'yes' + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Commit + community.network.vdirect_commit: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + devices: ['dev1', 'dev2'] + sync: no +''' + +RETURN = ''' +result: + description: Message detailing actions result + returned: success + type: str + sample: "Requested actions were successfully performed on all devices." +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + + +SUCCESS = 'Requested actions were successfully performed on all devices.' +FAILURE = 'Failure occurred while performing requested actions on devices. See details' + +ADC_DEVICE_TYPE = 'Adc' +CONTAINER_DEVICE_TYPE = 'Container' +PARTITIONED_CONTAINER_DEVICE_TYPE = 'AlteonPartitioned' +APPWALL_DEVICE_TYPE = 'AppWall' +DP_DEVICE_TYPE = 'DefensePro' + +SUCCEEDED = 'succeeded' +FAILED = 'failed' +NOT_PERFORMED = 'not performed' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + devices=dict( + required=True, type='list'), + apply=dict( + required=False, default=True, type='bool'), + save=dict( + required=False, default=True, type='bool'), + sync=dict( + required=False, default=True, type='bool'), +) + + +class CommitException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class MissingDeviceException(CommitException): + def __init__(self, device_name): + super(MissingDeviceException, self).__init__( + 'Device missing', + 'Device ' + repr(device_name) + ' does not exist') + + +class VdirectCommit(object): + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + self.devices = params['devices'] + self.apply = params['apply'] + self.save = params['save'] + self.sync = params['sync'] + self.devicesMap = {} + + def _validate_devices(self): + for device in self.devices: + try: + res = self.client.adc.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: ADC_DEVICE_TYPE}) + continue + res = self.client.container.get(device) + if res[rest_client.RESP_STATUS] == 200: + if res[rest_client.RESP_DATA]['type'] == PARTITIONED_CONTAINER_DEVICE_TYPE: + self.devicesMap.update({device: CONTAINER_DEVICE_TYPE}) + continue + res = self.client.appWall.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: APPWALL_DEVICE_TYPE}) + continue + res = self.client.defensePro.get(device) + if res[rest_client.RESP_STATUS] == 200: + self.devicesMap.update({device: DP_DEVICE_TYPE}) + continue + + except Exception as e: + raise CommitException('Failed to communicate with device ' + device, str(e)) + + raise MissingDeviceException(device) + + def _perform_action_and_update_result(self, device, action, perform, failure_occurred, actions_result): + + if not perform or failure_occurred: + actions_result[action] = NOT_PERFORMED + return True + + try: + if self.devicesMap[device] == ADC_DEVICE_TYPE: + res = self.client.adc.control_device(device, action) + elif self.devicesMap[device] == CONTAINER_DEVICE_TYPE: + res = self.client.container.control(device, action) + elif self.devicesMap[device] == APPWALL_DEVICE_TYPE: + res = self.client.appWall.control_device(device, action) + elif self.devicesMap[device] == DP_DEVICE_TYPE: + res = self.client.defensePro.control_device(device, action) + + if res[rest_client.RESP_STATUS] in [200, 204]: + actions_result[action] = SUCCEEDED + else: + actions_result[action] = FAILED + actions_result['failure_description'] = res[rest_client.RESP_STR] + return False + except Exception as e: + actions_result[action] = FAILED + actions_result['failure_description'] = 'Exception occurred while performing '\ + + action + ' action. Exception: ' + str(e) + return False + + return True + + def commit(self): + self._validate_devices() + + result_to_return = dict() + result_to_return['details'] = list() + + for device in self.devices: + failure_occurred = False + device_type = self.devicesMap[device] + actions_result = dict() + actions_result['device_name'] = device + actions_result['device_type'] = device_type + + if device_type in [DP_DEVICE_TYPE, APPWALL_DEVICE_TYPE]: + failure_occurred = not self._perform_action_and_update_result( + device, 'commit', True, failure_occurred, actions_result)\ + or failure_occurred + else: + failure_occurred = not self._perform_action_and_update_result( + device, 'apply', self.apply, failure_occurred, actions_result)\ + or failure_occurred + if device_type != CONTAINER_DEVICE_TYPE: + failure_occurred = not self._perform_action_and_update_result( + device, 'sync', self.sync, failure_occurred, actions_result)\ + or failure_occurred + failure_occurred = not self._perform_action_and_update_result( + device, 'save', self.save, failure_occurred, actions_result)\ + or failure_occurred + + result_to_return['details'].extend([actions_result]) + + if failure_occurred: + result_to_return['msg'] = FAILURE + + if 'msg' not in result_to_return: + result_to_return['msg'] = SUCCESS + + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_commit = VdirectCommit(module.params) + result = vdirect_commit.commit() + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_file.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_file.py new file mode 100644 index 00000000..9ce7c008 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_file.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_file +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Uploads a new or updates an existing runnable file into Radware vDirect server +description: + - Uploads a new or updates an existing configuration template or workflow template into the Radware vDirect server. + All parameters may be set as environment variables. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as VDIRECT_IP environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as VDIRECT_USER environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as VDIRECT_PASSWORD environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as VDIRECT_SECONDARY_IP environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as VDIRECT_WAIT environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as VDIRECT_HTTPS_PORT environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as VDIRECT_HTTP_PORT environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as VDIRECT_TIMEOUT environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as VDIRECT_HTTPS or VDIRECT_USE_SSL environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as VDIRECT_VALIDATE_CERTS or VDIRECT_VERIFY environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + file_name: + description: + - vDirect runnable file name to be uploaded. + - May be velocity configuration template (.vm) or workflow template zip file (.zip). + required: true + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Upload a new or updates an existing runnable file + community.network.vdirect_file: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + file_name: /tmp/get_vlans.vm +''' + +RETURN = ''' +result: + description: Message detailing upload result + returned: success + type: str + sample: "Workflow template created" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback +import os +import os.path + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + +TEMPLATE_EXTENSION = '.vm' +WORKFLOW_EXTENSION = '.zip' +WRONG_EXTENSION_ERROR = 'The file_name parameter must have ' \ + 'velocity script (.vm) extension or ZIP archive (.zip) extension' +CONFIGURATION_TEMPLATE_CREATED_SUCCESS = 'Configuration template created' +CONFIGURATION_TEMPLATE_UPDATED_SUCCESS = 'Configuration template updated' +WORKFLOW_TEMPLATE_CREATED_SUCCESS = 'Workflow template created' +WORKFLOW_TEMPLATE_UPDATED_SUCCESS = 'Workflow template updated' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + file_name=dict(required=True) +) + + +class FileException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class InvalidSourceException(FileException): + def __init__(self, message): + super(InvalidSourceException, self).__init__( + 'Error parsing file', repr(message)) + + +class VdirectFile(object): + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + + def upload(self, fqn): + if fqn.endswith(TEMPLATE_EXTENSION): + template_name = os.path.basename(fqn) + template = rest_client.Template(self.client) + runnable_file = open(fqn, 'r') + file_content = runnable_file.read() + + result_to_return = CONFIGURATION_TEMPLATE_CREATED_SUCCESS + result = template.create_from_source(file_content, template_name, fail_if_invalid=True) + if result[rest_client.RESP_STATUS] == 409: + result_to_return = CONFIGURATION_TEMPLATE_UPDATED_SUCCESS + result = template.upload_source(file_content, template_name, fail_if_invalid=True) + + if result[rest_client.RESP_STATUS] == 400: + raise InvalidSourceException(str(result[rest_client.RESP_STR])) + elif fqn.endswith(WORKFLOW_EXTENSION): + workflow = rest_client.WorkflowTemplate(self.client) + + runnable_file = open(fqn, 'rb') + file_content = runnable_file.read() + + result_to_return = WORKFLOW_TEMPLATE_CREATED_SUCCESS + result = workflow.create_template_from_archive(file_content, fail_if_invalid=True) + if result[rest_client.RESP_STATUS] == 409: + result_to_return = WORKFLOW_TEMPLATE_UPDATED_SUCCESS + result = workflow.update_archive(file_content, os.path.splitext(os.path.basename(fqn))[0]) + + if result[rest_client.RESP_STATUS] == 400: + raise InvalidSourceException(str(result[rest_client.RESP_STR])) + else: + result_to_return = WRONG_EXTENSION_ERROR + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_file = VdirectFile(module.params) + result = vdirect_file.upload(module.params['file_name']) + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_runnable.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_runnable.py new file mode 100644 index 00000000..0274fed5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/vdirect_runnable.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Radware LTD. +# +# 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 . + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +module: vdirect_runnable +author: Evgeny Fedoruk @ Radware LTD (@evgenyfedoruk) +short_description: Runs templates and workflow actions in Radware vDirect server +description: + - Runs configuration templates, creates workflows and runs workflow actions in Radware vDirect server. +notes: + - Requires the Radware vdirect-client Python package on the host. This is as easy as + C(pip install vdirect-client) +options: + vdirect_ip: + description: + - Primary vDirect server IP address, may be set as C(VDIRECT_IP) environment variable. + required: true + vdirect_user: + description: + - vDirect server username, may be set as C(VDIRECT_USER) environment variable. + required: true + vdirect_password: + description: + - vDirect server password, may be set as C(VDIRECT_PASSWORD) environment variable. + required: true + vdirect_secondary_ip: + description: + - Secondary vDirect server IP address, may be set as C(VDIRECT_SECONDARY_IP) environment variable. + vdirect_wait: + description: + - Wait for async operation to complete, may be set as C(VDIRECT_WAIT) environment variable. + type: bool + default: 'yes' + vdirect_https_port: + description: + - vDirect server HTTPS port number, may be set as C(VDIRECT_HTTPS_PORT) environment variable. + default: 2189 + vdirect_http_port: + description: + - vDirect server HTTP port number, may be set as C(VDIRECT_HTTP_PORT) environment variable. + default: 2188 + vdirect_timeout: + description: + - Amount of time to wait for async operation completion [seconds], + - may be set as C(VDIRECT_TIMEOUT) environment variable. + default: 60 + vdirect_use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection, + - may be set as C(VDIRECT_HTTPS) or C(VDIRECT_USE_SSL) environment variable. + type: bool + default: 'yes' + validate_certs: + description: + - If C(no), SSL certificates will not be validated, + - may be set as C(VDIRECT_VALIDATE_CERTS) or C(VDIRECT_VERIFY) environment variable. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + type: bool + default: 'yes' + aliases: [ vdirect_validate_certs ] + runnable_type: + description: + - vDirect runnable type. + required: true + choices: ['ConfigurationTemplate', 'Workflow', 'WorkflowTemplate', 'Plugin'] + runnable_name: + description: + - vDirect runnable name to run. + - May be configuration template name, workflow template name or workflow instance name. + required: true + action_name: + description: + - Workflow action name to run. + - Required if I(runnable_type=Workflow). + parameters: + description: + - Action parameters dictionary. In case of C(ConfigurationTemplate) runnable type, + - the device connection details should always be passed as a parameter. + +requirements: + - "vdirect-client >= 4.9.0-post4" +''' + +EXAMPLES = ''' +- name: Run the module from Ansible playbook + community.network.vdirect_runnable: + vdirect_ip: 10.10.10.10 + vdirect_user: vDirect + vdirect_password: radware + runnable_type: ConfigurationTemplate + runnable_name: get_vlans + parameters: {'vlans_needed':1,'adc':[{'type':'Adc','name':'adc-1'}]} +''' + +RETURN = ''' +result: + description: Message detailing run result + returned: success + type: str + sample: "Workflow action run completed." +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +try: + from vdirect_client import rest_client + HAS_REST_CLIENT = True +except ImportError: + HAS_REST_CLIENT = False + +CONFIGURATION_TEMPLATE_RUNNABLE_TYPE = 'ConfigurationTemplate' +WORKFLOW_TEMPLATE_RUNNABLE_TYPE = 'WorkflowTemplate' +WORKFLOW_RUNNABLE_TYPE = 'Workflow' +PLUGIN_RUNNABLE_TYPE = 'Plugin' + +TEMPLATE_SUCCESS = 'Configuration template run completed.' +WORKFLOW_CREATION_SUCCESS = 'Workflow created.' +WORKFLOW_ACTION_SUCCESS = 'Workflow action run completed.' +PLUGIN_ACTION_SUCCESS = 'Plugin action run completed.' + +meta_args = dict( + vdirect_ip=dict(required=True, fallback=(env_fallback, ['VDIRECT_IP'])), + vdirect_user=dict(required=True, fallback=(env_fallback, ['VDIRECT_USER'])), + vdirect_password=dict( + required=True, fallback=(env_fallback, ['VDIRECT_PASSWORD']), + no_log=True, type='str'), + vdirect_secondary_ip=dict( + required=False, fallback=(env_fallback, ['VDIRECT_SECONDARY_IP']), + default=None), + vdirect_use_ssl=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS', 'VDIRECT_USE_SSL']), + default=True, type='bool'), + vdirect_wait=dict( + required=False, fallback=(env_fallback, ['VDIRECT_WAIT']), + default=True, type='bool'), + vdirect_timeout=dict( + required=False, fallback=(env_fallback, ['VDIRECT_TIMEOUT']), + default=60, type='int'), + validate_certs=dict( + required=False, fallback=(env_fallback, ['VDIRECT_VERIFY', 'VDIRECT_VALIDATE_CERTS']), + default=True, type='bool', aliases=['vdirect_validate_certs']), + vdirect_https_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTPS_PORT']), + default=2189, type='int'), + vdirect_http_port=dict( + required=False, fallback=(env_fallback, ['VDIRECT_HTTP_PORT']), + default=2188, type='int'), + runnable_type=dict( + required=True, + choices=[CONFIGURATION_TEMPLATE_RUNNABLE_TYPE, WORKFLOW_TEMPLATE_RUNNABLE_TYPE, WORKFLOW_RUNNABLE_TYPE, PLUGIN_RUNNABLE_TYPE]), + runnable_name=dict(required=True), + action_name=dict(required=False, default=None), + parameters=dict(required=False, type='dict', default={}) +) + + +class RunnableException(Exception): + def __init__(self, reason, details): + self.reason = reason + self.details = details + + def __str__(self): + return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details) + + +class WrongActionNameException(RunnableException): + def __init__(self, action, available_actions): + super(WrongActionNameException, self).__init__('Wrong action name ' + repr(action), + 'Available actions are: ' + repr(available_actions)) + + +class MissingActionParametersException(RunnableException): + def __init__(self, required_parameters): + super(MissingActionParametersException, self).__init__( + 'Action parameters missing', + 'Required parameters are: ' + repr(required_parameters)) + + +class MissingRunnableException(RunnableException): + def __init__(self, name): + super(MissingRunnableException, self).__init__( + 'Runnable missing', + 'Runnable ' + name + ' is missing') + + +class VdirectRunnable(object): + + CREATE_WORKFLOW_ACTION = 'createWorkflow' + RUN_ACTION = 'run' + + def __init__(self, params): + self.client = rest_client.RestClient(params['vdirect_ip'], + params['vdirect_user'], + params['vdirect_password'], + wait=params['vdirect_wait'], + secondary_vdirect_ip=params['vdirect_secondary_ip'], + https_port=params['vdirect_https_port'], + http_port=params['vdirect_http_port'], + timeout=params['vdirect_timeout'], + strict_http_results=True, + https=params['vdirect_use_ssl'], + verify=params['validate_certs']) + self.params = params + self.type = self.params['runnable_type'] + self.name = self.params['runnable_name'] + + if self.type == WORKFLOW_TEMPLATE_RUNNABLE_TYPE: + self.action_name = VdirectRunnable.CREATE_WORKFLOW_ACTION + elif self.type == CONFIGURATION_TEMPLATE_RUNNABLE_TYPE: + self.action_name = VdirectRunnable.RUN_ACTION + else: + self.action_name = self.params['action_name'] + + if 'parameters' in self.params and self.params['parameters']: + self.action_params = self.params['parameters'] + else: + self.action_params = {} + + def _validate_runnable_exists(self): + if self.type == WORKFLOW_RUNNABLE_TYPE: + res = self.client.runnable.get_runnable_objects(self.type) + runnable_names = res[rest_client.RESP_DATA]['names'] + if self.name not in runnable_names: + raise MissingRunnableException(self.name) + else: + try: + self.client.catalog.get_catalog_item(self.type, self.name) + except rest_client.RestClientException: + raise MissingRunnableException(self.name) + + def _validate_action_name(self): + if self.type in [WORKFLOW_RUNNABLE_TYPE, PLUGIN_RUNNABLE_TYPE]: + res = self.client.runnable.get_available_actions(self.type, self.name) + available_actions = res[rest_client.RESP_DATA]['names'] + if self.action_name not in available_actions: + raise WrongActionNameException(self.action_name, available_actions) + + def _validate_required_action_params(self): + action_params_names = [n for n in self.action_params] + + res = self.client.runnable.get_action_info(self.type, self.name, self.action_name) + if 'parameters' in res[rest_client.RESP_DATA]: + action_params_spec = res[rest_client.RESP_DATA]['parameters'] + else: + action_params_spec = [] + + required_action_params_dict = [{'name': p['name'], 'type': p['type']} for p in action_params_spec + if p['type'] == 'alteon' or + p['type'] == 'defensePro' or + p['type'] == 'appWall' or + p['type'] == 'alteon[]' or + p['type'] == 'defensePro[]' or + p['type'] == 'appWall[]' or + p['direction'] != 'out'] + required_action_params_names = [n['name'] for n in required_action_params_dict] + + if set(required_action_params_names) & set(action_params_names) != set(required_action_params_names): + raise MissingActionParametersException(required_action_params_dict) + + def run(self): + self._validate_runnable_exists() + self._validate_action_name() + self._validate_required_action_params() + + data = self.action_params + + result = self.client.runnable.run(data, self.type, self.name, self.action_name) + result_to_return = {'msg': ''} + if result[rest_client.RESP_STATUS] == 200: + if result[rest_client.RESP_DATA]['success']: + if self.type == WORKFLOW_TEMPLATE_RUNNABLE_TYPE: + result_to_return['msg'] = WORKFLOW_CREATION_SUCCESS + elif self.type == CONFIGURATION_TEMPLATE_RUNNABLE_TYPE: + result_to_return['msg'] = TEMPLATE_SUCCESS + elif self.type == PLUGIN_RUNNABLE_TYPE: + result_to_return['msg'] = PLUGIN_ACTION_SUCCESS + else: + result_to_return['msg'] = WORKFLOW_ACTION_SUCCESS + result_to_return['output'] = result[rest_client.RESP_DATA] + + else: + if 'exception' in result[rest_client.RESP_DATA]: + raise RunnableException(result[rest_client.RESP_DATA]['exception']['message'], + result[rest_client.RESP_STR]) + else: + raise RunnableException('The status returned ' + str(result[rest_client.RESP_DATA]['status']), + result[rest_client.RESP_STR]) + else: + raise RunnableException(result[rest_client.RESP_REASON], + result[rest_client.RESP_STR]) + + return result_to_return + + +def main(): + + module = AnsibleModule(argument_spec=meta_args, + required_if=[['runnable_type', WORKFLOW_RUNNABLE_TYPE, ['action_name']], + ['runnable_type', PLUGIN_RUNNABLE_TYPE, ['action_name']]]) + + if not HAS_REST_CLIENT: + module.fail_json(msg="The python vdirect-client module is required") + + try: + vdirect_runnable = VdirectRunnable(module.params) + result = vdirect_runnable.run() + result = dict(result=result) + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_command.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_command.py new file mode 100644 index 00000000..16aa7b53 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_command.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: voss_command +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme VOSS +description: + - Sends arbitrary commands to an Extreme VSP device running VOSS, and + returns the results read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(community.network.voss_config) to configure VOSS devices. +notes: + - Tested against VOSS 7.0.0 +options: + commands: + description: + - List of commands to send to the remote VOSS device. The + resulting output from the command is returned. If the + I(wait_for) argument is provided, the module is not returned + until the condition is satisfied or the number of retries has + expired. If a command sent to the device requires answering a + prompt, it is possible to pass a dict containing I(command), + I(answer) and I(prompt). Common answers are 'y' or "\\r" + (carriage return, must be double quotes). See examples. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + default: 1 +""" + +EXAMPLES = r""" +tasks: + - name: Run show sys software on remote devices + community.network.voss_command: + commands: show sys software + + - name: Run show sys software and check to see if output contains VOSS + community.network.voss_command: + commands: show sys software + wait_for: result[0] contains VOSS + + - name: Run multiple commands on remote nodes + community.network.voss_command: + commands: + - show sys software + - show interfaces vlan + + - name: Run multiple commands and evaluate the output + community.network.voss_command: + commands: + - show sys software + - show interfaces vlan + wait_for: + - result[0] contains Version + - result[1] contains Basic + + - name: Run command that requires answering a prompt + community.network.voss_command: + commands: + - command: 'reset' + prompt: 'Are you sure you want to reset the switch? (y/n)' + answer: 'y' +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='voss_command does not support running config mode ' + 'commands. Please use voss_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_config.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_config.py new file mode 100644 index 00000000..79440b90 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_config.py @@ -0,0 +1,451 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Extreme Networks Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type +DOCUMENTATION = ''' +--- +module: voss_config +author: "Lindsay Hill (@LindsayHill)" +short_description: Manage Extreme VOSS configuration sections +description: + - Extreme VOSS configurations use a simple flat text file syntax. + This module provides an implementation for working with EXOS + configuration lines in a deterministic way. +notes: + - Tested against VOSS 7.0.0 + - Abbreviated commands are NOT idempotent, see + L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands). +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. + aliases: ['commands'] + parents: + description: + - The parent line that uniquely identifies the section the commands + should be checked against. If this argument is omitted, the commands + are checked against the set of top level or global commands. Note + that VOSS configurations only support one level of nested commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines), I(parents). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + choices: ['line', 'strict', 'exact', 'none'] + default: line + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + backup: + description: + - 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. If the C(backup_options) value is not given, + the backup file is written to the C(backup) 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. + type: bool + default: 'no' + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + aliases: ['config'] + defaults: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config verbose). + type: bool + default: 'no' + save_when: + description: + - When changes are made to the device running-configuration, the + changes are not copied to non-volatile storage by default. Using + this argument will change that behavior. If the argument is set to + I(always), then the running-config will always be saved and the + I(modified) flag will always be set to True. If the argument is set + to I(modified), then the running-config will only be saved if it + has changed since the last save to startup-config. If the argument + is set to I(never), the running-config will never be saved. + If the argument is set to I(changed), then the running-config + will only be saved if the task has made a change. + default: never + choices: ['always', 'never', 'modified', 'changed'] + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument + the module can generate diffs against different sources. + - When this option is configure as I(startup), the module will return + the diff of the running-config against the startup-config. + - When this option is configured as I(intended), the module will + return the diff of the running-config against the configuration + provided in the C(intended_config) argument. + - When this option is configured as I(running), the module will + return the before and after diff of the running-config with respect + to any changes made to the device configuration. + choices: ['running', 'startup', 'intended'] + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be + ignored during the diff. This is used for lines in the configuration + that are automatically updated by the system. This argument takes + a list of regular expressions or exact line matches. + intended_config: + description: + - The C(intended_config) provides the master configuration that + the node should conform to and is used to check the final + running-config against. This argument will not modify any settings + on the remote device and is strictly used to check the compliance + of the current device's configuration against. When specifying this + argument, the task should also modify the C(diff_against) value and + set it to I(intended). + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by _config.@ + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +''' + +EXAMPLES = """ +- name: Configure system name + community.network.voss_config: + lines: prompt "{{ inventory_hostname }}" + +- name: Configure interface settings + community.network.voss_config: + lines: + - name "ServerA" + backup: yes + parents: interface GigabitEthernet 1/1 + +- name: Check the running-config against master config + community.network.voss_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- name: Check the startup-config against the running-config + community.network.voss_config: + diff_against: startup + diff_ignore_lines: + - qos queue-profile .* + +- name: Save running to startup when modified + community.network.voss_config: + save_when: modified + +- name: Configurable backup path + community.network.voss_config: + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['prompt "VSP200"'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['interface GigabitEthernet 1/1', 'name "ServerA"', 'exit'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/vsp200_config.2018-08-21@15:00:21 +""" +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands, get_config +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import get_defaults_flag, get_connection +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import get_sublevel_config, VossNetworkConfig +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import dumps + + +def get_candidate_config(module): + candidate = VossNetworkConfig(indent=0) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + commands = module.params['lines'][0] + if (isinstance(commands, dict)) and (isinstance(commands['command'], list)): + candidate.add(commands['command'], parents=parents) + elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)): + candidate.add([commands['command']], parents=parents) + else: + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def get_running_config(module, current_config=None, flags=None): + running = module.params['running_config'] + if not running: + if not module.params['defaults'] and current_config: + running = current_config + else: + running = get_config(module, flags=flags) + + return running + + +def save_config(module, result): + result['changed'] = True + if not module.check_mode: + run_commands(module, 'save config\r') + else: + module.warn('Skipping command `save config` ' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + +def main(): + """ main entry point for module execution + """ + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + src=dict(type='path'), + + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + running_config=dict(aliases=['config']), + intended_config=dict(), + + defaults=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec), + + save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'), + + diff_against=dict(choices=['startup', 'intended', 'running']), + diff_ignore_lines=dict(type='list'), + ) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + + required_if = [('match', 'strict', ['lines']), + ('match', 'exact', ['lines']), + ('replace', 'block', ['lines']), + ('diff_against', 'intended', ['intended_config'])] + + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + parents = module.params['parents'] or list() + + match = module.params['match'] + replace = module.params['replace'] + + warnings = list() + result['warnings'] = warnings + + diff_ignore_lines = module.params['diff_ignore_lines'] + + config = None + contents = None + flags = get_defaults_flag(module) if module.params['defaults'] else [] + connection = get_connection(module) + + if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): + contents = get_config(module, flags=flags) + config = VossNetworkConfig(indent=0, contents=contents) + if module.params['backup']: + result['__backup__'] = contents + + if any((module.params['lines'], module.params['src'])): + candidate = get_candidate_config(module) + if match != 'none': + config = get_running_config(module) + config = VossNetworkConfig(contents=config, indent=0) + + if parents: + config = get_sublevel_config(config, module) + configobjs = candidate.difference(config, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands') + commands = commands.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + result['commands'] = commands + result['updates'] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + if commands: + try: + connection.edit_config(candidate=commands) + except ConnectionError as exc: + module.fail_json(msg=to_text(commands, errors='surrogate_then_replace')) + + result['changed'] = True + + running_config = module.params['running_config'] + startup = None + + if module.params['save_when'] == 'always': + save_config(module, result) + elif module.params['save_when'] == 'modified': + match = module.params['match'] + replace = module.params['replace'] + try: + # Note we need to re-retrieve running config, not use cached version + running = connection.get_config(source='running') + startup = connection.get_config(source='startup') + response = connection.get_diff(candidate=startup, running=running, diff_match=match, + diff_ignore_lines=diff_ignore_lines, path=None, + diff_replace=replace) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + + config_diff = response['config_diff'] + if config_diff: + save_config(module, result) + elif module.params['save_when'] == 'changed' and result['changed']: + save_config(module, result) + + if module._diff: + if not running_config: + try: + # Note we need to re-retrieve running config, not use cached version + contents = connection.get_config(source='running') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + else: + contents = running_config + + # recreate the object in order to process diff_ignore_lines + running_config = VossNetworkConfig(indent=0, contents=contents, + ignore_lines=diff_ignore_lines) + + if module.params['diff_against'] == 'running': + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params['diff_against'] == 'startup': + if not startup: + try: + contents = connection.get_config(source='startup') + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + else: + contents = startup + + elif module.params['diff_against'] == 'intended': + contents = module.params['intended_config'] + + if contents is not None: + base_config = VossNetworkConfig(indent=0, contents=contents, + ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + if module.params['diff_against'] == 'intended': + before = running_config + after = base_config + elif module.params['diff_against'] in ('startup', 'running'): + before = base_config + after = running_config + + result.update({ + 'changed': True, + 'diff': {'before': str(before), 'after': str(after)} + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_facts.py b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_facts.py new file mode 100644 index 00000000..2e420abf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/modules/voss_facts.py @@ -0,0 +1,503 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: voss_facts +author: "Lindsay Hill (@LindsayHill)" +short_description: Collect facts from remote devices running Extreme VOSS +description: + - Collects a base set of device facts from a remote device that + is running VOSS. This module prepends all of the base network fact + keys with C(ansible_net_). The facts module will always collect + a base set of facts from the device and can enable or disable + collection of additional facts. +notes: + - Tested against VOSS 7.0.0 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should + not be collected. + required: false + default: '!config' +''' + +EXAMPLES = """ +- name: Collect all facts from the device + community.network.voss_facts: + gather_subset: all + +- name: Collect only the config and default facts + community.network.voss_facts: + gather_subset: + - config + +- name: Do not collect hardware facts + community.network.voss_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str + +# hardware +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re + +from ansible_collections.community.network.plugins.module_utils.network.voss.voss import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show sys-info'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'SysDescr\s+: \S+ \((\S+)\)', data) + if match: + return match.group(1) + return '' + + def parse_hostname(self, data): + match = re.search(r'SysName\s+: (\S+)', data, re.M) + if match: + return match.group(1) + return '' + + def parse_model(self, data): + match = re.search(r'Chassis\s+: (\S+)', data, re.M) + if match: + return match.group(1) + return '' + + def parse_serialnum(self, data): + match = re.search(r'Serial#\s+: (\S+)', data) + if match: + return match.group(1) + return '' + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show khi performance memory' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + + if data: + match = re.search(r'Free:\s+(\d+)\s+\(KB\)', data, re.M) + if match: + self.facts['memfree_mb'] = int(round(int(match.group(1)) / 1024, 0)) + match = re.search(r'Used:\s+(\d+)\s+\(KB\)', data, re.M) + if match: + memused_mb = int(round(int(match.group(1)) / 1024, 0)) + self.facts['memtotal_mb'] = self.facts.get('memfree_mb', 0) + memused_mb + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces gigabitEthernet interface', + 'show interfaces gigabitEthernet name', + 'show ip interface', + 'show ipv6 address interface', + 'show lldp neighbor | include Port|SysName' + ] + + def populate(self): + super(Interfaces, self).populate() + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces_eth(interfaces) + + data = self.responses[1] + if data: + data = self.parse_interfaces(data) + self.populate_interfaces_eth_additional(data) + + data = self.responses[2] + if data: + data = self.parse_interfaces(data) + self.populate_ipv4_interfaces(data) + + data = self.responses[3] + if data: + self.populate_ipv6_interfaces(data) + + data = self.responses[4] + if data: + self.facts['neighbors'] = self.parse_neighbors(data) + + def populate_interfaces_eth(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + match = re.match(r'^\d+\s+(\S+)\s+\w+\s+\w+\s+(\d+)\s+([a-f\d:]+)\s+(\w+)\s+(\w+)$', value) + if match: + intf['mediatype'] = match.group(1) + intf['mtu'] = match.group(2) + intf['macaddress'] = match.group(3) + intf['adminstatus'] = match.group(4) + intf['operstatus'] = match.group(5) + intf['type'] = 'Ethernet' + facts[key] = intf + return facts + + def populate_interfaces_eth_additional(self, interfaces): + for key, value in iteritems(interfaces): + # This matches when no description is set + match = re.match(r'^\w+\s+\w+\s+(\w+)\s+(\d+)\s+\w+$', value) + if match: + self.facts['interfaces'][key]['description'] = '' + self.facts['interfaces'][key]['duplex'] = match.group(1) + self.facts['interfaces'][key]['bandwidth'] = match.group(2) + else: + # This matches when a description is set + match = re.match(r'^(.+)\s+\w+\s+\w+\s+(\w+)\s+(\d+)\s+\w+$', value) + if match: + self.facts['interfaces'][key]['description'] = match.group(1).strip() + self.facts['interfaces'][key]['duplex'] = match.group(2) + self.facts['interfaces'][key]['bandwidth'] = match.group(3) + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + if key not in self.facts['interfaces']: + if re.match(r'Vlan\d+', key): + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['type'] = 'VLAN' + elif re.match(r'Clip\d+', key): + self.facts['interfaces'][key] = dict() + self.facts['interfaces'][key]['type'] = 'Loopback' + if re.match(r'Port(\d+/\d+)', key): + key = re.split('Port', key)[1] + self.facts['interfaces'][key]['ipv4'] = list() + match = re.match(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', value, re.M) + if match: + addr = match.group(1) + subnet = match.group(2) + ipv4 = dict(address=addr, subnet=subnet) + self.add_ip_address(addr, 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + def populate_ipv6_interfaces(self, data): + addresses = re.split(r'-{3,}', data)[1].lstrip() + for line in addresses.split('\n'): + if not line: + break + + match = re.match(r'^([\da-f:]+)/(\d+)\s+([CV])-(\d+)\s+.+$', line) + if match: + address = match.group(1) + subnet = match.group(2) + interface_short_name = match.group(3) + interface_id = match.group(4) + if interface_short_name == 'C': + intf_type = 'Loopback' + interface_name = 'Clip' + interface_id + elif interface_short_name == 'V': + intf_type = 'VLAN' + interface_name = 'Vlan' + interface_id + else: + # Unknown interface type, better to gracefully ignore it for now + break + ipv6 = dict(address=address, subnet=subnet) + self.add_ip_address(address, 'ipv6') + try: + self.facts['interfaces'][interface_name].setdefault('ipv6', []).append(ipv6) + self.facts['interfaces'][interface_name]['type'] = intf_type + except KeyError: + self.facts['interfaces'][interface_name] = dict() + self.facts['interfaces'][interface_name]['type'] = intf_type + self.facts['interfaces'][interface_name].setdefault('ipv6', []).append(ipv6) + else: + break + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + lines = neighbors.split('Port: ') + if not lines: + return facts + for line in lines: + match = re.search(r'^(\w.*?)\s+Index.*IfName\s+(\w.*)$\s+SysName\s+:\s(\S+)', line, (re.M | re.S)) + if match: + intf = match.group(1) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = match.group(3) + fact['port'] = match.group(2) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + interfaces = re.split(r'-{3,}', data)[1].lstrip() + for line in interfaces.split('\n'): + if not line or re.match('^All', line): + break + else: + match = re.split(r'^(\S+)\s+', line) + key = match[1] + parsed[key] = match[2].strip() + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_macaddress(self, data): + match = re.search(r'Hardware is (?:.*), address is (\S+)', data) + if match: + return match.group(1) + return '' + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + return '' + + def parse_bandwidth(self, data): + match = re.search(r'BW (\d+)', data) + if match: + return int(match.group(1)) + return '' + + def parse_duplex(self, data): + match = re.search(r'(\w+) Duplex', data, re.M) + if match: + return match.group(1) + return '' + + def parse_mediatype(self, data): + match = re.search(r'media type is (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + return '' + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (.+)$', data, re.M) + if match: + return match.group(1) + return '' + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + return '' + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/netconf/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/netconf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/netconf/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/netconf/ce.py new file mode 100644 index 00000000..4050f794 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/netconf/ce.py @@ -0,0 +1,248 @@ +# +# (c) 2017 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +netconf: ce +short_description: Use ce netconf plugin to run netconf commands on Huawei Cloudengine platform +description: + - This ce plugin provides low level abstraction apis for + sending and receiving netconf commands from Huawei Cloudengine network devices. +options: + ncclient_device_handler: + type: str + default: huawei + description: + - Specifies the ncclient device handler name for Huawei Cloudengine. + To identify the ncclient device handler name refer ncclient library documentation. +''' + +import json +import re + +from ansible.module_utils._text import to_text, to_bytes +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.netconf import NetconfBase, ensure_ncclient + +try: + from ncclient import manager + from ncclient.operations import RPCError + from ncclient.transport.errors import SSHUnknownHostError + from ncclient.xml_ import to_ele, to_xml, new_ele + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False + +try: + from lxml.etree import fromstring +except ImportError: + from xml.etree.ElementTree import fromstring + + +class Netconf(NetconfBase): + + @ensure_ncclient + def get_text(self, ele, tag): + try: + return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() + except AttributeError: + pass + + @ensure_ncclient + def get_device_info(self): + device_info = dict() + device_info['network_os'] = 'ce' + filter_xml = ''' + + + + + + + + + + ''' + data = self.get(filter_xml) + data = re.sub(r'xmlns=".+?"', r'', data) + reply = fromstring(to_bytes(data, errors='surrogate_or_strict')) + sw_info = reply.find('.//systemInfo') + + device_info['network_os_version'] = self.get_text(sw_info, 'productVer') + device_info['network_os_hostname'] = self.get_text(sw_info, 'sysName') + device_info['network_os_platform_version'] = self.get_text(sw_info, 'platformVer') + device_info['network_os_platform'] = self.get_text(sw_info, 'productName') + + return device_info + + def execute_rpc(self, name): + """RPC to be execute on remote device + :name: Name of rpc in string format""" + return self.rpc(name) + + @ensure_ncclient + def load_configuration(self, *args, **kwargs): + """Loads given configuration on device + :format: Format of configuration (xml, text, set) + :action: Action to be performed (merge, replace, override, update) + :target: is the name of the configuration datastore being edited + :config: is the configuration in string format.""" + if kwargs.get('config'): + kwargs['config'] = to_bytes(kwargs['config'], errors='surrogate_or_strict') + if kwargs.get('format', 'xml') == 'xml': + kwargs['config'] = to_ele(kwargs['config']) + + try: + return self.m.load_configuration(*args, **kwargs).data_xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + ['execute_rpc', 'load_configuration', 'get_configuration', 'compare_configuration', + 'execute_action', 'halt', 'reboot', 'execute_nc_cli', 'dispatch_rpc'] + result['network_api'] = 'netconf' + result['device_info'] = self.get_device_info() + result['server_capabilities'] = [c for c in self.m.server_capabilities] + result['client_capabilities'] = [c for c in self.m.client_capabilities] + result['session_id'] = self.m.session_id + return json.dumps(result) + + @staticmethod + @ensure_ncclient + def guess_network_os(obj): + try: + m = manager.connect( + host=obj._play_context.remote_addr, + port=obj._play_context.port or 830, + username=obj._play_context.remote_user, + password=obj._play_context.password, + key_filename=obj.key_filename, + hostkey_verify=obj.get_option('host_key_checking'), + look_for_keys=obj.get_option('look_for_keys'), + allow_agent=obj._play_context.allow_agent, + timeout=obj.get_option('persistent_connect_timeout'), + # We need to pass in the path to the ssh_config file when guessing + # the network_os so that a jumphost is correctly used if defined + ssh_config=obj._ssh_config + ) + except SSHUnknownHostError as exc: + raise AnsibleConnectionFailure(to_text(exc)) + + guessed_os = None + for c in m.server_capabilities: + if re.search('huawei', c): + guessed_os = 'ce' + break + + m.close_session() + return guessed_os + + def get_configuration(self, *args, **kwargs): + """Retrieve all or part of a specified configuration. + :format: format in configuration should be retrieved + :filter: specifies the portion of the configuration to retrieve + (by default entire configuration is retrieved)""" + return self.m.get_configuration(*args, **kwargs).data_xml + + def compare_configuration(self, *args, **kwargs): + """Compare configuration + :rollback: rollback id""" + return self.m.compare_configuration(*args, **kwargs).data_xml + + @ensure_ncclient + def execute_action(self, xml_str): + """huawei execute-action""" + con_obj = None + try: + con_obj = self.m.action(action=xml_str) + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + return con_obj.xml + + def halt(self): + """reboot the device""" + return self.m.halt().data_xml + + def reboot(self): + """reboot the device""" + return self.m.reboot().data_xml + + @ensure_ncclient + def get(self, *args, **kwargs): + try: + if_rpc_reply = kwargs.pop('if_rpc_reply', False) + if if_rpc_reply: + return self.m.get(*args, **kwargs).xml + return self.m.get(*args, **kwargs).data_xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + @ensure_ncclient + def get_config(self, *args, **kwargs): + try: + return self.m.get_config(*args, **kwargs).data_xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + @ensure_ncclient + def edit_config(self, *args, **kwargs): + try: + return self.m.edit_config(*args, **kwargs).xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + @ensure_ncclient + def execute_nc_cli(self, *args, **kwargs): + try: + return self.m.cli(*args, **kwargs).xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + @ensure_ncclient + def commit(self, *args, **kwargs): + try: + return self.m.commit(*args, **kwargs).data_xml + except RPCError as exc: + raise Exception(to_xml(exc.xml)) + + def validate(self, *args, **kwargs): + return self.m.validate(*args, **kwargs).data_xml + + def discard_changes(self, *args, **kwargs): + return self.m.discard_changes(*args, **kwargs).data_xml + + @ensure_ncclient + def dispatch_rpc(self, rpc_command=None, source=None, filter=None): + """ + Execute rpc on the remote device eg. dispatch('get-next') + :param rpc_command: specifies rpc command to be dispatched either in plain text or in xml element format (depending on command) + :param source: name of the configuration datastore being queried + :param filter: specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) + :return: Returns xml string containing the rpc-reply response received from remote host + """ + if rpc_command is None: + raise ValueError('rpc_command value must be provided') + resp = self.m.dispatch(fromstring(rpc_command), source=source, filter=filter) + # just return rpc-reply xml + return resp.xml diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/netconf/sros.py b/collections-debian-merged/ansible_collections/community/network/plugins/netconf/sros.py new file mode 100644 index 00000000..52e11b4f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/netconf/sros.py @@ -0,0 +1,120 @@ +# +# (c) 2018 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +author: Unknown (!UNKNOWN) +netconf: sros +short_description: Use Nokia SROS netconf plugin to run netconf commands on Nokia SROS platform +deprecated: + why: This plugin moved in 'nokia.sros' collection + removed_in: '3.0.0' # was Ansible 2.13 + alternative: "Use the netconf plugin in 'nokia.sros' collection within Ansible galaxy" +description: + - This sros plugin provides low level abstraction apis for + sending and receiving netconf commands from Nokia sros network devices. +options: + ncclient_device_handler: + type: str + default: default + description: + - Specifies the ncclient device handler name for Nokia sros network os. To + identify the ncclient device handler name refer ncclient library documentation. +''' + +import json +import re + +from ansible.module_utils._text import to_text, to_native +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.netconf import NetconfBase +from ansible.plugins.netconf import ensure_ncclient + +try: + from ncclient import manager + from ncclient.transport.errors import SSHUnknownHostError + from ncclient.xml_ import to_ele + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False + + +class Netconf(NetconfBase): + def get_text(self, ele, tag): + try: + return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() + except AttributeError: + pass + + @ensure_ncclient + def get_device_info(self): + device_info = dict() + device_info['network_os'] = 'sros' + + xmlns = "urn:nokia.com:sros:ns:yang:sr:state" + f = '' % xmlns + reply = to_ele(self.m.get(filter=('subtree', f)).data_xml) + + device_info['network_os_hostname'] = reply.findtext('.//{%s}state/{*}system/{*}lldp/{*}system-name' % xmlns) + device_info['network_os_version'] = reply.findtext('.//{%s}state/{*}system/{*}version/{*}version-number' % xmlns) + device_info['network_os_model'] = reply.findtext('.//{%s}state/{*}system/{*}platform' % xmlns) + device_info['network_os_platform'] = 'Nokia 7x50' + return device_info + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + result['network_api'] = 'netconf' + result['device_info'] = self.get_device_info() + result['server_capabilities'] = [c for c in self.m.server_capabilities] + result['client_capabilities'] = [c for c in self.m.client_capabilities] + result['session_id'] = self.m.session_id + result['device_operations'] = self.get_device_operations(result['server_capabilities']) + return json.dumps(result) + + @staticmethod + @ensure_ncclient + def guess_network_os(obj): + try: + m = manager.connect( + host=obj._play_context.remote_addr, + port=obj._play_context.port or 830, + username=obj._play_context.remote_user, + password=obj._play_context.password, + key_filename=obj.key_filename, + hostkey_verify=obj.get_option('host_key_checking'), + look_for_keys=obj.get_option('look_for_keys'), + allow_agent=obj._play_context.allow_agent, + timeout=obj.get_option('persistent_connect_timeout'), + # We need to pass in the path to the ssh_config file when guessing + # the network_os so that a jumphost is correctly used if defined + ssh_config=obj._ssh_config + ) + except SSHUnknownHostError as exc: + raise AnsibleConnectionFailure(to_native(exc)) + + guessed_os = None + for c in m.server_capabilities: + if re.search('urn:nokia.com:sros:ns:yang:sr', c): + guessed_os = 'sros' + + m.close_session() + return guessed_os diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/__init__.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aireos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aireos.py new file mode 100644 index 00000000..240b1dff --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aireos.py @@ -0,0 +1,59 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re +import time + +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w]*\(.+\)?[>#\$](?:\s*)$"), + re.compile(br"User:") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"incorrect usage", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found", re.I), + re.compile(br"'[^']' +returned error code: ?\d+"), + ] + + def on_open_shell(self): + try: + commands = ('{"command": "' + self._connection._play_context.remote_user + '", "prompt": "Password:", "answer": "' + + self._connection._play_context.password + '"}', + '{"command": "config paging disable"}') + for cmd in commands: + self._exec_cli_command(cmd) + except AnsibleConnectionFailure: + try: + self._exec_cli_command(b'config paging disable') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/apconos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/apconos.py new file mode 100644 index 00000000..0cbd74f6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/apconos.py @@ -0,0 +1,35 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2019 APCON. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains terminal Plugin methods for apconos Config Module +# Apcon Networking +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br'>>\ |#\ |\$\ ') + ] + + terminal_stderr_re = [ + re.compile(br"connection timed out", re.I), + ] diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aruba.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aruba.py new file mode 100644 index 00000000..79bce7a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/aruba.py @@ -0,0 +1,68 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + ansi_re = [ + # check ECMA-48 Section 5.4 (Control Sequences) + re.compile(br'(\x1b\[\?1h\x1b=)'), + re.compile(br'((?:\x9b|\x1b\x5b)[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e])'), + re.compile(br'\x08.') + ] + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w]*\(.+\)\s*[\^\*]?(?:\[.+\])? ?#(?:\s*)$"), + re.compile(br"[pP]assword:$"), + re.compile(br"(?<=\s)[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\s*#\s*$"), + re.compile(br"[\r\n]?[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$"), + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"Error:", re.M), + re.compile(br"^% \w+", re.M), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found", re.I), + re.compile(br"'[^']' +returned error code: ?\d+"), + ] + + terminal_initial_prompt = b'Press any key to continue' + + terminal_initial_answer = b'\r' + + terminal_inital_prompt_newline = False + + def on_open_shell(self): + try: + self._exec_cli_command(b'no pag') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ce.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ce.py new file mode 100644 index 00000000..67936e8f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ce.py @@ -0,0 +1,60 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br'[\r\n]?<.+>(?:\s*)$'), + re.compile(br'[\r\n]?\[.+\](?:\s*)$'), + ] + #: terminal initial prompt + #: The password needs to be changed. Change now? [Y/N]: + terminal_initial_prompt = br'Change\s*now\s*\?\s*\[Y\/N\]\s*:' + + #: terminal initial answer + #: do not change password when it is asked to change with initial connection. + terminal_initial_answer = b'N' + terminal_stderr_re = [ + re.compile(br"% ?Error: "), + re.compile(br"^% \w+", re.M), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found", re.I), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"syntax error"), + re.compile(br"unknown command"), + re.compile(br"Error\[\d+\]: ", re.I), + re.compile(br"Error:", re.I) + ] + + def on_open_shell(self): + try: + self._exec_cli_command('screen-length 0 temporary') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/cnos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/cnos.py new file mode 100644 index 00000000..51a36cc8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/cnos.py @@ -0,0 +1,84 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains terminal Plugin methods for CNOS Config Module +# Lenovo Networking +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"), + re.compile(br">[\r\n]?") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + ] + + def on_open_shell(self): + try: + for cmd in (b'\n', b'terminal length 0\n', + b'no logging terminal\n'): + self._exec_cli_command(cmd) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + # Note: python-3.5 cannot combine u"" and r"" together. Thus make + # an r string and use to_text to ensure it's text + # on both py2 and py3. + cmd[u'prompt'] = to_text(r"[\r\n]?password: $", + errors='surrogate_or_strict') + cmd[u'answer'] = passwd + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), + errors='surrogate_or_strict')) + except AnsibleConnectionFailure: + msg = 'unable to elevate privilege to enable mode' + raise AnsibleConnectionFailure(msg) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(config' in prompt: + self._exec_cli_command(b'end') + self._exec_cli_command(b'disable') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'disable') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeos.py new file mode 100644 index 00000000..50f59016 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeos.py @@ -0,0 +1,35 @@ +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(br"\@[\w\-\.]+:\S+?[>#\$] ?$") + ] + + terminal_stderr_re = [ + re.compile(br"\n\s*command not found"), + re.compile(br"\nInvalid command"), + re.compile(br"\nCommit failed"), + re.compile(br"\n\s*Set failed"), + ] + + terminal_length = os.getenv('ANSIBLE_EDGEOS_TERMINAL_LENGTH', 10000) + + def on_open_shell(self): + try: + self._exec_cli_command('export VYATTA_PAGER=cat') + self._exec_cli_command('stty rows %s' % self.terminal_length) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeswitch.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeswitch.py new file mode 100644 index 00000000..27ac674d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/edgeswitch.py @@ -0,0 +1,87 @@ +# +# (c) 2018 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"\(([^\(\)]+)\) [>#]$"), + re.compile(br"\(([^\(\)]+)\) \(([^\(\)]+)\)#$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"An invalid") + ] + + def on_open_shell(self): + return + + def on_become(self, passwd=None): + prompt = self._get_prompt() + if prompt and prompt.endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + cmd[u'prompt'] = to_text(r"[\r\n]?[Pp]assword: ?$", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + + cmd = {u'command': u'terminal length 0'} + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to setup terminal in enable mode') + + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(Config' in prompt: + self._exec_cli_command(b'end') + self._exec_cli_command(b'exit') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'exit') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/enos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/enos.py new file mode 100644 index 00000000..e1b82d25 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/enos.py @@ -0,0 +1,83 @@ +# (C) 2017 Red Hat Inc. +# Copyright (C) 2017 Lenovo. +# +# GNU General Public License v3.0+ +# +# This program 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. +# +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Contains terminal Plugin methods for ENOS Config Module +# Lenovo Networking +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"), + re.compile(br">[\r\n]?") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + ] + + def on_open_shell(self): + try: + for cmd in (b'\n', b'terminal-length 0\n'): + self._exec_cli_command(cmd) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + # Note: python-3.5 cannot combine u"" and r"" together. Thus make + # an r string and use to_text to ensure it's text + # on both py2 and py3. + cmd[u'prompt'] = to_text(r"[\r\n]?password: $", + errors='surrogate_or_strict') + cmd[u'answer'] = passwd + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), + errors='surrogate_or_strict')) + except AnsibleConnectionFailure: + msg = 'unable to elevate privilege to enable mode' + raise AnsibleConnectionFailure(msg) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(config' in prompt: + self._exec_cli_command(b'end') + self._exec_cli_command(b'disable') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'disable') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/eric_eccli.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/eric_eccli.py new file mode 100644 index 00000000..d1622032 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/eric_eccli.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2019 Ericsson AB. +# +# 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 . +# + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible import constants as C +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase +from ansible.utils.display import Display +from ansible.module_utils.six import PY3 + +display = Display() + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?\[.*\][a-zA-Z0-9_.-]*[>\#] ?$"), + re.compile(br"[\r\n]?[a-zA-Z0-9_.-]*(?:\([^\)]+\))(?:[>#]) ?$"), + re.compile(br"bash\-\d\.\d(?:[$#]) ?"), + re.compile(br"[a-zA-Z0-9_.-]*\@[a-zA-Z0-9_.-]*\[\]\:\/flash\>") + ] + + terminal_stderr_re = [ + re.compile(br"[\r\n]+syntax error: .*"), + re.compile(br"Aborted: .*"), + re.compile(br"[\r\n]+Error: .*"), + re.compile(br"[\r\n]+% Error:.*"), + re.compile(br"[\r\n]+% Invalid input.*"), + re.compile(br"[\r\n]+% Incomplete command:.*") + ] + + def on_open_shell(self): + + try: + for cmd in (b'screen-length 0', b'screen-width 512'): + self._exec_cli_command(cmd) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/exos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/exos.py new file mode 100644 index 00000000..6836cdb8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/exos.py @@ -0,0 +1,59 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n](?:! )?(?:\* )?(?:\(.*\) )?(?:Slot-\d+ )?(?:VPEX )?\S+\.\d+ (?:[>#]) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Bad mask", re.I), + re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I), + re.compile(br"[%\S] ?Error: ?[\s]+", re.I), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"%% Invalid .* at '\^' marker.", re.I), + ] + + def on_open_shell(self): + try: + self._exec_cli_command(b'disable clipaging') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + try: + self._exec_cli_command(b'configure cli columns 256') + except AnsibleConnectionFailure: + self._connection.queue_message('warning', 'Unable to configure cli columns, command responses may be truncated') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/icx.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/icx.py new file mode 100644 index 00000000..e7d55549 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/icx.py @@ -0,0 +1,81 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +import json + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Bad mask", re.I), + re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I), + re.compile(br"[%\S] ?Error: ?[\s]+", re.I), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"Command authorization failed"), + re.compile(br"Error - *"), + re.compile(br"Error - Incorrect username or password."), + re.compile(br"Invalid input"), + re.compile(br"Already a http operation is in progress"), + re.compile(br"Flash access in progress. Please try later"), + re.compile(br"Error: .*"), + re.compile(br"^Error: .*", re.I), + re.compile(br"^Ambiguous input"), + re.compile(br"Errno") + ] + + def on_open_shell(self): + pass + + def __del__(self): + try: + self.close() + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + cmd[u'prompt'] = to_text(r"[\r\n](?:Local_)?[Pp]assword: ?$", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + cmd[u'prompt_retry_check'] = True + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + return + + if b'(config' in prompt: + self._exec_cli_command(b'exit') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'exit') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ironware.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ironware.py new file mode 100644 index 00000000..3651cbd1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/ironware.py @@ -0,0 +1,78 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?(?:\w+@)?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"[\r\n]Error - "), + re.compile(br"[\r\n](?:incomplete|ambiguous|unrecognised|invalid) (?:command|input)", re.I) + ] + + def on_open_shell(self): + self.disable_pager() + + def disable_pager(self): + cmd = {u'command': u'terminal length 0'} + try: + self._exec_cli_command(u'terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to disable terminal pager') + + def on_become(self, passwd=None): + if self._get_prompt().strip().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + # Note: python-3.5 cannot combine u"" and r"" together. Thus make + # an r string and use to_text to ensure it's text on both py2 and py3. + cmd[u'prompt'] = to_text(r"[\r\n]?password: ?$", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(config' in prompt: + self._exec_cli_command(b'end') + self._exec_cli_command(b'exit') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'exit') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/netvisor.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/netvisor.py new file mode 100644 index 00000000..27dffb59 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/netvisor.py @@ -0,0 +1,39 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br">.*[\r\n]?(.*)") + + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error: (?!\bdoes not exist\b)(?!\balready exists\b)") + ] diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/nos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/nos.py new file mode 100644 index 00000000..24518946 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/nos.py @@ -0,0 +1,54 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"([\r\n]|(\x1b\[\?7h))[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + # re.compile(br"^% \w+", re.M), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Bad mask", re.I), + re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"syntax error: unknown argument.", re.I) + ] + + def on_open_shell(self): + try: + self._exec_cli_command(u'terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/routeros.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/routeros.py new file mode 100644 index 00000000..0221b98d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/routeros.py @@ -0,0 +1,69 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase +from ansible.utils.display import Display + +display = Display() + + +class TerminalModule(TerminalBase): + + ansi_re = [ + # check ECMA-48 Section 5.4 (Control Sequences) + re.compile(br'(\x1b\[\?1h\x1b=)'), + re.compile(br'((?:\x9b|\x1b\x5b)[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e])'), + re.compile(br'\x08.') + ] + + terminal_initial_prompt = [ + br'\x1bZ', + ] + + terminal_initial_answer = b'\x1b/Z' + + terminal_stdout_re = [ + re.compile(br"\x1b<"), + re.compile(br"\[[\w\.]+\@[\w\s\-\.\/]+\] ?> ?$"), + re.compile(br"Please press \"Enter\" to continue!"), + re.compile(br"Do you want to see the software license\? \[Y\/n\]: ?"), + ] + + terminal_stderr_re = [ + re.compile(br"\nbad command name"), + re.compile(br"\nno such item"), + re.compile(br"\ninvalid value for"), + ] + + def on_open_shell(self): + prompt = self._get_prompt() + try: + if prompt.strip().endswith(b':'): + self._exec_cli_command(b' ') + if prompt.strip().endswith(b'!'): + self._exec_cli_command(b'\n') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to bypass license prompt') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/slxos.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/slxos.py new file mode 100644 index 00000000..24518946 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/slxos.py @@ -0,0 +1,54 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"([\r\n]|(\x1b\[\?7h))[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + # re.compile(br"^% \w+", re.M), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Bad mask", re.I), + re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"syntax error: unknown argument.", re.I) + ] + + def on_open_shell(self): + try: + self._exec_cli_command(u'terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/sros.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/sros.py new file mode 100644 index 00000000..77085a38 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/sros.py @@ -0,0 +1,43 @@ +# +# (c) 2016 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#|\$|>#) ?$"), + re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_stderr_re = [ + re.compile(br"Error:"), + ] + + def on_open_shell(self): + try: + self._exec_cli_command(b'environment no more') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/collections-debian-merged/ansible_collections/community/network/plugins/terminal/voss.py b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/voss.py new file mode 100644 index 00000000..f540be9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/network/plugins/terminal/voss.py @@ -0,0 +1,89 @@ +# +# (c) 2018 Extreme Networks 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]+[^\s#>]+(?:[>#])$", re.M) + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Discontiguous Subnet Mask"), + re.compile(br"Conflicting IP address"), + re.compile(br"[\r\n]Error: ?[\S]+"), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"Command authorization failed") + ] + + def on_open_shell(self): + try: + self._exec_cli_command(u'terminal more disable') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + # Note: python-3.5 cannot combine u"" and r"" together. Thus make + # an r string and use to_text to ensure it's text on both py2 and py3. + cmd[u'prompt'] = to_text(r"[\r\n](?:Local_)?[Pp]assword: ?$", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + cmd[u'prompt_retry_check'] = True + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if prompt.endswith(b')#'): + self._exec_cli_command(b'end') + self._exec_cli_command(b'disable') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'disable') -- cgit v1.2.3